พื้นฐานอาร์เรย์และการดำเนินการในที่เดิม
ทบทวนการอ้างดัชนี การเปลี่ยนค่า และข้อผิดพลาดที่พบบ่อยในการสัมภาษณ์ เช่น การคลาดเคลื่อนทีละหนึ่งและการแก้ไขลิสต์ขณะวนซ้ำ
พื้นฐานอาร์เรย์และการดำเนินการในที่เดิม เป็นบทเรียน DSA Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DSA Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
อาร์เรย์ในฐานะหน่วยความจำต่อเนื่อง
ภายในนั้น รายการของ Python ใช้ อาร์เรย์แบบไดนามิกเป็นโครงสร้างรองรับ — เป็นบล็อกหน่วยความจำต่อเนื่องที่เก็บสมาชิกไว้ในตำแหน่งที่อยู่ติดกัน การจัดวางนี้ทำให้เข้าถึงข้อมูลแบบสุ่มด้วยดัชนีได้ใน O(1): Python คำนวณ address = base + index × element_size ได้ทันที การแทรกหรือลบตรงกลางต้องเลื่อนสมาชิกถัดไปทั้งหมด จึงมีค่าใช้จ่ายเป็น O(n) ความไม่สมมาตรนี้เป็นที่มาของการพูดคุยเรื่องการแลกเปลี่ยนของอาร์เรย์ในการสัมภาษณ์ส่วนใหญ่
nums = [10, 20, 30, 40, 50]
# O(1) random access
print(nums[2]) # 30
print(nums[-1]) # 50
# O(1) append (amortised)
nums.append(60)
print(nums) # [10,20,30,40,50,60]
# O(n) insert at beginning
nums.insert(0, 0) # shifts all elements right
print(nums) # [0,10,20,30,40,50,60]นับเกินหรือนับขาด: ข้อผิดพลาดคลาสสิกของอาร์เรย์
ข้อผิดพลาดแบบนับเกินหรือนับขาดเป็นสาเหตุที่พบบ่อยที่สุดของคำตอบผิดในปัญหาอาร์เรย์ การจัดดัชนีเริ่มจาก 0 ของ Python หมายความว่าดัชนีสุดท้ายที่ใช้ได้คือ len(arr) - 1 เมื่อเขียนลูป ให้ตัดสินใจว่าต้องใช้ < หรือ <= โดยตรวจสอบเงื่อนไขขอบเขตกับข้อมูลเข้าที่เล็กที่สุดซึ่งใช้ได้ (n=1 หรือ n=2) ก่อนส่งคำตอบ ให้ไล่ตรวจขอบเขตด้วยตัวอย่างที่เป็นรูปธรรมเสมอ
def find_max(nums):
# Use len(nums)-1 as last index
max_val = nums[0] # safe if n >= 1
for i in range(1, len(nums)): # start at 1, not 0
if nums[i] > max_val:
max_val = nums[i]
return max_val
print(find_max([3, 1, 4, 1, 5])) # 5
print(find_max([7])) # 7 (single element)
# Would crash if we accessed nums[len(nums)]การกลับลำดับในข้อมูลเดิมด้วยตัวชี้สองตัว
การกลับลำดับอาร์เรย์ในข้อมูลเดิมใช้ตัวชี้สองตัวที่เริ่มจากปลายตรงข้ามกัน แล้วสลับสมาชิกเข้าหากันจนกว่าจะพบกัน วิธีนี้ใช้พื้นที่เพิ่มเติม O(1) และเวลา O(n) เงื่อนไข left < right (น้อยกว่าอย่างเคร่งครัด) ทำให้ถูกต้องทั้งกับความยาวคู่และคี่ — หากมีสมาชิกเป็นจำนวนคี่ สมาชิกตรงกลางจะอยู่ที่เดิมโดยอัตโนมัติ
def reverse_inplace(arr):
left, right = 0, len(arr) - 1
while left < right:
arr[left], arr[right] = arr[right], arr[left]
left += 1
right -= 1
# Space: O(1) Time: O(n)
a = [1, 2, 3, 4, 5]
reverse_inplace(a)
print(a) # [5, 4, 3, 2, 1]
b = [1, 2, 3]
reverse_inplace(b)
print(b) # [3, 2, 1] middle element unchangedการหมุนอาร์เรย์ในข้อมูลเดิม
การหมุนอาร์เรย์ไปทางขวา k ตำแหน่งสามารถทำในข้อมูลเดิมได้โดยกลับลำดับสามส่วน: กลับลำดับอาร์เรย์ทั้งหมด จากนั้นกลับลำดับสมาชิก k ตัวแรก แล้วจึงกลับลำดับสมาชิก n-k ตัวที่เหลือ วิธีนี้ใช้เวลา O(n) และพื้นที่ O(1) — ดีกว่าวิธีตัดแบ่งแล้วนำมาต่อกันซึ่งใช้พื้นที่ O(n) มาก ควรลด k ด้วยการหาเศษจากการหารด้วย n เสมอ เพื่อรองรับกรณี k ≥ n
def rotate(nums, k):
n = len(nums)
k %= n # handle k >= n
def rev(l, r):
while l < r:
nums[l], nums[r] = nums[r], nums[l]
l += 1; r -= 1
rev(0, n-1) # reverse all
rev(0, k-1) # reverse first k
rev(k, n-1) # reverse rest
a = [1, 2, 3, 4, 5, 6, 7]
rotate(a, 3)
print(a) # [5, 6, 7, 1, 2, 3, 4]การลบสมาชิกในข้อมูลเดิม
การลบค่าซ้ำหรือค่าที่ตรงกับเป้าหมายแบบ ในข้อมูลเดิมใช้ ตัวชี้เขียนเพื่อติดตามตำแหน่งที่ควรเขียนสมาชิกถัดไปที่ถูกต้อง ตัวชี้อ่านจะสแกนไปข้างหน้า เมื่อพบสมาชิกที่ถูกต้อง มันจะคัดลอกสมาชิกนั้นไปยังตำแหน่งเขียน แล้วเลื่อนตัวชี้ทั้งสองไปข้างหน้า นี่คือรูปแบบหลักสำหรับปัญหา LeetCode เช่น 'ลบสมาชิก', 'ลบค่าซ้ำจากอาร์เรย์ที่เรียงลำดับแล้ว' และ 'ย้ายเลขศูนย์'
def remove_element(nums, val):
write = 0
for read in range(len(nums)):
if nums[read] != val:
nums[write] = nums[read]
write += 1
return write # new length
nums = [3, 2, 2, 3]
new_len = remove_element(nums, 3)
print(nums[:new_len]) # [2, 2]
nums2 = [0, 1, 2, 2, 3, 0, 4, 2]
new_len2 = remove_element(nums2, 2)
print(nums2[:new_len2]) # [0, 1, 3, 0, 4]ย้ายเลขศูนย์: ตัวชี้อ่าน-เขียน
ย้ายเลขศูนย์ทั้งหมดไปยังท้ายอาร์เรย์ โดยคงลำดับของสมาชิกที่ไม่ใช่ศูนย์ไว้ แนวทางตัวชี้อ่าน-เขียนจะวางสมาชิกที่ไม่ใช่ศูนย์แต่ละตัวไว้ที่ตำแหน่งเขียน จากนั้นเติมส่วนท้ายด้วยเลขศูนย์ อีกแนวทางหนึ่งคือสลับเลขศูนย์ย้อนกลับ โดยคงลำดับไว้ได้โดยไม่ต้องมีรอบเติมข้อมูลครั้งที่สอง ทั้งสองแนวทางใช้เวลา O(n) และพื้นที่ O(1)
def move_zeroes(nums):
write = 0
# Move all non-zeroes to front
for read in range(len(nums)):
if nums[read] != 0:
nums[write] = nums[read]
write += 1
# Fill rest with zeroes
while write < len(nums):
nums[write] = 0
write += 1
a = [0, 1, 0, 3, 12]
move_zeroes(a)
print(a) # [1, 3, 12, 0, 0]ยกกำลังสองและเรียงลำดับในอาร์เรย์เดิม
เมื่อกำหนดอาร์เรย์จำนวนเต็มที่เรียงลำดับแล้ว ซึ่งอาจมีค่าติดลบ ให้ส่งคืนอาร์เรย์ของกำลังสองของสมาชิกเหล่านั้นในลำดับที่เรียงแล้ว วิธีตรงไปตรงมาคือยกกำลังสองก่อนแล้วจึงเรียงลำดับ ซึ่งใช้เวลา O(n log n) แนวทางตัวชี้สองตัวที่เหมาะสมที่สุดอาศัยข้อเท็จจริงที่ว่ากำลังสองที่มีค่ามากที่สุดมาจากปลายด้านใดด้านหนึ่งของข้อมูลนำเข้าที่เรียงลำดับแล้ว โดยเปรียบเทียบค่าสัมบูรณ์ของสมาชิกซ้ายสุดและขวาสุด แล้วเติมผลลัพธ์จากขวาไปซ้ายด้วยเวลา O(n)
def sorted_squares(nums):
n = len(nums)
result = [0] * n
left, right = 0, n - 1
pos = n - 1 # fill from the right
while left <= right:
l_sq = nums[left] ** 2
r_sq = nums[right] ** 2
if l_sq > r_sq:
result[pos] = l_sq
left += 1
else:
result[pos] = r_sq
right -= 1
pos -= 1
return result
print(sorted_squares([-4, -1, 0, 3, 10]))
# [0, 1, 9, 16, 100]การหาจุดหมุนและการแบ่งส่วน
ปัญหาธงชาติเนเธอร์แลนด์ แบ่งอาร์เรย์ออกเป็นสามส่วน (น้อยกว่าจุดหมุน เท่ากับจุดหมุน และมากกว่าจุดหมุน) ในอาร์เรย์เดิมโดยใช้ตัวชี้สามตัว นี่คือขั้นตอนย่อยสำคัญใน quick sort และเป็นวิธีแก้โจทย์ 'sort สี' ของ LeetCode การรักษาเงื่อนไขคงที่ว่า สมาชิกก่อนตัวชี้ low มีค่า < จุดหมุน และสมาชิกหลังตัวชี้ high มีค่า > จุดหมุน จะเป็นตัวขับเคลื่อนอัลกอริทึม
def sort_colors(nums):
# Dutch national flag: 0s, 1s, 2s
low, mid, high = 0, 0, len(nums) - 1
while mid <= high:
if nums[mid] == 0:
nums[low], nums[mid] = nums[mid], nums[low]
low += 1; mid += 1
elif nums[mid] == 1:
mid += 1
else:
nums[mid], nums[high] = nums[high], nums[mid]
high -= 1 # don't advance mid: new nums[mid] unexamined
a = [2, 0, 2, 1, 1, 0]
sort_colors(a)
print(a) # [0, 0, 1, 1, 2, 2]การแก้ไขสมาชิกอาร์เรย์ขณะวนซ้ำ
คุณสามารถ แก้ไขค่าของสมาชิก ได้อย่างปลอดภัย (เช่น คูณด้วย -1 เพื่อทำเครื่องหมายว่าเยี่ยมชมแล้ว) ระหว่างการวนซ้ำ แต่ ห้ามเปลี่ยนความยาว ของรายการระหว่างการวนรอบ วิธีเข้ารหัสที่ปลอดภัยคือเข้ารหัสค่าสองค่าไว้ชั่วคราวในจำนวนเต็มตัวเดียว (เช่น บิตเครื่องหมาย) เพื่อจำลองค่าบูลีนเพิ่มเติมหนึ่งค่าต่อสมาชิกโดยไม่จัดสรรพื้นที่เพิ่ม เทคนิคนี้ปรากฏในโจทย์ เช่น 'ค้นหาตัวเลขทั้งหมดที่หายไปจากอาร์เรย์'
def find_disappeared(nums):
# Mark visited by negating the value at the index
for n in nums:
idx = abs(n) - 1
if nums[idx] > 0:
nums[idx] *= -1 # mark as seen
# Indices with positive values are missing
return [i + 1 for i, v in enumerate(nums) if v > 0]
print(find_disappeared([4, 3, 2, 7, 8, 2, 3, 1]))
# [5, 6] -- O(n) time, O(1) extra spaceรายการตรวจสอบรูปแบบโจทย์อาร์เรย์สำหรับการสัมภาษณ์
ก่อนเขียนโค้ดสำหรับโจทย์อาร์เรย์ใด ๆ ให้ตรวจสอบรายการต่อไปนี้ในใจ:
- อาร์เรย์เรียงลำดับแล้วหรือไม่ (ทำให้ใช้ตัวชี้สองตัวและการค้นหาแบบทวิภาคได้)
- สมาชิกมีขอบเขตจำกัดหรือไม่ (เช่น 1..n) (ทำให้ใช้เทคนิคที่อาศัยดัชนีได้)
- จำเป็นต้องดำเนินการในอาร์เรย์เดิมหรือไม่ (ใช้ตัวชี้อ่าน-เขียนหรือการสลับค่า)
- ต้องการทุกคู่หรือเพียงคู่เดียว (มีผลต่อการยอมรับลูปซ้อนหรือไม่)
- กรณีขอบ: อาร์เรย์ว่าง สมาชิกเดียว หรือค่าของสมาชิกเหมือนกันทั้งหมด
def max_profit(prices):
# Pattern: single scan, track running minimum
# Time: O(n), Space: O(1)
if not prices: return 0 # edge case: empty
min_price = prices[0]
max_prof = 0
for price in prices[1:]: # start at index 1
max_prof = max(max_prof, price - min_price)
min_price = min(min_price, price)
return max_prof
print(max_profit([7, 1, 5, 3, 6, 4])) # 5
print(max_profit([7, 6, 4, 3, 1])) # 0อัลกอริทึมของ Kadane: ผลรวมช่วงย่อยสูงสุด
อัลกอริทึมของ Kadane ใช้ค้นหาช่วงย่อยที่อยู่ติดกันซึ่งมีผลรวมสูงสุด โดยใช้เวลา O(n) และพื้นที่ O(1) ในแต่ละขั้นตอน ให้ตัดสินใจว่าจะขยายช่วงย่อยปัจจุบันหรือเริ่มช่วงใหม่: current = max(num, current + num) หาก current + num น้อยกว่า num เพียงอย่างเดียว แสดงว่าช่วงย่อยปัจจุบันกำลังทำให้ผลรวมลดลง จึงเริ่มช่วงใหม่ ให้ติดตามค่าสูงสุดโดยรวมตลอดการทำงาน
def max_subarray(nums):
current = global_max = nums[0]
for n in nums[1:]:
current = max(n, current + n) # extend or restart
global_max = max(global_max, current)
return global_max
print(max_subarray([-2, 1, -3, 4, -1, 2, 1, -5, 4]))
# 6 (subarray [4, -1, 2, 1])
print(max_subarray([-1, -2, -3]))
# -1 (all negative: take the least negative)ตรวจสอบความเข้าใจอย่างรวดเร็ว
ตรวจสอบความเข้าใจของคุณเกี่ยวกับแนวคิดโครงสร้างข้อมูล & อัลกอริทึม — การเตรียมตัวสัมภาษณ์การเขียนโปรแกรมจากบทเรียนนี้
ทบทวนบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้ว่า อาร์เรย์ให้การเข้าถึงแบบสุ่มในเวลา O(1) แต่การแทรกและลบตรงกลางใช้เวลา O(n) — การทราบความไม่สมมาตรนี้ช่วยชี้นำการเลือกอัลกอริทึม รูปแบบตัวชี้อ่าน-เขียนช่วยลบสมาชิกหรือย้ายค่าในอาร์เรย์เดิมด้วยเวลา O(n) และพื้นที่ O(1) และ การเข้ารหัสด้วยบิตเครื่องหมายและเทคนิคใช้ดัชนีเป็นเครื่องหมายช่วยให้แก้โจทย์ด้วยพื้นที่ O(1) ได้ ทั้งที่มิฉะนั้นจะต้องใช้อาร์เรย์เสริม ถัดไปเราจะสำรวจผลรวมตั้งแต่ต้นและผลรวมสะสม
คำถามที่พบบ่อย
บทเรียน “พื้นฐานอาร์เรย์และการดำเนินการในที่เดิม” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “พื้นฐานอาร์เรย์และการดำเนินการในที่เดิม” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DSA Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “พื้นฐานอาร์เรย์และการดำเนินการในที่เดิม”
ทบทวนการอ้างดัชนี การเปลี่ยนค่า และข้อผิดพลาดที่พบบ่อยในการสัมภาษณ์ เช่น การคลาดเคลื่อนทีละหนึ่งและการแก้ไขลิสต์ขณะวนซ้ำ คุณปฏิบัติ DSA Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DSA Interview Prep หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน DSA Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน
บทเรียน “พื้นฐานอาร์เรย์และการดำเนินการในที่เดิม” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน DSA Interview Prep นี้ได้ไหม
ได้ บทเรียน DSA Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- พื้นฐานอาร์เรย์และการดำเนินการในที่เดิม
- ผลรวมคำนำหน้าและยอดรวมสะสม
- ตัวชี้สองตัว: จากปลายตรงข้าม
- ตัวชี้สองตัว: ช้าและเร็ว