Notes & software courses · Free to learn
Aph's Blog
On this page

บทเรียน: Array, String & Matrix

👋 อ่านฟรีทั้งหมดบน Aph's Blog — เนื้อหาภาษาไทย ทำตามทีละหน้าใน sidebar ได้เลย หากมีข้อเสนอแนะหรืออยากให้เพิ่มหัวข้อไหน บอกได้เสมอ

บทเรียนเต็มตั้งแต่พื้นฐานจนถึงเทคนิคขั้นสูง พร้อมโค้ด Python, ตัวอย่างไล่ทีละขั้น และโจทย์ฝึกมีเฉลย

Array คือโครงสร้างข้อมูลที่พื้นฐานที่สุดและเจอบ่อยที่สุดในการสัมภาษณ์ ถ้าเข้าใจบทนี้แน่น คุณจะต่อยอดไปหัวข้ออื่นได้ง่ายขึ้นมาก บทนี้จะพาไปตั้งแต่ "array คืออะไร" จนถึงเทคนิคที่ใช้แก้โจทย์ระดับ medium ได้จริง พร้อมโจทย์ฝึกมีเฉลยท้ายบท

1. Array คืออะไร (Mental Model)

Array คือกล่องข้อมูลหลาย ๆ ช่องที่เรียงต่อกันในหน่วยความจำ แต่ละช่องมีหมายเลขกำกับเรียกว่า index เริ่มนับจาก 0 เสมอ เพราะช่องเรียงต่อกันและรู้ขนาดของแต่ละช่อง คอมพิวเตอร์จึงกระโดดไปอ่านช่องไหนก็ได้ทันที (O(1)) โดยไม่ต้องไล่ทีละช่อง

index:    0     1     2     3     4
        +-----+-----+-----+-----+-----+
nums =  |  10 |  20 |  30 |  40 |  50 |
        +-----+-----+-----+-----+-----+

nums[0] = 10   (ช่องแรก)
nums[2] = 30
nums[4] = 50   (ช่องสุดท้าย = len - 1)
ทำไม index เริ่มที่ 0

index คือ "ระยะห่างจากช่องแรก" ช่องแรกห่างจากตัวเอง 0 ช่อง จึงเป็น index 0 เข้าใจจุดนี้จะช่วยลด bug off-by-one ได้มาก

2. การทำงานพื้นฐาน (Operations)

python
nums = [10, 20, 30, 40, 50]

print(nums[0])      # 10   อ่านค่าด้วย index
print(nums[-1])     # 50   index ติดลบ = นับจากท้าย
print(len(nums))    # 5    ความยาว

nums[1] = 99        # แก้ค่าในช่อง index 1
print(nums)         # [10, 99, 30, 40, 50]

# วน loop อ่านทุกค่า
for i, v in enumerate(nums):
    print(i, v)     # ได้ทั้ง index และค่า

เพิ่มและลบสมาชิก

python
nums = [10, 20, 30]
nums.append(40)       # เพิ่มท้าย -> [10, 20, 30, 40]
nums.insert(1, 15)    # แทรกที่ index 1 -> [10, 15, 20, 30, 40]
nums.pop()            # ลบท้าย -> [10, 15, 20, 30]
nums.pop(0)           # ลบ index 0 -> [15, 20, 30]
print(nums)

Complexity ของแต่ละการทำงาน (และเหตุผล)

การทำงานComplexityทำไม
เข้าถึง/แก้ด้วย indexO(1)กระโดดไปช่องนั้นได้เลย
ค้นหาค่า (array ไม่เรียง)O(n)ต้องไล่ดูทุกช่อง
append (เพิ่มท้าย)O(1) เฉลี่ยไม่ต้องเลื่อนใคร
insert/ลบกลาง arrayO(n)ต้องเลื่อนสมาชิกที่เหลือ
จุดสำคัญที่ถูกถามในสัมภาษณ์

การลบ/แทรกตรงกลางเป็น O(n) เพราะต้องเลื่อนของ ถ้าโจทย์ต้องเพิ่ม/ลบหัวท้ายบ่อย ๆ ควรนึกถึงโครงสร้างอื่น เช่น deque หรือ linked list

3. String คือ Array ของตัวอักษร

String ทำงานคล้าย array มาก เข้าถึงตัวอักษรด้วย index ได้ และตัด (slice) ได้ แต่มีจุดต่างสำคัญใน Python คือ string แก้ไขทีละตัวไม่ได้ (immutable) ถ้าจะแก้ต้องสร้างใหม่

python
s = "hello"
print(s[0])       # 'h'
print(s[1:4])     # 'ell'  (slice index 1 ถึง 3)
print(s[::-1])    # 'olleh' (กลับด้าน)
print(len(s))     # 5

words = "a,b,c".split(",")   # ['a', 'b', 'c']
joined = "-".join(words)      # 'a-b-c'

# s[0] = 'H'  # ❌ Error! string แก้ทีละตัวไม่ได้
s = "H" + s[1:]               # ✅ สร้างใหม่ -> 'Hello'
เคล็ดลับ

ถ้าต้องแก้ string หลายครั้ง ให้แปลงเป็น list ก่อน (list(s)) แก้เสร็จค่อย ''.join(...) กลับ จะเร็วกว่าการต่อ string ซ้ำ ๆ

4. เทคนิค Two Pointers

เทคนิคที่ใช้ตัวชี้ (pointer) สองตัวแทนการวน loop ซ้อน loop ช่วยลดเวลาจาก O(n²) เหลือ O(n) มี 2 รูปแบบหลัก

รูปแบบที่ 1: วิ่งเข้าหากัน (จากสองปลาย)

ตั้งตัวชี้ที่หัวและท้าย แล้วขยับเข้าหากัน เหมาะกับ palindrome หรือหาคู่ใน array ที่เรียงแล้ว

python
# เช็คว่าเป็น palindrome ไหม (อ่านหน้า-หลังเหมือนกัน)
def is_palindrome(s):
    i, j = 0, len(s) - 1
    while i < j:
        if s[i] != s[j]:
            return False
        i += 1
        j -= 1
    return True

print(is_palindrome("racecar"))  # True
print(is_palindrome("hello"))    # False
python
# หาคู่ที่บวกกันได้ target ใน array ที่เรียงแล้ว
def two_sum_sorted(nums, target):
    i, j = 0, len(nums) - 1
    while i < j:
        s = nums[i] + nums[j]
        if s == target:
            return [i, j]
        elif s < target:
            i += 1      # ผลรวมน้อยไป ดันซ้ายขึ้น
        else:
            j -= 1      # ผลรวมมากไป ดึงขวาลง
    return []

print(two_sum_sorted([1, 3, 4, 6, 8], 10))  # [2, 3] -> 4+6

รูปแบบที่ 2: วิ่งไปทางเดียวกัน (slow/fast)

ตัวชี้ slow เก็บตำแหน่งผลลัพธ์ ตัว fast ไล่อ่านข้อมูล เหมาะกับการลบซ้ำหรือกรองข้อมูลในที่เดิม

python
# ลบค่าซ้ำออกจาก array ที่เรียงแล้ว (แก้ในที่เดิม)
def remove_duplicates(nums):
    if not nums:
        return 0
    slow = 0
    for fast in range(1, len(nums)):
        if nums[fast] != nums[slow]:
            slow += 1
            nums[slow] = nums[fast]
    return slow + 1   # จำนวนสมาชิกที่ไม่ซ้ำ

arr = [1, 1, 2, 2, 3]
k = remove_duplicates(arr)
print(k, arr[:k])  # 3 [1, 2, 3]

5. เทคนิค Sliding Window

ใช้กับโจทย์ที่ถามเกี่ยวกับ "ช่วงที่ต่อเนื่องกัน" (subarray/substring) แทนที่จะคำนวณทุกช่วงใหม่ เราเลื่อนหน้าต่างทีละก้าวและอัปเดตเฉพาะส่วนที่เปลี่ยน

หน้าต่างขนาดคงที่ (Fixed window)

python
# ผลรวมมากสุดของ subarray ยาว k
def max_sum_k(nums, k):
    window = sum(nums[:k])      # หน้าต่างแรก
    best = window
    for i in range(k, len(nums)):
        window += nums[i] - nums[i - k]  # +ตัวใหม่ -ตัวเก่า
        best = max(best, window)
    return best

print(max_sum_k([2, 1, 5, 1, 3, 2], 3))  # 9 -> (5+1+3)

หน้าต่างขนาดยืดหยุ่น (Variable window)

python
# หาความยาว substring ที่ไม่มีตัวอักษรซ้ำ ที่ยาวที่สุด
def longest_unique(s):
    seen = set()
    left = 0
    best = 0
    for right in range(len(s)):
        while s[right] in seen:      # ถ้าซ้ำ หดหน้าต่างจากซ้าย
            seen.remove(s[left])
            left += 1
        seen.add(s[right])
        best = max(best, right - left + 1)
    return best

print(longest_unique("abcabcbb"))  # 3 -> "abc"

6. Prefix Sum (เทคนิคโบนัส)

ถ้าต้องถามผลรวมของช่วงต่าง ๆ บ่อย ๆ ให้คำนวณผลรวมสะสมไว้ก่อน แล้วตอบแต่ละคำถามได้ใน O(1)

python
nums = [3, 1, 4, 1, 5]
prefix = [0]
for n in nums:
    prefix.append(prefix[-1] + n)
# prefix = [0, 3, 4, 8, 9, 14]

# ผลรวมช่วง index 1 ถึง 3 = prefix[4] - prefix[1]
print(prefix[4] - prefix[1])  # 6 -> (1+4+1)

7. Matrix (Array 2 มิติ)

Matrix คือ array ที่สมาชิกแต่ละตัวเป็น array อีกที เข้าถึงด้วยสอง index คือ [แถว][คอลัมน์]

python
grid = [[1, 2, 3],
        [4, 5, 6]]

print(grid[1][2])       # 6  (แถว 1, คอลัมน์ 2)
print(len(grid))        # 2  จำนวนแถว
print(len(grid[0]))     # 3  จำนวนคอลัมน์

# วนทุกช่อง
for r in range(len(grid)):
    for c in range(len(grid[0])):
        print(grid[r][c], end=' ')
# 1 2 3 4 5 6

# สร้าง matrix ขนาด 3x3 เต็มไปด้วย 0
zeros = [[0] * 3 for _ in range(3)]
กับดักยอดฮิตของ matrix

อย่าสร้าง 2D list ด้วย [[0]*3]*3 เพราะทุกแถวจะชี้ไป list เดียวกัน แก้แถวหนึ่งเปลี่ยนหมด! ให้ใช้ [[0]*3 for _ in range(3)] แทน

8. จับสัญญาณว่าโจทย์ใช้เทคนิคไหน

เจอลักษณะนี้ในโจทย์ลองใช้
array เรียงแล้ว, หาคู่, palindromeTwo pointers (สองปลาย)
ลบ/กรองข้อมูลในที่เดิมTwo pointers (slow/fast)
subarray/substring ต่อเนื่อง ยาว/สั้นที่สุดSliding window
ถามผลรวมของช่วงหลาย ๆ ครั้งPrefix sum
ตาราง/กริด/พิกัดMatrix traversal

9. ตัวอย่างไล่ทีละขั้น (Two Sum sorted)

ลองไล่ดูว่า two_sum_sorted([1, 3, 4, 6, 8], 10) ทำงานอย่างไรทีละก้าว

nums = [1, 3, 4, 6, 8], target = 10

รอบ 1: i=0(1) j=4(8)  -> 1+8=9  < 10  ดัน i ขึ้น
รอบ 2: i=1(3) j=4(8)  -> 3+8=11 > 10  ดึง j ลง
รอบ 3: i=1(3) j=3(6)  -> 3+6=9  < 10  ดัน i ขึ้น
รอบ 4: i=2(4) j=3(6)  -> 4+6=10 = 10  เจอ! ตอบ [2, 3]

10. ข้อผิดพลาดที่พบบ่อย

  • Off-by-one — สับสนระหว่าง index สุดท้ายคือ len(arr) หรือ len(arr) - 1 (คำตอบคือ len-1)
  • แก้ไข array ขณะวน loop ทำให้ index เพี้ยน — ถ้าต้องลบ ให้วนจากท้ายมาหน้า หรือสร้าง list ใหม่
  • ลืมเช็ค array ว่าง ([]) หรือมีสมาชิกตัวเดียว
  • สร้าง matrix ผิดด้วย [[0]*n]*m (ดูกับดักด้านบน)

11. โจทย์ฝึก (ลองทำก่อนดูเฉลย)

ลองเขียนเองให้สุดก่อนกดดูเฉลยทุกข้อ — นี่คือวิธีฝึกที่ได้ผลที่สุด

โจทย์ 1 (ง่าย): กลับลำดับ array ในที่เดิม

รับ array แล้วกลับลำดับสมาชิกโดยไม่สร้าง array ใหม่ เช่น [1,2,3,4] -> [4,3,2,1]

ดูเฉลย

ใช้ two pointers จากสองปลาย สลับค่ากันแล้วขยับเข้าหากัน

python
def reverse_array(nums):
    i, j = 0, len(nums) - 1
    while i < j:
        nums[i], nums[j] = nums[j], nums[i]
        i += 1
        j -= 1
    return nums

print(reverse_array([1, 2, 3, 4]))  # [4, 3, 2, 1]

โจทย์ 2 (กลาง): ราคาหุ้นซื้อ-ขายกำไรสูงสุด

รับ array ราคาหุ้นรายวัน หากำไรสูงสุดจากการซื้อหนึ่งวันแล้วขายวันหลัง เช่น [7,1,5,3,6,4] -> 5 (ซื้อที่ 1 ขายที่ 6)

ดูคำใบ้

วนครั้งเดียว เก็บราคาต่ำสุดที่เคยเจอ และอัปเดตกำไรสูงสุด = ราคาวันนี้ - ราคาต่ำสุด

ดูเฉลย
python
def max_profit(prices):
    min_price = float('inf')
    best = 0
    for p in prices:
        min_price = min(min_price, p)   # ราคาต่ำสุดจนถึงตอนนี้
        best = max(best, p - min_price) # กำไรถ้าขายวันนี้
    return best

print(max_profit([7, 1, 5, 3, 6, 4]))  # 5

Complexity: O(n) เวลา, O(1) หน่วยความจำ — วนแค่รอบเดียว

โจทย์ 3 (กลาง): substring ไม่ซ้ำที่ยาวที่สุด

หาความยาวของ substring ที่ไม่มีตัวอักษรซ้ำที่ยาวที่สุด เช่น "pwwkew" -> 3 ("wke")

ดูเฉลย

ใช้ sliding window แบบยืดหยุ่น (เหมือนหัวข้อ 5) เก็บตัวอักษรในหน้าต่างด้วย set

python
def longest_unique(s):
    seen = set()
    left = best = 0
    for right in range(len(s)):
        while s[right] in seen:
            seen.remove(s[left])
            left += 1
        seen.add(s[right])
        best = max(best, right - left + 1)
    return best

print(longest_unique("pwwkew"))  # 3

สรุปบทนี้

  • Array เข้าถึงด้วย index O(1) แต่แทรก/ลบกลางเป็น O(n)
  • String คล้าย array แต่ใน Python แก้ทีละตัวไม่ได้ (immutable)
  • Two pointers ลดเวลา loop ซ้อนจาก O(n²) เหลือ O(n)
  • Sliding window ใช้กับโจทย์ช่วงต่อเนื่อง (subarray/substring)
  • Prefix sum ตอบผลรวมช่วงได้ O(1) ต่อคำถาม
  • Matrix คือ array 2 มิติ ระวังการสร้างด้วย [[0]*n]*m