Longest Increasing Subsequence
Kamu mendapatkan list bilangan bulat nums. Subsekuens mempertahankan beberapa elemen dalam urutan aslinya dan membuang sisanya; elemen yang dipertahankan tidak harus bersebelahan. Kembalikan panjang subsekuens terpanjang dengan nilai yang meningkat secara ketat dari kiri ke kanan. Dua nilai yang sama secara berurutan tidak dihitung sebagai meningkat.
Fungsi
- numsinteger-array
- daftar bilangan bulat untuk dipilih
- Mengembalikaninteger
- panjang subsekuens meningkat ketat terpanjang
Batasan
1 ≤ nums.length ≤ 2500-104 ≤ nums[i] ≤ 104
Contoh
- Input
- nums = [3, 1, 8, 2, 5, 9, 4, 7]
- Output
- 4
- Penjelasan
- 1, 2, 5, 9 terus meningkat, begitu juga 1, 2, 5, 7 dan 1, 2, 4, 7: panjangnya 4. Tidak ada pilihan lima nilai yang terus meningkat.
- Input
- nums = [7, 7, 7, 7]
- Output
- 1
- Penjelasan
- Nilainya harus terus meningkat, jadi tidak ada dua angka 7 yang bisa berada dalam subsekuens yang sama. Satu elemen saja sudah dihitung, sehingga jawabannya 1.
- Input
- nums = [12, -4, 0, 25, -10, 3, 16, 5]
- Output
- 4
- Penjelasan
- -4, 0, 3, 16 memiliki panjang 4. Dimulai dari elemen pertama, 12, kamu hanya mendapatkan dua nilai, seperti 12, 25: subsekuens terbaik tidak harus dimulai dari depan.
Pertanyaan lanjutan
Bisakah kamu mengembalikan salah satu subsekuens menaik terpanjang itu sendiri, beserta panjangnya, dan tetap berjalan dalam waktu O(n log n)?
Petunjuk
Buka satu per satu. Setiap petunjuk mengungkap sedikit lebih banyak.
Subsekuens terbaik dari seluruh list sulit dijelaskan secara langsung. Ajukan pertanyaan yang lebih spesifik untuk setiap indeks
i: apa subsekuens meningkat terpanjang yang berakhir tepat dengannums[i]?Subsekuens yang berakhir di
nums[i]bisa hanya terdiri darinums[i], atau melanjutkan subsekuens terbaik yang berakhir dinums[j] < nums[i]sebelumnya. Pilihjterbaik seperti itu, lalu tambahkan satu. Jawabannya adalah nilai terbesar dari nilai-nilai ini, di mana pun subsekuens itu berakhir.Untuk mendapatkan kompleksitas di bawah
O(n²), simpan hanya nilai terkecil yang bisa menjadi nilai akhir subsekuens untuk setiap panjang. Nilai-nilai tersebut tetap terurut, jadi pencarian biner akan memberi tahu apakah angka baru memperpanjang subsekuens terpanjang atau mengganti nilai akhirnya.
Solusi
List berisi n angka memiliki 2^n subsekuens, terlalu banyak untuk diperiksa. Jika kita menanyakan, untuk setiap indeks, berapa panjang subsekuens menaik terbaik yang berakhir tepat di sana, kita mendapatkan tabel O(n²). Versi tercepat menyimpan, untuk setiap panjang, nilai terkecil yang dapat menjadi nilai akhir subsekuens dengan panjang tersebut, lalu menempatkan setiap elemen menggunakan pencarian biner.
Ambil atau lewati setiap elemen
Benar, tetapi tidak selesai pada tes terbesar
Intuisi
Tentukan untuk setiap elemen apakah akan mempertahankannya; kamu boleh mempertahankan nums[i] hanya jika nilainya lebih besar daripada nilai terakhir yang dipertahankan. longest(i, prev) adalah jumlah elemen terbanyak yang masih bisa kamu tambahkan mulai dari indeks i, ketika elemen terakhir yang dipertahankan berada di prev (atau -1). Melewati elemen menghasilkan longest(i+1, prev); mempertahankannya menghasilkan 1 + longest(i+1, i). Setiap subsekuens meningkat adalah satu jalur dari pilihan-pilihan ini.
Pada 1, 2, 3, ..., n, kedua cabang tetap terbuka dan jumlah pemanggilan berlipat ganda untuk setiap elemen: 2^40 sudah sekitar 10^12. Namun, ada paling banyak n² pasangan berbeda (i, prev).
Algoritma
- Tulis
longest(i, prev), denganprevsebagai indeks elemen terakhir yang dipertahankan, atau-1. - Jika
imelewati akhir, kembalikan 0. - Lewati
nums[i]:best = longest(i+1, prev). - Jika
prevadalah-1ataunums[i] > nums[prev], pertahankan elemen tersebut:best = max(best, 1 + longest(i+1, i)). - Kembalikan
best. Jawabannya adalahlongest(0, -1).
def lengthOfLIS(nums):
def longest(i, prev):
# The longest increasing subsequence of nums[i:] whose values all exceed nums[prev].
# prev is -1 while nothing has been taken.
if i == len(nums):
return 0
best = longest(i + 1, prev) # skip nums[i]
if prev == -1 or nums[i] > nums[prev]:
best = max(best, 1 + longest(i + 1, i)) # take nums[i]
return best
return longest(0, -1)Suburutan terpanjang yang berakhir pada setiap indeks
Intuisi
Misalkan ending[i] adalah panjang subsekuens meningkat terpanjang yang elemen terakhirnya adalah nums[i]. Jika ada, elemen sebelum nums[i] adalah nums[j] dengan j < i dan nums[j] < nums[i], dan bagian hingga elemen itu harus sepanjang mungkin. Jadi, ending[i] = 1 + max(ending[j]) untuk semua j tersebut, atau 1 jika tidak ada. Isi nilainya dari kiri ke kanan.
Untuk [3, 1, 8, 2, 5, 9, 4, 7], tabelnya adalah [1, 1, 2, 2, 3, 4, 3, 4]: 5 dapat mengikuti 3, 1, atau 2, dan yang terbaik di antaranya memiliki panjang 2, jadi ending[4] = 3. Jawabannya adalah nilai terbesar, karena subsekuens terbaik dapat berakhir di mana saja.
n(n-1)/2 perbandingan: sekitar 3.1 × 10^6 untuk n = 2500.
Algoritma
- Buat
endingdengan setiap entri bernilai 1. - Untuk setiap
idari kiri ke kanan, periksa setiapj < i. - Jika
nums[j] < nums[i], aturending[i]menjadiending[j] + 1jika nilainya lebih besar. - Kembalikan nilai terbesar dalam
ending.
def lengthOfLIS(nums):
n = len(nums)
# ending[i]: the longest increasing subsequence that ends with nums[i]
ending = [1] * n
for i in range(1, n):
for j in range(i):
if nums[j] < nums[i] and ending[j] + 1 > ending[i]:
ending[i] = ending[j] + 1
return max(ending)Ekor terkecil dengan pencarian biner
Intuisi
Untuk setiap panjang, simpan hanya nilai terkecil yang dapat menjadi nilai akhir subsekuens menaik dengan panjang tersebut: tails[k] untuk panjang k+1. Nilai akhir yang lebih kecil tidak pernah lebih buruk: apa pun yang bisa mengikuti 9 juga bisa mengikuti 5. tails selalu meningkat secara ketat: hilangkan nilai terakhir dari subsekuens menaik dengan panjang k+2, lalu kamu mendapatkan subsekuens dengan panjang k+1 yang berakhir dengan nilai lebih kecil.
Untuk setiap x, lakukan pencarian biner untuk menemukan nilai akhir pertama yang ≥ x. Jika tidak ada, tambahkan x: ini memperpanjang subsekuens terpanjang. Jika ada, ganti nilai akhir tersebut dengan x: subsekuens yang panjangnya kurang satu berakhir dengan nilai di bawah x, jadi x menghasilkan panjang yang sama dengan nilai akhir yang lebih kecil.
Untuk contoh 1, tails menjadi [3], [1], [1, 8], [1, 2], [1, 2, 5], [1, 2, 5, 9], [1, 2, 4, 9], [1, 2, 4, 7]: panjangnya 4. Dalam [1, 2, 4, 9], nilai 4 muncul setelah 9, jadi hanya panjang tails yang bermakna.
Algoritma
- Mulai dengan list kosong
tails. - Untuk setiap
xdalamnums, lakukan pencarian biner untuk indeks pertamakdengantails[k] ≥ x. - Jika tidak ada tail yang
≥ x, tambahkanx. - Jika tidak, atur
tails[k] = x. - Kembalikan panjang
tails.
from bisect import bisect_left
def lengthOfLIS(nums):
# tails[k]: the smallest last value of any increasing subsequence of length k + 1
tails = []
for x in nums:
k = bisect_left(tails, x) # the first tail that is >= x
if k == len(tails):
tails.append(x) # x extends the longest subsequence so far
else:
tails[k] = x # x is a smaller ending for length k + 1
return len(tails)
Jebakan dan kasus tepi
- Mengembalikan
ending[n-1]alih-alih entri terbesar. Untuk[1, 2, 3, 0], entri terakhir adalah 1, tetapi jawabannya 3. - Membandingkan dengan
≤alih-alih<.[7, 7, 7, 7]harus mengembalikan 1, bukan 4. - Dalam versi tails, mencari tail pertama yang
> xalih-alih≥ x. Angka 7 yang kedua kemudian ditambahkan setelah angka pertama, sehingga nilai yang sama dihitung sebagai meningkat. - Keliru menyelesaikan versi berurutan. Dalam contoh 1, rangkaian naik terpanjang dari elemen yang bersebelahan adalah 2, 5, 9 (panjang 3), sedangkan jawabannya 4.
- Tetap menggunakan indeks berbasis 0 di Lua atau R, padahal array dimulai dari 1. Penanda
prev = -1menjadi 0, dan pencarian biner berjalan pada indeks 1 hingga ukuran saat ini.
Pertanyaan yang sering diajukan3
Apa kompleksitas waktu dari Longest Increasing Subsequence?
method tails berjalan dalam waktu O(n log n) dan ruang O(n): satu pencarian biner untuk setiap elemen. Tabel untuk setiap pasangan indeks membutuhkan waktu O(n²), dan mencoba setiap subsequence membutuhkan O(2ⁿ).
Apa itu patience sorting dalam Longest Increasing Subsequence?
Ini adalah method tails yang dinamai berdasarkan permainan kartu. Setiap tails adalah kartu teratas dari sebuah tumpukan: sebuah kartu ditempatkan pada tumpukan paling kiri yang kartu teratasnya memiliki nilai setidaknya sebesar nilainya, atau memulai tumpukan baru. Jumlah tumpukan adalah jawabannya.
Bagaimana cara menemukan subsekuens tidak menurun yang terpanjang?
Izinkan elemen bertetangga bernilai sama. Pada tabel, gunakan nums[j] ≤ nums[i]. Dalam method tails, cari tail pertama yang nilainya lebih besar secara ketat dari x, sehingga nilai yang sama memperpanjang list alih-alih mengganti tail. Setelah itu, [7, 7, 7, 7] menghasilkan 4.
Soal serupa
Soal yang memakai ide yang sama. Mengerjakan dua atau tiga di antaranya membuat polanya melekat.
def lengthOfLIS(nums):
# Tulis kodemu di siniTes #1
Tes #2
Tes #3
Argumen
nums = [3, 1, 8, 2, 5, 9, 4, 7]
Output yang diharapkan
4