การกลับลำดับลิสต์เชื่อมโยง
กลับลำดับลิสต์เชื่อมโยงเดี่ยวทั้งแบบวนซ้ำด้วยการจัดการการเชื่อมใหม่ของตัวชี้สามตัว และแบบเรียกซ้ำ โดยติดตามแต่ละขั้นบนแผนภาพสไตล์กระดานไวท์บอร์ด
การกลับลำดับลิสต์เชื่อมโยง เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
เหตุใดการกลับลำดับรายการจึงสำคัญ
การกลับลำดับรายการเชื่อมโยงเป็นหนึ่งในคำถามสัมภาษณ์การเขียนโปรแกรมที่ถูกถามบ่อยที่สุด โดยทดสอบความสามารถของคุณในการจัดการตัวชี้อย่างแม่นยำโดยไม่สูญเสียการติดตามโหนด รูปแบบย่อยปรากฏทั้งในปัญหาเดี่ยวและเป็นขั้นตอนย่อยภายในอัลกอริทึมขนาดใหญ่ เช่น การตรวจจับพาลินโดรม การจัดเรียงรายการใหม่ และการกลับลำดับเป็นกลุ่ม k โหนด
แนวทางแบบวนซ้ำใช้ตัวชี้สามตัว ได้แก่ prev, curr และ next_node ส่วนแนวทางแบบเรียกซ้ำแสดงตรรกะเดียวกันในรูปแบบการเดินผ่านสแตกการเรียก ทั้งสองแนวทางใช้เวลา O(n) และแนวทางแบบวนซ้ำใช้พื้นที่ O(1)
การกลับลำดับแบบวนซ้ำด้วยตัวชี้สามตัว
ในแต่ละขั้นตอนของการกลับลำดับแบบวนซ้ำ: บันทึก curr.next เพื่อไม่ให้ส่วนที่เหลือของรายการสูญหาย เปลี่ยนทิศทางของ curr.next ให้ชี้ย้อนกลับไปยัง prev เลื่อน prev ไปยัง curr และเลื่อน curr ไปยัง next ที่บันทึกไว้ เมื่อ curr กลายเป็น None ลูปจะสิ้นสุด และ prev จะเป็นหัวรายการใหม่
คำช่วยจำที่มีประโยชน์คือ: บันทึก เปลี่ยนทิศทาง เลื่อน เลื่อน
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_list(head):
prev, curr = None, head
while curr:
next_node = curr.next # Save
curr.next = prev # Flip
prev = curr # Advance prev
curr = next_node # Advance curr
return prev # new head
# Test
nodes = [ListNode(i) for i in range(1, 6)]
for i in range(len(nodes) - 1):
nodes[i].next = nodes[i+1]
head = reverse_list(nodes[0])
while head:
print(head.val, end=' ') # 5 4 3 2 1
head = head.nextการติดตามการทำงานทีละขั้น
ให้เราติดตามการทำงานของ reverse_list บน 1 -> 2 -> 3 ในตอนเริ่มต้น prev=None, curr=1 ขั้นที่ 1: บันทึกโหนดถัดไปเป็น 2 กลับลิงก์ของ 1 ให้ชี้เป็น None กำหนดโหนดก่อนหน้าเป็น 1 และโหนดปัจจุบันเป็น 2 ขั้นที่ 2: บันทึกโหนดถัดไปเป็น 3 กลับลิงก์ของ 2 ให้ชี้ไปยัง 1 กำหนดโหนดก่อนหน้าเป็น 2 และโหนดปัจจุบันเป็น 3 ขั้นที่ 3: บันทึกโหนดถัดไปเป็น None กลับลิงก์ของ 3 ให้ชี้ไปยัง 2 กำหนดโหนดก่อนหน้าเป็น 3 และโหนดปัจจุบันเป็น None ลูปจบลง แล้วคืนค่าโหนดก่อนหน้าเป็น 3 ซึ่งเป็นหัวรายการใหม่ของ 3 -> 2 -> 1
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_list_traced(head):
prev, curr = None, head
step = 0
while curr:
step += 1
next_node = curr.next
curr.next = prev
print(f'Step {step}: flipped {curr.val}.next -> {prev.val if prev else None}')
prev = curr
curr = next_node
return prev
nodes = [ListNode(i) for i in [1, 2, 3]]
for i in range(len(nodes) - 1):
nodes[i].next = nodes[i+1]
head = reverse_list_traced(nodes[0])
print('New head:', head.val) # 3การกลับลำดับแบบเรียกซ้ำ
วิธีแบบเรียกซ้ำเชื่อว่า reverse_list(head.next) จะคืนหัวรายการใหม่ของส่วนต่อท้ายที่กลับลำดับแล้ว สิ่งที่เหลือมีเพียงการกลับตัวชี้ระหว่าง head และ head.next: กำหนด head.next.next = head (ให้โหนดที่สองเดิมชี้กลับมายังโหนดแรกเดิม) และกำหนด head.next = None (ตัดลิงก์ไปข้างหน้าเดิม) หัวรายการใหม่จะถูกส่งย้อนกลับขึ้นมาจากกรณีฐาน
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_list_rec(head):
# Base case: empty or single node
if not head or not head.next:
return head
new_head = reverse_list_rec(head.next) # reverse suffix
head.next.next = head # former second node points back
head.next = None # sever forward link
return new_head
nodes = [ListNode(i) for i in range(1, 5)]
for i in range(len(nodes) - 1):
nodes[i].next = nodes[i+1]
head = reverse_list_rec(nodes[0])
while head:
print(head.val, end=' ') # 4 3 2 1
head = head.nextการกลับลำดับรายการย่อย (LeetCode 92)
LeetCode 92 เรื่อง 'กลับลำดับรายการเชื่อมโยง II' ให้กลับลำดับรายการย่อยตั้งแต่ตำแหน่งซ้ายถึงตำแหน่งขวา (เริ่มนับจาก 1) โดยใช้การเดินผ่านรายการเพียงรอบเดียว เคล็ดลับคือค้นหาโหนดก่อนรายการย่อย (ใช้หัวรายการจำลองเพื่อให้กรณีนี้ใช้ได้เสมอ) จากนั้นกลับลำดับด้วยตัวชี้สามตัวเป็นจำนวนขั้นเท่ากับผลต่างระหว่างตำแหน่งขวากับตำแหน่งซ้ายพอดี แล้วเชื่อมส่วนที่กลับลำดับแล้วกลับเข้ากับรายการโดยรอบ
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverseBetween(head, left, right):
dummy = ListNode(0, head)
pre = dummy
# Advance pre to node just before position 'left'
for _ in range(left - 1):
pre = pre.next
curr = pre.next
for _ in range(right - left):
next_node = curr.next
curr.next = next_node.next
next_node.next = pre.next
pre.next = next_node
return dummy.next
nodes = [ListNode(i) for i in range(1, 6)]
for i in range(len(nodes) - 1):
nodes[i].next = nodes[i+1]
head = reverseBetween(nodes[0], 2, 4)
while head:
print(head.val, end=' ') # 1 4 3 2 5
head = head.nextการกลับลำดับโหนดเป็นกลุ่ม k (LeetCode 25)
LeetCode 25 เรื่อง 'กลับลำดับโหนดเป็นกลุ่ม k' จะกลับลำดับทุกกลุ่มที่มีโหนดต่อเนื่องกัน k โหนด วิธีการคือ ตรวจสอบก่อนว่ายังมีโหนดเหลืออย่างน้อย k โหนดหรือไม่ หากไม่มีก็ปล่อยไว้ตามเดิม กลับลำดับ k โหนดถัดไปด้วยวิธีแบบวนซ้ำ จากนั้นกลับลำดับรายการส่วนที่เหลือแบบเรียกซ้ำแล้วเชื่อมเข้าด้วยกัน ความซับซ้อนด้านเวลายังคงเป็น O(n) โดยมีความลึกของการเรียกซ้ำเป็น O(n/k)
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverseKGroup(head, k):
# Check if k nodes are available
curr, count = head, 0
while curr and count < k:
curr = curr.next
count += 1
if count < k:
return head # fewer than k nodes left, keep as-is
# Reverse k nodes
prev, curr = None, head
for _ in range(k):
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
# head is now the tail of the reversed group
head.next = reverseKGroup(curr, k)
return prev
nodes = [ListNode(i) for i in range(1, 6)]
for i in range(len(nodes) - 1):
nodes[i].next = nodes[i+1]
head = reverseKGroup(nodes[0], 2)
while head:
print(head.val, end=' ') # 2 1 4 3 5
head = head.nextรายการเชื่อมโยงแบบพาลินโดรม
LeetCode 234 เรื่อง 'รายการเชื่อมโยงแบบพาลินโดรม': ตรวจสอบว่ารายการเชื่อมโยงเป็นพาลินโดรมหรือไม่ โดยใช้เวลา O(n) และพื้นที่ O(1) กลยุทธ์คือหาจุดกึ่งกลางด้วยตัวชี้ช้าและเร็ว กลับลำดับครึ่งหลังในตำแหน่งเดิม เปรียบเทียบโหนดของทั้งสองครึ่งทีละโหนด แล้วเลือกคืนสภาพรายการตามต้องการ วิธีนี้เชื่อมทักษะพื้นฐานสองอย่างเข้าด้วยกัน ได้แก่ การหาจุดกึ่งกลางและการกลับลำดับ
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def isPalindrome(head):
# Find mid
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# Reverse second half
prev, curr = None, slow
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
# Compare
left, right = head, prev
while right:
if left.val != right.val:
return False
left = left.next
right = right.next
return True
def build(arr):
d = ListNode(0)
c = d
for v in arr:
c.next = ListNode(v)
c = c.next
return d.next
print(isPalindrome(build([1,2,2,1]))) # True
print(isPalindrome(build([1,2,3]))) # Falseเปรียบเทียบแบบวนซ้ำกับแบบเรียกซ้ำ
การกลับลำดับแบบวนซ้ำใช้พื้นที่ O(1) และโดยทั่วไปเป็นวิธีที่ควรเลือกใช้ การกลับลำดับแบบเรียกซ้ำใช้พื้นที่สแตก O(n) เนื่องจากความลึกของการเรียก ซึ่งอาจทำให้สแตกทำงานล้นเมื่อรายการยาวมาก (ขีดจำกัดเริ่มต้นของไพธอนอยู่ที่ประมาณ 1,000 ระดับการเรียกซ้ำ)
ในการสัมภาษณ์งาน ควรเขียนแบบวนซ้ำก่อนเพื่อแสดงให้เห็นว่าเข้าใจข้อจำกัดด้านพื้นที่ จากนั้นจึงกล่าวถึงแบบเรียกซ้ำว่าเป็นทางเลือกที่อ่านง่ายกว่า หากกำหนดความยาวรายการไว้ได้
import sys
print('Default recursion limit:', sys.getrecursionlimit())
# For a list of 10,000 nodes the recursive reversal would hit this limit
# Iterative reversal has no such constraint
# Increase if needed (use sparingly):
# sys.setrecursionlimit(20000)ข้อผิดพลาดที่พบบ่อยในการกลับลำดับ
ข้อผิดพลาดเกือบทั้งหมดในการกลับลำดับเกิดจากสามประการ ประการแรกคือไม่บันทึกโหนดถัดไปก่อนเขียนทับ: curr.next = prev จะทำลายการอ้างอิงไปข้างหน้า หากไม่ได้บันทึก next_node ไว้ก่อน ประการที่สองคือไม่คืนค่าโหนดก่อนหน้า: เมื่อจบลูป curr จะเป็น None แต่ prev คือหัวรายการใหม่ ประการที่สามคือกำหนดกรณีฐานของการเรียกซ้ำไม่ถูกต้อง: หากลืม not head.next รายการที่มีโหนดเดียวจะไม่ถูกจัดการ และทำให้เกิด AttributeError
# Minimal correct iterative reversal — annotated against common bugs
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_list(head):
prev, curr = None, head
while curr:
next_node = curr.next # BUG if omitted: lose rest of list
curr.next = prev
prev = curr
curr = next_node
return prev # BUG if you return curr: it is None
nodes = [ListNode(i) for i in [1, 2, 3]]
nodes[0].next = nodes[1]
nodes[1].next = nodes[2]
h = reverse_list(nodes[0])
while h:
print(h.val, end=' ') # 3 2 1
h = h.nextจัดเรียงรายการใหม่ (LeetCode 143)
LeetCode 143 เรื่อง 'จัดเรียงรายการใหม่' จัดลำดับ L0 → L1 → L2 → ... → Ln ใหม่เป็น L0 → Ln → L1 → Ln-1 → L2 → Ln-2 โดยใช้เวลา O(n) และพื้นที่ O(1) วิธีแก้ประกอบด้วยสามขั้นตอน: หาจุดกึ่งกลาง กลับลำดับครึ่งหลัง และสอดแทรกสองครึ่งเข้าด้วยกัน การเชี่ยวชาญการกลับลำดับทำให้โจทย์ที่ดูซับซ้อนนี้กลายเป็นการผสมผสานเทคนิคที่คุ้นเคยอย่างตรงไปตรงมา
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reorderList(head):
if not head or not head.next:
return
# Find mid
slow = fast = head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
# Reverse second half
prev, curr = None, slow.next
slow.next = None
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
# Interleave
first, second = head, prev
while second:
tmp1, tmp2 = first.next, second.next
first.next = second
second.next = tmp1
first, second = tmp1, tmp2
nodes = [ListNode(i) for i in range(1, 5)]
for i in range(len(nodes) - 1):
nodes[i].next = nodes[i+1]
reorderList(nodes[0])
h = nodes[0]
while h:
print(h.val, end=' ') # 1 4 2 3
h = h.nextสรุป: การกลับลำดับคือส่วนประกอบพื้นฐาน
การกลับลำดับรายการเชื่อมโยงแทบไม่ใช่เป้าหมายสุดท้าย แต่เป็นส่วนประกอบพื้นฐาน การตรวจจับพาลินโดรม การกลับลำดับเป็นกลุ่ม k การจัดเรียงรายการใหม่ และการกลับลำดับระหว่างตำแหน่งต่าง ๆ ล้วนใช้รูปแบบการวนซ้ำด้วยตัวชี้สามตัวแบบเดียวกัน เมื่อรูปแบบนี้กลายเป็นสิ่งที่ทำได้โดยอัตโนมัติ คุณจะมีสมาธิเหลือสำหรับพิจารณาโครงสร้างของโจทย์ในระดับที่สูงขึ้น
ควรฝึกการกลับลำดับอยู่เสมอจนสามารถเขียนได้จากความจำภายในเวลาไม่ถึงสองนาที เพราะรูปแบบนี้จะปรากฏในรูปแบบใดรูปแบบหนึ่งแทบทุกการสัมภาษณ์งานเกี่ยวกับรายการเชื่อมโยง
ตรวจสอบความเข้าใจอย่างรวดเร็ว
ทดสอบความเข้าใจแนวคิดเรื่องโครงสร้างข้อมูลและอัลกอริทึม — การเตรียมตัวสัมภาษณ์การเขียนโปรแกรมจากบทเรียนนี้
ทบทวนบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้ว่า: รูปแบบบันทึก-กลับลิงก์-เลื่อน-เลื่อนแบบวนซ้ำกลับลำดับรายการได้ในเวลา O(n) และใช้พื้นที่ O(1) วิธีแบบเรียกซ้ำเชื่อว่าส่วนต่อท้ายกลับลำดับไว้แล้ว และแก้ไขเพียงลิงก์สุดท้าย และ การกลับลำดับเป็นขั้นตอนย่อยสำคัญในการตรวจจับพาลินโดรม การจัดเรียงรายการใหม่ และการกลับลำดับเป็นกลุ่ม k ต่อไปเราจะสำรวจการตรวจจับวงจรด้วยอัลกอริทึมของฟลอยด์
คำถามที่พบบ่อย
บทเรียน “การกลับลำดับลิสต์เชื่อมโยง” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การกลับลำดับลิสต์เชื่อมโยง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Coding Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การกลับลำดับลิสต์เชื่อมโยง”
กลับลำดับลิสต์เชื่อมโยงเดี่ยวทั้งแบบวนซ้ำด้วยการจัดการการเชื่อมใหม่ของตัวชี้สามตัว และแบบเรียกซ้ำ โดยติดตามแต่ละขั้นบนแผนภาพสไตล์กระดานไวท์บอร์ด คุณปฏิบัติ Coding Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Coding Interview Prep หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Coding Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “การกลับลำดับลิสต์เชื่อมโยง” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Coding Interview Prep นี้ได้ไหม
ได้ บทเรียน Coding Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- คลาสโหนดและการสร้างลิสต์
- การกลับลำดับลิสต์เชื่อมโยง
- การตรวจจับวัฏจักรด้วยอัลกอริทึมของ Floyd
- ผสาน แบ่ง และค้นหาโหนดลำดับที่ n จากท้าย