On this page
- 1. Array คืออะไร (Mental Model)
- 2. การทำงานพื้นฐาน (Operations)
- เพิ่มและลบสมาชิก
- Complexity ของแต่ละการทำงาน (และเหตุผล)
- 3. String คือ Array ของตัวอักษร
- 4. เทคนิค Two Pointers
- รูปแบบที่ 1: วิ่งเข้าหากัน (จากสองปลาย)
- รูปแบบที่ 2: วิ่งไปทางเดียวกัน (slow/fast)
- 5. เทคนิค Sliding Window
- หน้าต่างขนาดคงที่ (Fixed window)
- หน้าต่างขนาดยืดหยุ่น (Variable window)
- 6. Prefix Sum (เทคนิคโบนัส)
- 7. Matrix (Array 2 มิติ)
- 8. จับสัญญาณว่าโจทย์ใช้เทคนิคไหน
- 9. ตัวอย่างไล่ทีละขั้น (Two Sum sorted)
- 10. ข้อผิดพลาดที่พบบ่อย
- 11. โจทย์ฝึก (ลองทำก่อนดูเฉลย)
- โจทย์ 1 (ง่าย): กลับลำดับ array ในที่เดิม
- โจทย์ 2 (กลาง): ราคาหุ้นซื้อ-ขายกำไรสูงสุด
- โจทย์ 3 (กลาง): substring ไม่ซ้ำที่ยาวที่สุด
- สรุปบทนี้
บทเรียน: Array, String & Matrix
บทเรียนเต็มตั้งแต่พื้นฐานจนถึงเทคนิคขั้นสูง พร้อมโค้ด 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 เข้าใจจุดนี้จะช่วยลด bug off-by-one ได้มาก
2. การทำงานพื้นฐาน (Operations)
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 และค่าเพิ่มและลบสมาชิก
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 | ทำไม |
|---|---|---|
| เข้าถึง/แก้ด้วย index | O(1) | กระโดดไปช่องนั้นได้เลย |
| ค้นหาค่า (array ไม่เรียง) | O(n) | ต้องไล่ดูทุกช่อง |
| append (เพิ่มท้าย) | O(1) เฉลี่ย | ไม่ต้องเลื่อนใคร |
| insert/ลบกลาง array | O(n) | ต้องเลื่อนสมาชิกที่เหลือ |
การลบ/แทรกตรงกลางเป็น O(n) เพราะต้องเลื่อนของ ถ้าโจทย์ต้องเพิ่ม/ลบหัวท้ายบ่อย ๆ ควรนึกถึงโครงสร้างอื่น เช่น deque หรือ linked list
3. String คือ Array ของตัวอักษร
String ทำงานคล้าย array มาก เข้าถึงตัวอักษรด้วย index ได้ และตัด (slice) ได้ แต่มีจุดต่างสำคัญใน Python คือ string แก้ไขทีละตัวไม่ได้ (immutable) ถ้าจะแก้ต้องสร้างใหม่
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 ที่เรียงแล้ว
# เช็คว่าเป็น 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# หาคู่ที่บวกกันได้ 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 ไล่อ่านข้อมูล เหมาะกับการลบซ้ำหรือกรองข้อมูลในที่เดิม
# ลบค่าซ้ำออกจาก 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)
# ผลรวมมากสุดของ 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)
# หาความยาว 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)
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 คือ [แถว][คอลัมน์]
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)]อย่าสร้าง 2D list ด้วย [[0]*3]*3 เพราะทุกแถวจะชี้ไป list เดียวกัน แก้แถวหนึ่งเปลี่ยนหมด! ให้ใช้ [[0]*3 for _ in range(3)] แทน
8. จับสัญญาณว่าโจทย์ใช้เทคนิคไหน
| เจอลักษณะนี้ในโจทย์ | ลองใช้ |
|---|---|
| array เรียงแล้ว, หาคู่, palindrome | Two 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 จากสองปลาย สลับค่ากันแล้วขยับเข้าหากัน
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)
ดูคำใบ้
วนครั้งเดียว เก็บราคาต่ำสุดที่เคยเจอ และอัปเดตกำไรสูงสุด = ราคาวันนี้ - ราคาต่ำสุด
ดูเฉลย
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])) # 5Complexity: O(n) เวลา, O(1) หน่วยความจำ — วนแค่รอบเดียว
โจทย์ 3 (กลาง): substring ไม่ซ้ำที่ยาวที่สุด
หาความยาวของ substring ที่ไม่มีตัวอักษรซ้ำที่ยาวที่สุด เช่น "pwwkew" -> 3 ("wke")
ดูเฉลย
ใช้ sliding window แบบยืดหยุ่น (เหมือนหัวข้อ 5) เก็บตัวอักษรในหน้าต่างด้วย set
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