0Pricing
DSA Interview Prep · บทเรียน

พื้นฐานอาร์เรย์และการดำเนินการในที่เดิม

ทบทวนการอ้างดัชนี การเปลี่ยนค่า และข้อผิดพลาดที่พบบ่อยในการสัมภาษณ์ เช่น การคลาดเคลื่อนทีละหนึ่งและการแก้ไขลิสต์ขณะวนซ้ำ

พื้นฐานอาร์เรย์และการดำเนินการในที่เดิม เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. พื้นฐานอาร์เรย์และการดำเนินการในที่เดิม
  2. ผลรวมคำนำหน้าและยอดรวมสะสม
  3. ตัวชี้สองตัว: จากปลายตรงข้าม
  4. ตัวชี้สองตัว: ช้าและเร็ว
← กลับไปที่ DSA Interview Prep