Reorder List
Kamu mendapatkan singly linked list yang disimpan dalam dua array dengan panjang yang sama. Node i menyimpan nilai values[i] dan terhubung ke node next[i], -1 mengakhiri list, dan head-nya adalah node 0. Node-node tersebut tidak disimpan sesuai urutan list, jadi ikuti tautannya.
Sebut node-node sesuai urutannya dalam list sebagai L0, L1, ..., Lk. Susun ulang list sehingga urutannya menjadi L0, Lk, L1, Lk-1, L2, Lk-2, ...: node pertama, lalu node terakhir, lalu node kedua, lalu node kedua dari belakang, dan seterusnya. Kembalikan nilai-nilai dalam urutan baru tersebut.
Fungsi
- nextinteger-array
- indeks node yang ditautkan oleh setiap node, atau -1 untuk node terakhir
- valuesinteger-array
- nilai yang disimpan oleh setiap node
- Mengembalikaninteger-array
- nilai dalam urutan list yang disusun ulang
Batasan
1 ≤ n ≤ 4000, dengannsebagai panjangnextdanvalues.-1000 ≤ values[i] ≤ 1000- Setiap
next[i]adalah-1atau indeks node dari0hinggan-1. - Mulai dari node
0, list mengunjungi setiap node tepat satu kali lalu mencapai-1. Tidak ada siklus.
Contoh
- Input
- next = [3, 4, -1, 1, 2]values = [1, 3, 5, 2, 4]
- Output
- [1, 5, 2, 4, 3]
- Penjelasan
- Dengan mengikuti tautan dari node
0, daftar tersebut terbaca1, 2, 3, 4, 5. Elemen pertama, terakhir, kedua, dan kedua dari belakang menghasilkan1, 5, 2, 4, lalu nilai tengah3muncul terakhir.
- Input
- next = [2, 3, 1, -1]values = [10, 30, 20, 40]
- Output
- [10, 40, 20, 30]
- Penjelasan
- list mengunjungi node
0, 2, 1, 3dan membaca10, 20, 30, 40. Pasangan pertama dan terakhir menghasilkan10, 40, lalu pasangan di bagian dalam menghasilkan20, 30.
- Input
- next = [-1]values = [7]
- Output
- [7]
- Penjelasan
- List dengan satu node tidak memiliki apa pun untuk dipindahkan, jadi jawabannya adalah
[7].
Pertanyaan lanjutan
Dapatkah kamu mengurutkan ulang tautannya dengan memori tambahan O(1)?
Petunjuk
Buka satu per satu. Setiap petunjuk mengungkap sedikit lebih banyak.
Tuliskan urutan target untuk
1, 2, 3, 4, 5, 6. Node mana yang berasal dari bagian depan list, dan dalam urutan apa node dari bagian belakang muncul?Bagian belakang muncul dalam urutan terbalik:
6, 5, 4. Jika bagian belakang dibalik, jawabannya adalah dua list yang digabungkan dengan mengambil satu node dari masing-masing secara bergantian.Temukan bagian tengah dengan pointer lambat yang bergerak satu tautan dan pointer cepat yang bergerak dua tautan. Potong list setelah bagian tengah, balik bagian kedua dengan membalik tautan-tautannya, lalu jalin kedua bagian bersama-sama node demi node.
Solusi
Urutan baru bergantian antara bagian depan dan belakang, tetapi singly linked list hanya bergerak maju. Bagi pekerjaan ini menjadi tiga langkah standar pada list: temukan bagian tengah dengan pointer lambat dan cepat, balikkan paruh belakang, lalu jalin kedua paruh secara bergantian satu node setiap kali.
Gerakkan ekor ke depan, satu node setiap kali
Intuisi
Pointer di sini adalah indeks node, dan bergerak maju berarti node = next[node]. Setelah node pertama ada node terakhir, jadi lepaskan node terakhir dan sisipkan setelah node saat ini, lalu ulangi dua node lebih jauh. Untuk 1, 2, 3, 4, 5 hasilnya adalah 1, 5, 2, 3, 4, lalu 1, 5, 2, 4, 3. Berhenti saat kurang dari dua node mengikuti node saat ini.
Mencari node sebelum node terakhir mengharuskan kamu menelusuri hingga akhir pada setiap putaran. Penelusuran berkurang dua node setiap putaran, sekitar n²/4 langkah secara total: empat juta untuk 4000 node.
Algoritma
- Tetapkan
cur = 0. - Selama setidaknya dua node mengikuti
cur, telusuribeforedaricurhingganext[next[before]] == -1, sehinggatail = next[before]adalah node terakhir. - Lepaskan node itu dengan
next[before] = -1, lalu sisipkan:next[tail] = next[cur]dannext[cur] = tail. - Pindahkan
curkenext[tail]dan ulangi. - Telusuri mulai dari node
0dan kumpulkan nilainya.
def reorderList(next, values):
cur = 0
# While at least two nodes follow cur, move the last node right after cur.
while next[cur] != -1 and next[next[cur]] != -1:
before = cur
while next[next[before]] != -1: # find the node before the tail
before = next[before]
tail = next[before]
next[before] = -1
next[tail] = next[cur]
next[cur] = tail
cur = next[tail]
result = []
node = 0
while node != -1:
result.append(values[node])
node = next[node]
return resultSalin nilai dan baca dari kedua ujung
Intuisi
array mencapai elemen terakhirnya dalam satu langkah. Salin nilai sesuai urutan list, [1, 2, 3, 4, 5] pada contoh pertama, lalu baca dengan lo dari depan dan hi dari belakang: ambil lo, ambil hi, lalu gerakkan keduanya ke arah tengah selama lo < hi. Hasilnya adalah 1, 5, 2, 4; jika panjangnya ganjil, keduanya bertemu pada 3 di tengah, dan elemen itu ditempatkan terakhir.
Kompleksitas waktunya O(n) dan memori tambahannya O(n), serta tautannya tidak pernah berubah.
Algoritma
- Telusuri mulai dari simpul
0dan tambahkan setiap nilai keinOrder. - Tetapkan
lo = 0danhike indeks terakhir. - Selama
lo < hi, tambahkaninOrder[lo]daninOrder[hi], lalu naikkanlodan turunkanhi. - Jika
lo == hi, tambahkan nilai tengah.
def reorderList(next, values):
# Write the values out in list order.
in_order = []
node = 0
while node != -1:
in_order.append(values[node])
node = next[node]
# Take from the front and the back in turn.
result = []
lo, hi = 0, len(in_order) - 1
while lo < hi:
result.append(in_order[lo])
result.append(in_order[hi])
lo += 1
hi -= 1
if lo == hi: # odd length: the middle value comes last
result.append(in_order[lo])
return resultTemukan bagian tengah, balik urutan separuh bagian belakang, lalu jalin
Intuisi
Untuk 1, 2, 3, 4, 5, 6, jawabannya adalah 1, 6, 2, 5, 3, 4: separuh depan 1, 2, 3 secara berurutan, diselingi separuh belakang 4, 5, 6 yang dibaca dari belakang. Jadi, potong list di bagian tengah, balik separuh belakang, lalu selang-selingkan.
slow bergerak satu tautan, sedangkan fast bergerak dua tautan, sehingga slow berhenti di node terakhir separuh depan: 3, baik untuk lima maupun enam node. Memotong setelahnya membuat separuh depan memiliki satu node tambahan saat panjangnya ganjil, dan node itu menjadi node terakhir dalam jawaban. Proses selang-seling mengambil satu node dari setiap separuh secara bergantian sampai separuh belakang habis.
Setiap node disentuh beberapa kali dalam jumlah tetap, dengan beberapa indeks sebagai memori. R menyalin vektor tautan saat pertama kali diubah, jadi di R memorinya adalah O(n).
Algoritma
- Pindahkan
slowsatu tautan danfastdua tautan selamanext[fast]dannext[next[fast]]bukan-1. - Tetapkan
second = next[slow], lalunext[slow] = -1. - Balikkan list yang dimulai dari
second; head barunya adalahprev. - Jalin: selama
secondbukan-1, tautkanfirstkeseconddansecondkenext[first]yang lama, lalu majukan keduanya. - Telusuri mulai dari node
0dan kumpulkan nilainya.
def reorderList(next, values):
# 1. Find the middle: slow stops on the last node of the first half.
slow = fast = 0
while next[fast] != -1 and next[next[fast]] != -1:
slow = next[slow]
fast = next[next[fast]]
second = next[slow]
next[slow] = -1 # cut the list in two
# 2. Reverse the second half.
prev = -1
while second != -1:
after = next[second]
next[second] = prev
prev = second
second = after
# 3. Weave: one node from the front half, one from the reversed back half.
first, second = 0, prev
while second != -1:
first_after, second_after = next[first], next[second]
next[first] = second
next[second] = first_after
first, second = first_after, second_after
result = []
node = 0
while node != -1:
result.append(values[node])
node = next[node]
return result
Jebakan dan kasus tepi
- Jangan berhenti memotong list di bagian tengah. Tanpa
next[slow] = -1, bagian depan masih menunjuk ke bagian belakang, dan setelah diselang-selingkan, list akan berputar kembali ke dirinya sendiri. - Jangan menimpa tautan sebelum menyimpannya. Saat membalik dan menyelang-selingkan, baca
next[node]ke dalam variabel terlebih dahulu, atau sisa list akan hilang. - Jangan menyelang-selingkan sampai bagian depan berakhir. Jika panjangnya ganjil, perulangan itu berjalan satu kali lagi saat
secondbernilai-1, lalu membacanext[-1]. - Jangan menganggap urutan array sebagai urutan list. Pada contoh pertama,
valuesadalah[1, 3, 5, 2, 4], tetapi urutan list adalah1, 2, 3, 4, 5. - Di Lua dan R, indeks array dimulai dari 1. Tetap gunakan indeks node berbasis 0 dan baca
next[node + 1].
Pertanyaan yang sering diajukan3
Mengapa membalik paruh kedua list?
Jawaban membaca bagian belakang dari node terakhir hingga node pertama, sedangkan singly linked list hanya berjalan ke depan. Membalik bagian belakang sekali, dalam O(n), memungkinkan penggabungan berjalan maju pada kedua bagian.
Apa kompleksitas waktu dari Reorder List?
method in-place berjalan dalam waktu O(n) dengan memori tambahan O(1): mencari bagian tengah, membalik, dan menyelang-selingkan masing-masing mengunjungi setiap node sebanyak jumlah konstan. Menyalin nilai ke dalam array juga membutuhkan waktu O(n), tetapi memerlukan memori O(n), sedangkan memajukan tail satu node setiap kali membutuhkan waktu O(n²).
Di mana bagian tengahnya jika list memiliki jumlah node genap?
Hentikan pointer lambat di node terakhir pada separuh bagian depan. Jika ada enam node, kedua separuh terdiri dari tiga node; jika ada lima, jumlahnya tiga dan dua. Node tambahan itu menjadi akhir jawaban, sesuai dengan posisi nilai tengah dalam urutan.
Soal serupa
Soal yang memakai ide yang sama. Mengerjakan dua atau tiga di antaranya membuat polanya melekat.
def reorderList(next, values):
# Tulis kodemu di siniTes #1
Tes #2
Tes #3
Argumen
next = [3, 4, -1, 1, 2] values = [1, 3, 5, 2, 4]
Output yang diharapkan
[1, 5, 2, 4, 3]