On this page
ข้อ 16 · LC1004 Max Consecutive Ones III (หนึ่งต่อเนื่องมากสุด พลิก k) 🟡
พลิก 0 เป็น 1 ได้มากสุด k ตัว หาช่วง 1 ต่อเนื่องที่ยาวที่สุด
โจทย์ (LC1004): กำหนด binary array (มีแค่ 0 กับ 1) ชื่อ nums และเลขจำนวนเต็ม k ให้ return จำนวนสูงสุดของ 1 ที่ต่อเนื่องกันในอาร์เรย์ หากสามารถพลิก (flip) 0 เป็น 1 ได้อย่างมากที่สุด k ตัว
- Input:
- nums = [1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0], k = 2
- Output:
- 6
- Explanation:
- พลิก 0 สองตัวตรงกลาง (index 4-5) เป็น 1 จะได้ช่วง [1,1,1,1,1,1] ยาว 6 ตัวติดกัน — ยาวที่สุดเท่าที่พลิกได้ 2 ตัว
- Input:
- nums = [0, 0, 0], k = 0
- Output:
- 0
- Explanation:
- k = 0 พลิกไม่ได้เลยสักตัว และไม่มี 1 อยู่ในอาร์เรย์เดิม จึงไม่มีช่วง 1 ต่อเนื่องแม้แต่ตัวเดียว
- 1 <= nums.length <= 10^5
- nums[i] เป็น 0 หรือ 1 เท่านั้น
- 0 <= k <= nums.length
แนวทาง — ต้องใช้อะไร & คิดยังไง
ใช้ Sliding Window ขนาดยืดหยุ่น (variable size) กุญแจคือเปลี่ยนมุมมอง: "พลิก 0 ได้ k ตัว" เท่ากับ "window ที่ยาวที่สุดที่มี 0 ไม่เกิน k ตัว" เพราะ 0 ทุกตัวใน window พลิกเป็น 1 ได้ตราบใดที่ไม่เกิน k
brute force ลองทุกช่วงแล้วนับ 0 เป็น O(n²) ช้า Sliding Window ขยายขวารับของเข้าเรื่อย ๆ track จำนวน 0 ไว้ ถ้าเกิน k ค่อยหดซ้าย จึงเหลือ O(n)
- initialize left = 0, zeros = 0, best = 0
- iterate right ไปทุก index ถ้า nums[right] == 0 ให้ zeros += 1 (รับตัวใหม่ทางขวา)
- ขณะที่ zeros > k ให้หดซ้าย: ถ้า nums[left] == 0 ลด zeros แล้ว left += 1
- window ถูกต้องแล้ว update best = max(best, right - left + 1)
- จบ loop return best
ต้องใช้ while ไม่ใช่ if ตอนหดซ้าย และความยาว window คือ right - left + 1 (บวกหนึ่งเพราะรวมทั้งสองปลาย)
ไล่ทีละสเต็ป
ลองไล่ nums = [1, 1, 0, 0, 1], k = 1 ดูว่า left หดตอนไหน
| right (ค่า) | zeros | หดซ้าย? | left | ความยาว (right-left+1) | best |
|---|---|---|---|---|---|
| 0 (1) | 0 | ไม่ | 0 | 1 | 1 |
| 1 (1) | 0 | ไม่ | 0 | 2 | 2 |
| 2 (0) | 1 | ไม่ (≤k) | 0 | 3 | 3 |
| 3 (0) | 2 | ใช่ จน zeros≤1 | 3 | 1 | 3 |
| 4 (1) | 1 | ไม่ | 3 | 2 | 3 |
best = 3 (ช่วง [1, 1, 0] พลิก 0 หนึ่งตัวได้ 1 สามตัว)
▶ เฉลยละเอียด (ลองเองก่อนนะ)
def longest_ones(nums, k):
left = 0
zeros = 0 # จำนวน 0 ในหน้าต่างตอนนี้
best = 0
for right in range(len(nums)):
if nums[right] == 0:
zeros += 1 # รับตัวใหม่ทางขวา
while zeros > k: # 0 เกินโควตาพลิก
if nums[left] == 0:
zeros -= 1 # หดซ้าย ถอด 0 ออก
left += 1
best = max(best, right - left + 1) # หน้าต่างตอนนี้ถูกต้อง
return best
print(longest_ones([1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0], 2)) # 66กุญแจคือเปลี่ยนมุมมอง: แทนที่จะคิดว่า "พลิก 0 กี่ตัว" ให้คิดว่า "window ที่ยาวที่สุดที่มี 0 ไม่เกิน k ตัว" มีค่าเท่ากัน เพราะ 0 ทุกตัวใน window พลิกเป็น 1 ได้ตราบใดที่ไม่เกิน k เราจึงขยายขวารับของเข้าเรื่อย ๆ และ track จำนวน 0 ไว้
เมื่อ 0 ใน window เกิน k เราหดซ้าย (ขยับ left) จนจำนวน 0 กลับมาไม่เกิน k left ขยับรวมกันไม่เกิน n ครั้งตลอด loop จึงยังเป็น O(n) ไม่ใช่ O(n²) ถ้าเผลอใช้ if แทน while จะหด 0 ไม่พอเมื่อ k ลดหลายตัว
Time O(n) แต่ละตัวเข้าและออก window อย่างละครั้ง · Space O(1) track แค่ตัวนับกับ pointer (ตัวชี้)
window ขนาดยืดหยุ่น + "เปลี่ยนโจทย์เป็น condition (เงื่อนไข) บน window": พลิก k ตัว = window ที่มี 0 ไม่เกิน k ขยายขวาเสมอ หดซ้ายเมื่อผิด condition