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

ข้อ 15 · LC1456 Maximum Number of Vowels in a Substring of Given Length (นับสระในหน้าต่าง k) 🟡

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

หา substring ยาว k ที่มีตัวสระมากที่สุด แล้วคืนจำนวนสระนั้น

โจทย์ (LC1456): กำหนด string s และเลขจำนวนเต็ม k ให้หาจำนวน vowel (สระในภาษาอังกฤษ: a, e, i, o, u) ที่มากที่สุด ที่สามารถปรากฏได้ใน substring ใด ๆ ของ s ที่มีความยาวเท่ากับ k พอดี

Example 1
Input:
s = "abciiidef", k = 3
Output:
3
Explanation:
ช่วง "iii" มีสระครบทั้ง 3 ตัว เป็น substring ยาว 3 ที่มีสระมากที่สุดที่เป็นไปได้
Example 2
Input:
s = "aeiou", k = 2
Output:
2
Explanation:
ทุกตัวอักษรเป็นสระ ดังนั้น substring ยาว 2 ช่วงไหนก็มีสระครบ 2 ตัวเท่ากันหมด
Example 3
Input:
s = "leetcode", k = 3
Output:
2
Explanation:
ช่วง "lee", "eet" หรือ "ode" ต่างมีสระ 2 ตัว ซึ่งเป็นค่ามากที่สุดที่หาได้ในสตริงนี้
Constraints (ข้อจำกัด)
  • 1 <= s.length <= 10^5
  • s เป็นตัวอักษรอังกฤษพิมพ์เล็ก
  • 1 <= k <= s.length

แนวทาง — ต้องใช้อะไร & คิดยังไง

ใช้ Sliding Window ขนาดคงที่ k เหมือนข้อก่อน แต่แทนที่จะเก็บผลรวม เรา track "จำนวน vowel ใน window" แทน เช็คว่าตัวอักษรเป็นสระด้วย set("aeiou") (hash set) ทำให้เช็คได้ O(1)

brute force นับสระใหม่ในทุก substring ยาว k เป็น O(nk) ช้า Sliding Window เก็บ count แล้วปรับทีละหนึ่งตอนเลื่อน จึงเหลือ O(n)

  1. เตรียม set ของสระ นับสระใน window แรก s[:k] เก็บใน count แล้ว initialize best = count
  2. iterate i จาก k ไปจนจบ string
  3. ถ้าตัวใหม่ s[i] เป็นสระ count += 1
  4. ถ้าตัวเก่า s[i - k] เป็นสระ count -= 1
  5. update best = max(best, count)
  6. (ปรับให้เร็ว) ถ้า best == k หยุดได้เลย เพราะสระเยอะสุดใน window ยาว k คือ k
จุดพลาดที่พบบ่อย

ตัวที่ต้องถอดคือ s[i - k] (ห่างไป k ช่อง) ไม่ใช่ s[i - k + 1] — ใช้ index ผิดจะนับเพี้ยนทันที

ไล่ทีละสเต็ป

ลองไล่ s = "leetcode", k = 3 window แรก "lee" มีสระ 2 (e, e)

iตัวเข้า s[i]ตัวออก s[i-k]count หลังปรับbest
เริ่ม--2 (lee)2
3t (ไม่ใช่สระ)l (ไม่ใช่สระ)2 (eet)2
4c (ไม่ใช่สระ)e (สระ)1 (etc)2
5o (สระ)e (สระ)1 (tco)2
6d (ไม่ใช่สระ)t (ไม่ใช่สระ)1 (cod)2
7e (สระ)c (ไม่ใช่สระ)2 (ode)2

best = 2 ตลอด คืนค่า 2

▶ เฉลยละเอียด (ลองเองก่อนนะ)
เฉลย (Python) — โค้ดนี้รันได้จริงpython
def max_vowels(s, k):
    vowels = set("aeiou")
    count = sum(1 for c in s[:k] if c in vowels)   # นับสระในหน้าต่างแรก
    best = count
    for i in range(k, len(s)):
        if s[i] in vowels:        # ตัวใหม่เข้าทางขวา
            count += 1
        if s[i - k] in vowels:    # ตัวเก่าหลุดออกทางซ้าย
            count -= 1
        best = max(best, count)
        if best == k:             # เต็มหน้าต่างแล้ว ไม่มีทางมากกว่านี้
            break
    return best

print(max_vowels("abciiidef", 3))  # 3
print(max_vowels("leetcode", 3))   # 2
Output
3
2

แทนที่จะนับสระใหม่ทุก window (ช้า O(nk)) เรา track count แล้วปรับทีละหนึ่งตอนเลื่อน: ตัวใหม่เข้าเป็นสระ +1, ตัวเก่าที่หลุดออก (s[i - k]) เป็นสระ -1 ใช้ set("aeiou") เช็คว่าเป็นสระ O(1)

if best == k แล้ว break เป็นการปรับให้เร็วขึ้น เพราะสระมากสุดใน window ยาว k คือ k อยู่แล้ว เจอครบก็ไม่ต้องดูต่อ เอาออกก็ยังถูก เพียงแต่ iterate ครบทุกตัว

Time O(n) iterate string รอบเดียว · Space O(1) hash set ของสระมีแค่ 5 ตัว ถือเป็นค่าคงที่

💡 สรุป pattern

window ขนาดคงที่ที่ track "count" แทนผลรวม: ตัวเข้าเพิ่มนับ ตัวออกลดนับ pattern เดียวกับผลรวมแต่เปลี่ยนสิ่งที่ track เท่านั้น