CHUYÊN ĐỀ COMPETITIVE PROGRAMMING — TUẦN 4

Sliding Window

Pattern chuyên dụng cho mọi bài có chữ "subarray liên tiếp" hoặc "substring" với điều kiện. Biến O(n²) thành O(n) bằng cách không bao giờ quay lại phần đã xét.

10 worked examples · 2 knowledge checks · 12 bài về nhà

Phần 1. Nền tảng Sliding Window

Hai biến thể, tín hiệu nhận biết, và vì sao luôn O(n)
1.1

1.1 Sliding Window là gì?

Định nghĩa:
Sliding Window duy trì một cửa sổ liên tục [left, right] trên mảng/string, di chuyển hai biên theo điều kiện bài toán. Mỗi biên di chuyển một chiều (chỉ tiến, không lùi) → tổng số lần di chuyển ≤ 2n → O(n).

Quan hệ với Two Pointers (Tuần 2)

Sliding Window là biến thể chuyên biệt của Two Pointers Same Direction. Khác biệt:

Tinh thần

Brute force: thử mọi cặp (i, j) với i ≤ j → O(n²) cửa sổ. Sliding window: chỉ duyệt qua các cửa sổ "có ý nghĩa" theo đường monoton → O(n).

1.2

1.2 Hai biến thể — Trực quan

Fixed Window (k = 3) — kích thước cố định 3 1 4 1 5 9 2 Cửa sổ size 3 trượt từng bước Variable Window — kích thước co/dãn theo điều kiện a b c d e f ↑ left ↑ right

Cả hai biến thể đều có right tăng đơn điệu. Khác biệt: Fixed có left = right − k + 1; Variable có left tăng theo điều kiện.

1.3

1.3 Tín hiệu nhận biết Sliding Window

Tín hiệu trong đềBiến thể
"subarray/substring kích thước k"Fixed Window
"subarray có k phần tử liên tiếp..."Fixed Window
"tất cả anagram độ dài p trong s"Fixed Window (size = len(p))
"subarray nhỏ nhất có tổng ≥ s"Variable Window
"subarray dài nhất thoả điều kiện X"Variable Window
"substring không lặp ký tự"Variable Window
"≤ k loại ký tự khác nhau"Variable Window
"đếm số subarray thoả điều kiện"Variable Window (kỹ thuật ≤K − ≤K-1)
Ghi nhớ chốt:
"Subarray/substring liên tiếp" + "kích thước k" → Fixed.
"Subarray/substring liên tiếp" + "max/min/đếm" + điều kiện → Variable.
1.4

1.4 Vì sao Sliding Window luôn O(n)? Phân tích Amortised

Một lo lắng tự nhiên: trong Variable Window, vòng while shrink có thể chạy nhiều lần trong một bước — sao tổng time là O(n)?

Phân tích amortised

Quan sát: cả leftright đều di chuyển chỉ tiến.

$$ T(n) = O(\text{right moves}) + O(\text{left moves}) + O(\text{const work per step}) = O(n) $$
Tinh thần chung:
Đây cùng tinh thần với phân tích Longest Consecutive Sequence (Tuần 3). Nguyên tắc: nếu mỗi đơn vị công việc chỉ "trả tiền" một lần qua toàn thuật toán, tổng vẫn O(n) dù trong một bước có thể tốn nhiều.

Phần 2. Fixed Window

Cửa sổ kích thước cố định k — đơn giản, là điểm khởi đầu lý tưởng
2.1

2.1 Template chuẩn — Fixed Window

Mô hình tổng quát:
def fixed_window(arr, k):
    # Pha 1: build cửa sổ đầu tiên
    window_state = init(arr[:k])
    best = evaluate(window_state)

    # Pha 2: trượt cửa sổ
    for i in range(k, len(arr)):
        window_state = add(window_state, arr[i])         # thêm bên phải
        window_state = remove(window_state, arr[i - k])  # bỏ bên trái
        best = update_best(best, evaluate(window_state))
    return best

Ba chỗ tuỳ biến

  1. window_state: tổng / max / Counter / set / bitmask...
  2. add, remove: cập nhật O(1) nếu state là tổng / Counter.
  3. evaluate: tính giá trị muốn theo dõi từ state.

Time chuẩn: O(n) nếu add/remove/evaluate đều O(1) hoặc O(k) phân bổ.

2.2

2.2 Worked Example 1 — Maximum Average Subarray I

Đề (LeetCode #643): Mảng nums, số k. Tìm trung bình lớn nhất của subarray liên tiếp size k.

Constraints: 1 ≤ k ≤ n ≤ 10⁵ → cần O(n).

Brute force

Duyệt mọi cửa sổ, tính tổng → O(n × k). Với n,k = 10⁵ → 10¹⁰ → TLE.

Sliding Window

def findMaxAverage(nums, k):
    window_sum = sum(nums[:k])           # cửa sổ đầu tiên
    best = window_sum
    for i in range(k, len(nums)):
        window_sum += nums[i] - nums[i - k]   # add right, remove left — O(1)
        best = max(best, window_sum)
    return best / k

Time O(n), Space O(1).

2.3

2.3 Maximum Average — Trace tay

nums = [1, 12, -5, -6, 50, 3], k = 4.

Cửa sổ ban đầu

nums[0..3] = [1,12,-5,-6], sum = 2, best = 2.

inums[i]nums[i−k]thay đổiwindow_sumbest
4501 (idx 0)+50 −12 + 49 = 5151
5312 (idx 1)+3 −1251 − 9 = 4251

Trả về 51 / 4 = 12.75.

Bí quyết O(1) update:
nums[i] − nums[i − k] — chỉ cộng phần tử mới và trừ phần tử bị đẩy ra. Đây là tinh thần "incremental update" — không tính lại tổng từ đầu.
2.4

2.4 Worked Example 2 — Find All Anagrams in a String

Đề (LeetCode #438): String s, pattern p. Tìm mọi index bắt đầu của substring trong s là anagram của p.

Ví dụ: s = "cbaebabacd", p = "abc" → [0, 6] (substring "cba" và "bac").

Ý tưởng

Cửa sổ size len(p). So sánh Counter của cửa sổ với Counter của p.

from collections import Counter
def findAnagrams(s, p):
    if len(s) < len(p): return []
    k = len(p)
    p_cnt = Counter(p)
    w_cnt = Counter(s[:k])
    res = []
    if w_cnt == p_cnt: res.append(0)
    for i in range(k, len(s)):
        w_cnt[s[i]] += 1                        # add
        w_cnt[s[i - k]] -= 1                    # remove
        if w_cnt[s[i - k]] == 0:
            del w_cnt[s[i - k]]
        if w_cnt == p_cnt: res.append(i - k + 1)
    return res
2.5

2.5 Find Anagrams — Tối ưu so sánh O(1)

Code 2.4 vẫn đúng nhưng w_cnt == p_cnt tốn O(k) mỗi bước → O(n × k). Có thể tối ưu O(n).

Đếm số "match"

Duy trì biến matches = số ký tự có w_cnt[ch] == p_cnt[ch]. Anagram tương đương matches == số ký tự khác nhau trong p_cnt.

def findAnagrams(s, p):
    if len(s) < len(p): return []
    p_cnt = Counter(p)
    w_cnt = Counter()
    matches = 0
    needed = len(p_cnt)
    res = []
    for i, ch in enumerate(s):
        # add
        w_cnt[ch] += 1
        if w_cnt[ch] == p_cnt[ch]: matches += 1
        elif w_cnt[ch] == p_cnt[ch] + 1: matches -= 1
        # remove (khi cửa sổ vượt size)
        if i >= len(p):
            left_ch = s[i - len(p)]
            if w_cnt[left_ch] == p_cnt[left_ch]: matches -= 1
            elif w_cnt[left_ch] == p_cnt[left_ch] + 1: matches += 1
            w_cnt[left_ch] -= 1
        if matches == needed:
            res.append(i - len(p) + 1)
    return res

Time O(n), Space O(k).

2.6

2.6 Worked Example 3 — Permutation in String

Đề (LeetCode #567): Cho s1, s2. Trả True nếu s2 chứa một permutation của s1 dưới dạng substring.

Đây chính là Find Anagrams nhưng chỉ cần biết "có hay không" thay vì liệt kê.

def checkInclusion(s1, s2):
    if len(s1) > len(s2): return False
    p_cnt = Counter(s1)
    w_cnt = Counter(s2[:len(s1)])
    if w_cnt == p_cnt: return True
    for i in range(len(s1), len(s2)):
        w_cnt[s2[i]] += 1
        w_cnt[s2[i - len(s1)]] -= 1
        if w_cnt[s2[i - len(s1)]] == 0:
            del w_cnt[s2[i - len(s1)]]
        if w_cnt == p_cnt: return True
    return False
Bài học pattern reuse:
Cùng pattern, đổi điều kiện return → bài mới. Find Anagrams thu thập mọi match; Permutation in String dừng ở match đầu tiên. Đây là minh chứng giá trị của thinking-by-pattern.
2.7

2.7 Worked Example 4 — Maximum Number of Vowels in a Substring

Đề (LeetCode #1456): String s, số k. Tìm số nguyên âm nhiều nhất trong substring size k.

def maxVowels(s, k):
    vowels = set('aeiou')
    count = sum(1 for ch in s[:k] if ch 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)
    return best

Time O(n), Space O(1).

Mẫu chung của Fixed Window đếm

Nhìn lại WE1, WE4: cùng cấu trúc — state = count/sum, update bằng +/−. Đây là cấu trúc đơn giản nhất của Fixed Window và xuất hiện ở rất nhiều bài.

Quy tắc:
Khi window state là một số (sum/count), Fixed Window là O(1) update. Khi state là Counter/set, có thể vẫn O(1) amortised với mẹo "matches" như WE2.5.
2.Q

Knowledge Check — Fixed Window

Q1: Trong Fixed Window, vì sao mỗi bước update tốn O(1) thay vì O(k)?
Tinh thần "incremental update": new_sum = old_sum + nums[i] − nums[i−k]. Hai phép cộng/trừ thay vì k phép cộng. Đây là chìa khoá O(n) thay vì O(nk).
Q2: Trong Find All Anagrams (#438), so sánh hai Counter w_cnt == p_cnt mỗi bước có complexity gì?
So sánh dict yêu cầu duyệt mọi key. Khi bảng chữ cái nhỏ (26), O(k) thực tế là hằng số — vẫn ổn. Nhưng có thể tối ưu hoàn toàn O(1) bằng biến đếm matches.
Q3: Tín hiệu mạnh nhất gợi ý dùng Fixed Window là gì?
Cụm "size k", "subarray of length k", "substring of size len(p)" là dấu hiệu thẳng thừng nhất. Khi không có size cố định mà có max/min độ dài thoả điều kiện → Variable Window.

Phần 3. Variable Window

Cửa sổ co/dãn theo điều kiện — pattern then chốt cho substring/subarray bài khó
3.1

3.1 Template chuẩn — Variable Window

Mô hình tổng quát:
def variable_window(arr):
    state = init()
    left = 0
    best = init_best()
    for right in range(len(arr)):
        state = add(state, arr[right])
        # Shrink: di chuyển left cho đến khi cửa sổ "hợp lệ"
        while not is_valid(state):
            state = remove(state, arr[left])
            left += 1
        best = update_best(best, right - left + 1)
    return best

Ba điểm tuỳ biến

  1. state: cấu trúc theo dõi cửa sổ (Counter, sum, max...).
  2. is_valid: điều kiện bài toán — quyết định khi nào shrink.
  3. best: max length / min length / count.
3.2

3.2 Shrink Condition — Trái tim của Variable Window

Câu hỏi quan trọng nhất khi thiết kế Variable Window: khi nào shrink?

Bài toánShrink khiMục tiêu
Substring không lặp ký tựCó ký tự lặp trong windowMax length
Subarray có tổng ≥ sTổng ≥ s (tìm cái ngắn nhất)Min length
≤ k loại ký tự khác nhau> k loạiMax length
Min Window SubstringĐã chứa đủ patternMin length

Hai mẫu shrink phổ biến

  1. "While vi phạm, shrink để khôi phục hợp lệ" — bài tìm max length thoả điều kiện.
  2. "While đã thoả, shrink để tìm min" — bài tìm min length thoả điều kiện.
Bí quyết phỏng vấn:
Phát biểu rõ shrink condition trước khi code. "Em sẽ shrink window khi [X], cho đến khi [Y]" — đây là cách diễn đạt cho thấy người học hiểu pattern, không chỉ chạy theo template.
3.3

3.3 Worked Example 5 — Minimum Size Subarray Sum

Đề (LeetCode #209): Mảng nums dương, số target. Tìm độ dài subarray ngắn nhất có tổng ≥ target. Trả 0 nếu không có.

Pattern: "Shrink khi đã thoả"

def minSubArrayLen(target, nums):
    left = 0
    cur_sum = 0
    best = float('inf')
    for right in range(len(nums)):
        cur_sum += nums[right]
        while cur_sum >= target:                # đã thoả → cố thu nhỏ
            best = min(best, right - left + 1)
            cur_sum -= nums[left]
            left += 1
    return best if best != float('inf') else 0

Time O(n), Space O(1).

Vì sao bài này yêu cầu nums dương?

Nếu có số âm, mở rộng right không nhất thiết tăng tổng → không monoton → sliding window không áp dụng được. Bài có số âm cần Prefix Sum + Deque (Tuần 6).

3.4

3.4 Min Size Subarray — Trace tay

target = 7, nums = [2, 3, 1, 2, 4, 3]. Kỳ vọng: 2 (subarray [4,3]).

rightnums[right]cur_sumshrink?left saubest
022không0
135không0
216không0
328có (8≥7) → trừ 2 → 614
4410trừ 3 → 7, trừ 1 → 633
539trừ 2 → 7, trừ 4 → 352

Kết quả: 2.

Quan sát:
Vòng while ở bước right=4 và right=5 chạy nhiều lần — nhưng left chỉ tiến tổng cộng 5 lần qua cả thuật toán. Đây là phân tích amortised: tổng số lần shrink ≤ n.
3.5

3.5 Worked Example 6 — Longest Substring Without Repeating (Set version)

Đề (LeetCode #3): String s. Độ dài substring dài nhất không có ký tự lặp.

Variant 1 — Set + while shrink

def lengthOfLongestSubstring(s):
    seen = set()
    left = 0
    best = 0
    for right, ch in enumerate(s):
        while ch in seen:                  # shrink cho đến khi hợp lệ
            seen.remove(s[left])
            left += 1
        seen.add(ch)
        best = max(best, right - left + 1)
    return best

Time O(n), Space O(k) với k = số ký tự khác nhau.

Đây là pattern "Shrink khi vi phạm"

Cửa sổ phải không có ký tự lặp. Khi ch đã có trong set, vi phạm — shrink left cho đến khi ch không còn trong set.

3.6

3.6 Longest Substring No Repeat — Variant 2 (Last-seen index)

Variant 1 chạy đúng O(n) amortised nhưng có thể tối ưu hằng số bằng "nhảy left" thay vì shrink từng bước:

def lengthOfLongestSubstring(s):
    last_seen = {}
    left = 0
    best = 0
    for right, ch in enumerate(s):
        if ch in last_seen and last_seen[ch] >= left:
            left = last_seen[ch] + 1       # nhảy left thẳng tới sau vị trí cũ
        last_seen[ch] = right
        best = max(best, right - left + 1)
    return best

So sánh

VariantShrinkTimeĐọc dễ?
1 — Set + whileTăng left từng bướcO(n) amortisedDễ hiểu
2 — Last-seen jumpNhảy left một lầnO(n) đảm bảoKhó hiểu hơn
Khi nào dùng Variant 2?
Khi cần đảm bảo time không amortised (ví dụ trong môi trường thời gian thực hoặc có ràng buộc hằng số). Trong phỏng vấn, Variant 1 là an toàn hơn vì code rõ và tổng quát.
3.7

3.7 Worked Example 7 — Longest Substring with At Most K Distinct Characters

Đề (LeetCode #340): String s, số k. Độ dài substring dài nhất có tối đa k loại ký tự khác nhau.

Pattern: "Shrink khi vi phạm" với Counter

from collections import defaultdict
def lengthOfLongestSubstringKDistinct(s, k):
    cnt = defaultdict(int)
    left = 0
    best = 0
    for right, ch in enumerate(s):
        cnt[ch] += 1
        while len(cnt) > k:                # vi phạm
            cnt[s[left]] -= 1
            if cnt[s[left]] == 0:
                del cnt[s[left]]
            left += 1
        best = max(best, right - left + 1)
    return best

Time O(n), Space O(k).

3.8

3.8 Longest Substring K Distinct — Trace

s = "eceba", k = 2. Kỳ vọng: 3 (substring "ece").

rightchcnt sau addshrink?left saucnt sau shrinkbest
0e{e:1}không0{e:1}1
1c{e:1,c:1}không0{e:1,c:1}2
2e{e:2,c:1}không0{e:2,c:1}3
3b{e:2,c:1,b:1}có (3>2)2{e:1,b:1}3
4a{e:1,b:1,a:1}4{a:1}3

Kết quả: 3.

Quan sát kỹ thuật:
del cnt[s[left]] khi count về 0 là quan trọng — nếu không xoá, len(cnt) sẽ sai (vẫn đếm key có count = 0). Đây là bẫy phổ biến.
3.9

3.9 Worked Example 8 — Fruit Into Baskets

Đề (LeetCode #904): Mảng fruits. Hai giỏ, mỗi giỏ một loại. Bắt đầu từ một vị trí bất kỳ, hái liên tiếp. Tối đa hái được bao nhiêu trái?

Đây chính là Longest Substring with At Most K Distinct với k = 2.

def totalFruit(fruits):
    cnt = defaultdict(int)
    left = 0
    best = 0
    for right, f in enumerate(fruits):
        cnt[f] += 1
        while len(cnt) > 2:
            cnt[fruits[left]] -= 1
            if cnt[fruits[left]] == 0:
                del cnt[fruits[left]]
            left += 1
        best = max(best, right - left + 1)
    return best
Lessons learned:
Bài đề nhìn rất khác (giỏ trái cây) nhưng cấu trúc hoàn toàn giống #340. Đây là lý do tại sao học pattern thay vì học bài. Một sinh viên giỏi sẽ đọc đề Fruit và lập tức nói: "Đây là K Distinct với k=2."
3.10

3.10 Worked Example 9 — Longest Repeating Character Replacement

Đề (LeetCode #424): String s chữ hoa, số k. Có thể đổi tối đa k ký tự để được substring toàn cùng một ký tự. Độ dài lớn nhất là bao nhiêu?

Quan sát then chốt

Window hợp lệ ⟺ (window_length − max_freq_in_window) ≤ k. Tức số ký tự "khác đa số" trong window không vượt k.

from collections import defaultdict
def characterReplacement(s, k):
    cnt = defaultdict(int)
    left = 0
    max_freq = 0
    best = 0
    for right, ch in enumerate(s):
        cnt[ch] += 1
        max_freq = max(max_freq, cnt[ch])
        # vi phạm: cần đổi nhiều hơn k ký tự
        if (right - left + 1) - max_freq > k:
            cnt[s[left]] -= 1
            left += 1
        best = max(best, right - left + 1)
    return best

Time O(n), Space O(26).

3.11

3.11 Worked Example 10 — Minimum Window Substring (Hard)

Đề (LeetCode #76): String s, t. Tìm substring ngắn nhất của s chứa mọi ký tự của t (kể cả tần suất).

Ví dụ: s = "ADOBECODEBANC", t = "ABC" → "BANC".

Pattern: "Shrink khi đã thoả" + Counter + matches

from collections import Counter
def minWindow(s, t):
    if not t or len(s) < len(t): return ""
    need = Counter(t)
    have = defaultdict(int)
    needed = len(need)
    formed = 0
    left = 0
    best_len = float('inf')
    best_l, best_r = 0, 0
    for right, ch in enumerate(s):
        have[ch] += 1
        if ch in need and have[ch] == need[ch]:
            formed += 1
        while formed == needed:           # đã thoả → shrink để tìm min
            if right - left + 1 < best_len:
                best_len = right - left + 1
                best_l, best_r = left, right
            have[s[left]] -= 1
            if s[left] in need and have[s[left]] < need[s[left]]:
                formed -= 1
            left += 1
    return s[best_l:best_r+1] if best_len != float('inf') else ""
3.12

3.12 Min Window — Phân tích chi tiết

Vai trò các biến

Logic cập nhật formed

Tăng khi have[ch] == need[ch] (vừa đủ — không tăng nếu vượt). Giảm khi shrink làm have[s[left]] < need[s[left]] (vừa thiếu).

Vì sao cần biến formed mà không so sánh trực tiếp have == need?

So sánh hai dict tốn O(số ký tự khác nhau) mỗi bước → tổng O(nk). Biến formed cập nhật O(1) → tổng O(n). Giống tinh thần matches ở Find All Anagrams (2.5).

Đây là bài "boss" của tuần:
Min Window Substring được hỏi ở Google, Facebook, Amazon. Sinh viên code được bài này = đã làm chủ Variable Window.
3.13

3.13 Min Window — Trace tay

s = "ADOBECODEBANC", t = "ABC". need={A:1,B:1,C:1}, needed=3.

rightchhaveformedshrink?best
0A{A:1}1
1D{A:1,D:1}1
2O{A:1,D:1,O:1}1
3B{A:1,D:1,O:1,B:1}2
4E...,E:12
5C...,C:13shrink → "ADOBEC" (6)6
............shrink dần, formed giảm về 2...
10A...3shrink → "BANC" (4)4
11N...3shrink tiếp4
12C...3shrink hết4

Kết quả: "BANC". Đầy đủ 13 bước có trong code repository — đây chỉ là tóm tắt.

3.Q

Knowledge Check — Variable Window

Q1: Trong Min Size Subarray Sum (#209), vì sao bài yêu cầu mảng các số dương?
Sliding Window dựa trên tính monoton: thêm phần tử → tổng tăng (hoặc giảm) đều một chiều. Có số âm phá vỡ điều này. Bài có số âm phải dùng Prefix Sum + Deque (Monotonic Deque ở Tuần 6).
Q2: Bài Longest Repeating Character Replacement có công thức window hợp lệ là gì?
Số ký tự cần đổi = tổng độ dài − số ký tự đa số. Nếu ≤ k thì đổi được. Đây là quan sát then chốt — không có nó thì bài không giải được bằng sliding window.
Q3: Trong Min Window Substring (#76), vì sao biến formed tăng chỉ khi have[ch] == need[ch] (không tăng nếu vượt)?
Khi have[ch] đã = need[ch], thêm 1 nữa không làm window "thoả thêm" — vẫn cùng trạng thái thoả. Tăng formed sẽ làm sai số ký tự đã đủ. Cùng logic cho khi giảm: chỉ giảm formed khi cắt qua ngưỡng "vừa thiếu".

Phần 4. Tổng hợp và Bẫy thường gặp

Decision tree, common pitfalls, và bài về nhà
4.1

4.1 Decision Tree — Fixed vs Variable

Đặc điểmFixed WindowVariable Window
Kích thước windowCố định k (cho trước)Co/dãn theo điều kiện
Logic shrinkTự động left = right − k + 1Cần shrink condition
Mục tiêu điển hìnhMax/min/count của windowMax/min/count độ dài window
Ví dụMax Average #643, Find Anagrams #438Min Subarray #209, Min Window #76

Mẫu nhận diện trong đề

4.2

4.2 Common Pitfalls

  1. Quên xoá key khỏi Counter khi count về 0.
    cnt[s[left]] -= 1
    if cnt[s[left]] == 0: del cnt[s[left]]      # ĐỪNG QUÊN
    Nếu không xoá, len(cnt) sai → bài K Distinct sẽ TLE hoặc kết quả sai.
  2. Sai vị trí cập nhật best trong Variable Window. Cập nhật trong/sau vòng while tuỳ bài: max length thì sau (để window đã hợp lệ); min length thì trong while (để bắt được window vừa thoả).
  3. Hiểu sai amortised vs worst-case. Một số sinh viên thấy while trong for nghĩ là O(n²). Phải lập luận: "left chỉ tăng — tổng số bước while ≤ n".
  4. Áp dụng Sliding Window cho mảng có số âm. Sliding Window cần monoton. Số âm phá monoton trong nhiều bài — phải kiểm tra trước khi áp dụng.
  5. Quên trường hợp window rỗng / cửa sổ ban đầu. Min Subarray Sum: nếu không tồn tại → trả 0 chứ không phải float('inf'). Min Window Substring: trả "" nếu không tìm thấy.
5.0

Tổng kết Tuần 4

Đã học

Bài về nhà (12 bài)

  1. LeetCode #643 — Maximum Average Subarray I
  2. LeetCode #1456 — Maximum Number of Vowels in a Substring of Given Length
  3. LeetCode #438 — Find All Anagrams in a String
  4. LeetCode #567 — Permutation in String
  5. LeetCode #209 — Minimum Size Subarray Sum
  6. LeetCode #3 — Longest Substring Without Repeating Characters (cả 2 variant)
  7. LeetCode #340 — Longest Substring with At Most K Distinct Characters
  8. LeetCode #904 — Fruit Into Baskets
  9. LeetCode #424 — Longest Repeating Character Replacement
  10. LeetCode #76 — Minimum Window Substring (Hard — bài boss của tuần)
  11. LeetCode #1004 — Max Consecutive Ones III
  12. LeetCode #2962 — Count Subarrays Where Max Element Appears at Least K Times

Tuần 5 sẽ học gì?

Linked List — Fast & Slow Pointer, Reverse, Merge

Pattern Two Pointers tiếp tục được dùng — nhưng trên cấu trúc tuần tự không có index ngẫu nhiên. Học cách: phát hiện chu kỳ (Floyd's Tortoise and Hare), tìm midpoint, đảo linked list, merge sorted lists. Đây là chuẩn bị cho merge sort và k-way merge ở Tuần 9.

Hết Tuần 4 — Cảm ơn các em.

Mục lục

Nhấn T hoặc Esc để đóng