Buổi 12 — Practice Round 1: FAANG Mastery

Navigation

11-Segment-Tree | → 13-Practice-FAANG-2 Giai đoạn: FAANG Mastery | Loại: Mock Interview


1. Mục tiêu

Buổi này simulate điều kiện Google/Meta on-site interview thực tế. Mục tiêu không phải là giải đúng 100%, mà là luyện tư duy có cấu trúc dưới áp lực thời gian.

Mindset trước khi bắt đầu

Interviewer không chỉ chấm “đúng hay sai” — họ đánh giá cách bạn suy nghĩ, khả năng giao tiếp technical, và xử lý edge cases. Một giải pháp O(N²) được giải thích rõ ràng còn tốt hơn O(N) viết im lặng không giải thích.

Điều kiện buổi luyện:

  • Thời gian: 45 phút / problem set
  • Không tra cứu solution trong quá trình làm
  • Nói to suy nghĩ theo framework UMPIRE
  • Sau khi hết giờ: phân tích lại approach, so sánh với optimal

2. Interview Simulation Rules

Framework UMPIRE — Phải nói to từng bước

BướcViết tắtHành động
1U — UnderstandĐọc đề, hỏi clarifying questions, vẽ ví dụ cụ thể
2M — MatchNhận diện pattern: “Bài này giống dạng Sliding Window vì…“
3P — PlanPseudocode bằng lời, nêu Time/Space complexity dự kiến
4I — ImplementViết code sạch, đặt tên biến có nghĩa
5R — ReviewChạy tay qua test case, kiểm tra edge cases
6E — EvaluatePhân tích complexity, đề xuất optimizations

Quy tắc bắt buộc

  • KHÔNG bắt đầu code trước bước Plan
  • PHẢI nêu ít nhất 2 clarifying questions trước khi code
  • PHẢI phân tích complexity sau khi code xong
  • Nếu bí, NÊU RÕ “Tôi đang nghĩ hướng X, nhưng chưa chắc vì Y”

Quy tắc chấm điểm nhanh (tự chấm)

  • Hoàn thành trong 45 phút + optimal: 5 điểm
  • Hoàn thành trong 45 phút + suboptimal: 4 điểm
  • Hoàn thành nhưng overtime: 3 điểm
  • Partial solution + giải thích rõ approach: 2 điểm
  • Stuck hoàn toàn: 1 điểm → cần review lại fundamentals

3. Problem Set A — Two Pointers & Sliding Window Focus

Problem A1: #3 Longest Substring Without Repeating Characters (Medium)

Đề bài: Cho string s, tìm độ dài của substring dài nhất không có ký tự lặp lại.

Ví dụ:

Input: s = "abcabcbb"  →  Output: 3  (substring "abc")
Input: s = "bbbbb"     →  Output: 1  (substring "b")
Input: s = "pwwkew"    →  Output: 3  (substring "wke")

Clarifying questions cần hỏi:

  • Input có thể là empty string không? → Trả về 0
  • Có uppercase/lowercase/số/ký tự đặc biệt không? → Chỉ có ASCII printable characters
  • Cần trả về substring hay chỉ độ dài? → Chỉ độ dài

Approach 1: Brute Force — O(N²) / O(min(N, M))

// Approach 1: Brute Force — kiểm tra từng substring
// Time: O(N²) | Space: O(min(N, M)) với M là kích thước alphabet
function lengthOfLongestSubstring_Brute(s: string): number {
  let maxLen = 0;
 
  for (let i = 0; i < s.length; i++) {
    const seen = new Set<string>();
    let j = i;
 
    // Mở rộng substring từ i cho đến khi gặp ký tự trùng
    while (j < s.length && !seen.has(s[j])) {
      seen.add(s[j]);
      j++;
    }
 
    maxLen = Math.max(maxLen, j - i);
  }
 
  return maxLen;
}

Approach 2: Sliding Window với HashMap — O(N) / O(min(N, M))

// Approach 2 (Optimal): Sliding Window + HashMap lưu index gần nhất
// Key insight: khi gặp ký tự trùng tại index j,
//   thay vì dịch left từng bước → nhảy thẳng đến (lastSeen[s[j]] + 1)
// Time: O(N) — mỗi ký tự xử lý tối đa 2 lần (add + remove)
// Space: O(min(N, M)) — HashMap chứa tối đa min(N, kích thước alphabet) entries
function lengthOfLongestSubstring(s: string): number {
  // Map từ character → index gần nhất của nó
  const lastSeen = new Map<string, number>();
  let maxLen = 0;
  let left = 0; // con trỏ trái của sliding window
 
  for (let right = 0; right < s.length; right++) {
    const char = s[right];
 
    // Nếu char đã xuất hiện VÀ index của nó nằm trong window hiện tại
    // → phải dịch left qua để loại bỏ ký tự trùng
    if (lastSeen.has(char) && lastSeen.get(char)! >= left) {
      // Nhảy left đến ngay sau vị trí xuất hiện trước đó của char
      left = lastSeen.get(char)! + 1;
    }
 
    // Cập nhật vị trí gần nhất của char
    lastSeen.set(char, right);
 
    // Cập nhật max length: window hiện tại có kích thước (right - left + 1)
    maxLen = Math.max(maxLen, right - left + 1);
  }
 
  return maxLen;
}
 
// Test cases
console.log(lengthOfLongestSubstring("abcabcbb")); // 3
console.log(lengthOfLongestSubstring("bbbbb"));     // 1
console.log(lengthOfLongestSubstring("pwwkew"));    // 3
console.log(lengthOfLongestSubstring(""));          // 0
console.log(lengthOfLongestSubstring(" "));         // 1
console.log(lengthOfLongestSubstring("au"));        // 2

Pattern nhận dạng

Bất kỳ bài nào hỏi “tìm subarray/substring thoả điều kiện X” + muốn tối ưu thì nên nghĩ đến Sliding Window. Key: định nghĩa rõ khi nào window hợp lệ và khi nào cần thu nhỏ window.


Problem A2: #76 Minimum Window Substring (Hard)

Đề bài: Cho string s và string t, tìm substring ngắn nhất trong s chứa tất cả ký tự của t (kể cả duplicates).

Ví dụ:

Input: s = "ADOBECODEBANC", t = "ABC"  →  Output: "BANC"
Input: s = "a", t = "a"               →  Output: "a"
Input: s = "a", t = "aa"              →  Output: ""  (không tìm được)

Clarifying questions:

  • Nếu không tìm được → trả về "" (empty string)
  • st có phân biệt hoa thường không? → Có
  • t có ký tự trùng không? → Có, phải bao gồm đủ số lần xuất hiện
// Minimum Window Substring — Sliding Window với frequency counting
// Time: O(|s| + |t|) — mỗi ký tự trong s được duyệt tối đa 2 lần
// Space: O(|s| + |t|) — lưu frequency maps
 
function minWindow(s: string, t: string): string {
  if (s.length === 0 || t.length === 0) return "";
 
  // Bước 1: Đếm frequency của từng ký tự trong t
  const need = new Map<string, number>();
  for (const char of t) {
    need.set(char, (need.get(char) ?? 0) + 1);
  }
 
  // Số lượng ký tự PHÂN BIỆT cần thoả mãn
  const required = need.size;
 
  // Sliding window pointers
  let left = 0;
  let right = 0;
 
  // `formed`: số ký tự phân biệt đã thoả mãn đủ frequency trong window
  let formed = 0;
 
  // Frequency của ký tự trong window hiện tại
  const windowCounts = new Map<string, number>();
 
  // Kết quả: [window length, left index, right index]
  let result: [number, number, number] = [-1, 0, 0];
 
  while (right < s.length) {
    // Thêm ký tự s[right] vào window
    const char = s[right];
    windowCounts.set(char, (windowCounts.get(char) ?? 0) + 1);
 
    // Kiểm tra xem ký tự này đã thoả mãn yêu cầu của t chưa
    // (frequency trong window >= frequency cần thiết)
    if (
      need.has(char) &&
      windowCounts.get(char) === need.get(char)
    ) {
      formed++;
    }
 
    // Co window lại từ trái khi đã có đủ tất cả ký tự cần thiết
    while (left <= right && formed === required) {
      // Cập nhật kết quả nếu window hiện tại nhỏ hơn
      const windowSize = right - left + 1;
      if (result[0] === -1 || windowSize < result[0]) {
        result = [windowSize, left, right];
      }
 
      // Loại bỏ ký tự s[left] khỏi window
      const leftChar = s[left];
      windowCounts.set(leftChar, windowCounts.get(leftChar)! - 1);
 
      // Nếu sau khi loại bỏ, ký tự này không còn đủ frequency
      if (
        need.has(leftChar) &&
        windowCounts.get(leftChar)! < need.get(leftChar)!
      ) {
        formed--;
      }
 
      left++; // Thu hẹp window từ trái
    }
 
    right++; // Mở rộng window từ phải
  }
 
  // Trả về substring, hoặc "" nếu không tìm được
  return result[0] === -1
    ? ""
    : s.substring(result[1], result[1] + result[0]);
}
 
// Test cases
console.log(minWindow("ADOBECODEBANC", "ABC")); // "BANC"
console.log(minWindow("a", "a"));               // "a"
console.log(minWindow("a", "aa"));              // ""
console.log(minWindow("aa", "aa"));             // "aa"

Template Sliding Window tổng quát

left = 0, right = 0
while right < n:
    add s[right] to window
    while window_is_valid:          ← hoặc window_is_invalid tùy bài
        update answer
        remove s[left] from window
        left++
    right++

Bài tìm minimum: thu nhỏ khi hợp lệ Bài tìm maximum: thu nhỏ khi không hợp lệ


4. Problem Set B — Graph & DP Focus

Problem B1: #207 Course Schedule (Medium)

Đề bài:numCourses khoá học (đánh số 0 đến numCourses-1). prerequisites[i] = [a, b] nghĩa là phải học b trước a. Hỏi có thể hoàn thành tất cả khoá học không?

Ví dụ:

Input: numCourses = 2, prerequisites = [[1,0]]   → Output: true
Input: numCourses = 2, prerequisites = [[1,0],[0,1]] → Output: false  (cycle!)

Key insight: Bài này tương đương hỏi “Directed graph có cycle không?”

Approach 1: DFS Cycle Detection

// Approach 1: DFS với 3 trạng thái (WHITE/GRAY/BLACK)
// WHITE (0) = chưa thăm, GRAY (1) = đang trong DFS stack, BLACK (2) = đã xong
// Time: O(V + E) | Space: O(V + E)
 
function canFinish_DFS(numCourses: number, prerequisites: number[][]): boolean {
  // Xây dựng adjacency list
  const graph: number[][] = Array.from({ length: numCourses }, () => []);
  for (const [course, prereq] of prerequisites) {
    graph[prereq].push(course); // prereq → course
  }
 
  // 0 = unvisited (WHITE), 1 = visiting (GRAY), 2 = visited (BLACK)
  const state = new Array(numCourses).fill(0);
 
  // DFS trả về true nếu phát hiện cycle
  function hasCycle(node: number): boolean {
    if (state[node] === 1) return true;  // GRAY → back edge → cycle!
    if (state[node] === 2) return false; // BLACK → đã xử lý, không có cycle
 
    state[node] = 1; // Đánh dấu đang thăm (GRAY)
 
    for (const neighbor of graph[node]) {
      if (hasCycle(neighbor)) return true;
    }
 
    state[node] = 2; // Đánh dấu hoàn thành (BLACK)
    return false;
  }
 
  // Chạy DFS từ mỗi node chưa thăm
  for (let i = 0; i < numCourses; i++) {
    if (state[i] === 0 && hasCycle(i)) return false;
  }
 
  return true; // Không có cycle → có thể hoàn thành
}

Approach 2: Kahn’s Algorithm (BFS Topological Sort)

// Approach 2: Kahn's Algorithm — BFS topo sort
// Ý tưởng: node có in-degree = 0 là node có thể học đầu tiên
// Nếu sau khi xử lý hết, vẫn còn node chưa xử lý → có cycle
// Time: O(V + E) | Space: O(V + E)
 
function canFinish_Kahn(numCourses: number, prerequisites: number[][]): boolean {
  // Xây dựng adjacency list và in-degree array
  const graph: number[][] = Array.from({ length: numCourses }, () => []);
  const inDegree = new Array(numCourses).fill(0);
 
  for (const [course, prereq] of prerequisites) {
    graph[prereq].push(course);
    inDegree[course]++; // course cần 1 prerequisite
  }
 
  // Queue chứa các node có in-degree = 0 (có thể học ngay)
  const queue: number[] = [];
  for (let i = 0; i < numCourses; i++) {
    if (inDegree[i] === 0) queue.push(i);
  }
 
  let processedCount = 0;
  let head = 0; // Index pointer — O(1) dequeue (xem 03-Stack-and-Queue § Pitfall 2)
 
  while (head < queue.length) {
    const course = queue[head++];
    processedCount++;
 
    // Giảm in-degree của tất cả khoá học phụ thuộc vào course này
    for (const nextCourse of graph[course]) {
      inDegree[nextCourse]--;
      // Nếu in-degree về 0, có thể học khoá này rồi
      if (inDegree[nextCourse] === 0) {
        queue.push(nextCourse);
      }
    }
  }
 
  // Nếu xử lý đủ tất cả courses → không có cycle
  return processedCount === numCourses;
}
 
// Test
console.log(canFinish_DFS(2, [[1,0]]));        // true
console.log(canFinish_DFS(2, [[1,0],[0,1]]));  // false
console.log(canFinish_Kahn(2, [[1,0]]));       // true
console.log(canFinish_Kahn(2, [[1,0],[0,1]])); // false

So sánh 2 approaches

DFSKahn’s BFS
Phát hiện cycleQua GRAY nodeprocessedCount < numCourses
Topo order outputCó (DFS finish order)Có (queue order)
Dễ implementTrung bìnhDễ hơn
Interview preferenceThường hỏi cả 2Intuitive hơn

Problem B2: #416 Partition Equal Subset Sum (Medium)

Đề bài: Cho array số nguyên dương nums. Hỏi có thể chia array thành 2 subset có tổng bằng nhau không?

Ví dụ:

Input: nums = [1,5,11,5]  →  Output: true   ([1,5,5] và [11])
Input: nums = [1,2,3,5]   →  Output: false  (không chia được)

Key insight: Tổng array = S → tìm subset có tổng = S/2. Đây là bài 0/1 Knapsack.

// Partition Equal Subset Sum — 0/1 Knapsack DP
// Time: O(N × target) | Space: O(target) với space optimization
// target = totalSum / 2
 
function canPartition(nums: number[]): boolean {
  const totalSum = nums.reduce((a, b) => a + b, 0);
 
  // Nếu tổng lẻ → không thể chia đôi bằng nhau
  if (totalSum % 2 !== 0) return false;
 
  const target = totalSum / 2;
 
  // dp[j] = true nếu có thể tạo sum = j từ các phần tử đã xét
  // Khởi tạo: dp[0] = true (sum = 0 luôn đạt được với tập rỗng)
  const dp = new Array(target + 1).fill(false);
  dp[0] = true;
 
  for (const num of nums) {
    // Duyệt ngược để tránh dùng cùng phần tử 2 lần (0/1 knapsack)
    // Nếu duyệt xuôi → unbounded knapsack (dùng nhiều lần được)
    for (let j = target; j >= num; j--) {
      // dp[j] = có thể đạt sum j KHÔNG dùng num
      //       OR có thể đạt sum (j - num) TRƯỚC ĐÓ và thêm num vào
      dp[j] = dp[j] || dp[j - num];
    }
 
    // Optimization: early exit nếu đã tìm được
    if (dp[target]) return true;
  }
 
  return dp[target];
}
 
// Trace through example: nums = [1, 5, 11, 5], target = 11
// Initial:  dp = [T, F, F, F, F, F, F, F, F, F, F, F]
// After 1:  dp = [T, T, F, F, F, F, F, F, F, F, F, F]
// After 5:  dp = [T, T, F, F, F, T, T, F, F, F, F, F]
// After 11: dp = [T, T, F, F, F, T, T, F, F, F, F, T] ← dp[11] = true!
 
console.log(canPartition([1, 5, 11, 5])); // true
console.log(canPartition([1, 2, 3, 5]));  // false
console.log(canPartition([1, 1]));        // true
console.log(canPartition([1, 2, 5]));     // false

Mẹo nhớ 0/1 vs Unbounded Knapsack

  • Duyệt ngược (j từ target → num): mỗi item dùng tối đa 1 lần (0/1 Knapsack)
  • Duyệt xuôi (j từ num → target): item có thể dùng nhiều lần (Unbounded Knapsack)

5. Problem Set C — Hard Challenge

Problem C1: #124 Binary Tree Maximum Path Sum (Hard)

Đề bài: Cho binary tree, tìm path (đường đi qua bất kỳ node nào, không cần qua root) có tổng lớn nhất. Path phải là một đường liên tục.

Ví dụ:

     -10
     /  \
    9   20
       /  \
      15   7
Output: 42  (path: 15 → 20 → 7)

    1
   / \
  2   3
Output: 6  (path: 2 → 1 → 3)
// Binary Tree Maximum Path Sum — Recursive với Global Max
// Key insight: với mỗi node, path đi QUA node đó có thể dùng:
//   - Chỉ node đó
//   - Node + left subtree path
//   - Node + right subtree path
//   - Node + cả left + right (nhưng chỉ tính được khi node là "đỉnh" của path)
// Hàm helper trả về "max gain" khi đi QUA node theo 1 hướng (để parent dùng)
// Time: O(N) — duyệt mỗi node đúng 1 lần
// Space: O(H) — call stack depth = chiều cao cây
 
class TreeNode {
  val: number;
  left: TreeNode | null;
  right: TreeNode | null;
  constructor(val = 0, left: TreeNode | null = null, right: TreeNode | null = null) {
    this.val = val;
    this.left = left;
    this.right = right;
  }
}
 
function maxPathSum(root: TreeNode | null): number {
  // globalMax lưu kết quả tốt nhất tìm được trong toàn bộ quá trình DFS
  let globalMax = -Infinity;
 
  // Hàm này trả về: max gain khi dùng node này và ĐI MỘT HƯỚNG (để parent sử dụng)
  // Không thể đi cả 2 hướng vì parent cần nối thêm → sẽ tạo nhánh rẽ
  function maxGain(node: TreeNode | null): number {
    if (node === null) return 0;
 
    // Tính max gain từ subtree trái và phải
    // Dùng Math.max(0, ...) để loại bỏ nhánh âm (không dùng còn hơn dùng âm)
    const leftGain = Math.max(0, maxGain(node.left));
    const rightGain = Math.max(0, maxGain(node.right));
 
    // Tổng path đi QUA node này (có thể dùng cả 2 nhánh — node là "đỉnh" của path)
    const pathThroughNode = node.val + leftGain + rightGain;
 
    // Cập nhật global max
    globalMax = Math.max(globalMax, pathThroughNode);
 
    // Trả về max gain KHI ĐI 1 HƯỚNG (để parent node có thể nối tiếp)
    return node.val + Math.max(leftGain, rightGain);
  }
 
  maxGain(root);
  return globalMax;
}
 
// Test
//     -10
//     /  \
//    9   20
//       /  \
//      15   7
const root1 = new TreeNode(-10,
  new TreeNode(9),
  new TreeNode(20, new TreeNode(15), new TreeNode(7))
);
console.log(maxPathSum(root1)); // 42
 
//   1
//  / \
// 2   3
const root2 = new TreeNode(1, new TreeNode(2), new TreeNode(3));
console.log(maxPathSum(root2)); // 6
 
// Edge case: single negative node
console.log(maxPathSum(new TreeNode(-3))); // -3

Edge case quan trọng

  • Node có giá trị âm: dùng Math.max(0, gain) để bỏ qua nhánh âm
  • Tất cả nodes đều âm: phải chọn ít nhất 1 node → globalMax = -Infinity ban đầu
  • Single node tree: trả về giá trị của node đó (dù âm)

6. Pattern Drill Table

Luyện nhận diện pattern trước khi code. Nhìn vào mô tả → đoán pattern ngay trong 10 giây.

#Mô tả bài toánPattern
1Tìm subarray dài nhất có sum ≤ KSliding Window
2Đếm số distinct elements trong mỗi window kích thước KSliding Window + HashMap
3Số operations tối thiểu để kết nối tất cả nodesUnion Find / MST (Kruskal/Prim)
4Tìm K phần tử lớn nhất trong stream dữ liệuHeap (Min-Heap kích thước K)
5Đường đi ngắn nhất giữa 2 nodes trong unweighted graphBFS
6Có tồn tại path thoả điều kiện trong cây khôngDFS / Recursion
7Số cách để đạt tổng = X, dùng mỗi phần tử tối đa 1 lần0/1 Knapsack DP
8Tìm tất cả subsets/permutations/combinationsBacktracking
9Two strings: edit distance / longest common subsequence2D DP
10Mảng đã sort, tìm cặp có tổng = targetTwo Pointers
11Interval scheduling: chọn nhiều nhất không overlapGreedy (sort by end time)
12Chuỗi có dấu ngoặc hợp lệ không? / tính toán expressionStack
13Tìm ancestor chung gần nhất của 2 nodes trong BSTBST property / DFS
14Số islands / connected components trong gridBFS/DFS hoặc Union Find
15Minimum cost để đi từ góc trên-trái → góc dưới-phảiDynamic Programming (Grid DP)

Pattern Recognition Speed Drill

Che cột Pattern, nhìn từng mô tả và nói to pattern trong 5 giây. Lặp lại cho đến khi tất cả 15 bài nhận ra trong < 30 giây tổng cộng.


7. Interview Self-Assessment Rubric

Tự chấm sau mỗi buổi mock interview. Mục tiêu: tổng điểm ≥ 20/25 trước khi apply.

Dimension1 — Struggling3 — On Track5 — FAANG Ready
Problem ComprehensionHiểu sai đề, bỏ qua constraints quan trọngHiểu đúng sau khi hỏi clarifying questionsNgay lập tức nắm constraints, tự vẽ ví dụ edge cases
Pattern RecognitionKhông biết approach sau 10 phútNhận ra pattern sau 3-5 phút với gợi ýNhận ra pattern trong 1-2 phút, biết nhiều approaches
Code QualityCode sai syntax, biến đặt tên a/b/c, nhiều bugCode chạy được, tên biến tạm ổn, vài minor bugsClean code, tên biến tự mô tả, không có bug, có comments
Complexity AnalysisKhông biết Big-O của solution mình viếtPhân tích được Time/Space sau khi được nhắcChủ động nêu Time/Space cho từng approach, giải thích tại sao
CommunicationIm lặng code, không giải thích, hỏi mới trả lờiGiải thích được khi được hỏi, dùng UMPIRE đôi lúcLiên tục nói to suy nghĩ, proactively giải thích trade-offs

Cách tính điểm tổng:

  • 20-25: FAANG Ready — nộp CV ngay
  • 15-19: On Track — luyện thêm 2-3 tuần
  • 10-14: Getting There — cần review lại weak areas
  • < 10: Need More Practice — quay lại buổi học cơ bản

Track progress theo thời gian

Ghi điểm sau mỗi mock session: [Date]: P=X, PR=X, CQ=X, CA=X, C=X → Total=XX


8. Common FAANG Interview Mistakes

Mistake #1: Bắt đầu code ngay khi đọc xong đề

Vấn đề: Mất 20 phút code sai hướng vì hiểu sai constraint. Fix: Luôn dành 3-5 phút đầu để hỏi clarifying questions và vẽ ví dụ cụ thể. Hỏi: “Có thể có negative numbers không?”, “Input có thể empty không?”, “Cần optimize cho Time hay Space?”

Mistake #2: Không confirm approach trước khi code

Vấn đề: Code xong 30 phút, interviewer nói “Có approach tốt hơn không?” — không còn thời gian sửa. Fix: Sau khi plan xong, nói to approach và complexity rồi hỏi “Approach này có ổn không, hay anh/chị muốn tôi tối ưu hơn?” trước khi gõ code.

Mistake #3: Bỏ qua edge cases

Vấn đề: Code pass basic test nhưng fail trên null, empty array, single element, negative numbers, overflow. Fix: Sau khi code xong, tự hỏi 5 câu: (1) Input rỗng? (2) Một phần tử? (3) Tất cả phần tử giống nhau? (4) Giá trị âm? (5) Overflow/underflow?

Mistake #4: Không chủ động phân tích complexity

Vấn đề: Interviewer phải hỏi “Time complexity của bạn là gì?” — đây là dấu hiệu bạn không proactive. Fix: Sau khi code xong (không cần đợi hỏi), tự nêu: “Solution này có Time complexity O(N log N) vì… và Space O(N) vì… Có thể optimize xuống O(N) nếu…”

Mistake #5: Tên biến bí ẩn dưới áp lực

Vấn đề: Khi nervous, code ra a, b, c, x, y, tmp — interviewer không follow được logic. Fix: Quy tắc bất di bất dịch: left/right cho two pointers, windowStart/windowEnd cho sliding window, visited/parent/distance cho graph, dp[i] với comment giải thích ý nghĩa. Tên biến tự mô tả = code sạch = điểm cao.


9. Buổi tiếp theo

Navigation

Tiếp theo: 13-Practice-FAANG-2 — Final Mock: FAANG Mastery Complete Buổi cuối cùng: 3 round interview simulation với Hard problems, FAANG readiness checklist đầy đủ, và định hướng sau vault.

Chuẩn bị cho buổi 13:

  • Ôn lại Heap (cho #295 Find Median from Data Stream)
  • Ôn lại Linked List merge (cho #23 Merge K Sorted Lists)
  • Ôn lại BFS/DFS trên Binary Tree (cho #297 Serialize/Deserialize)
  • Self-assess điểm rubric của buổi này trước khi sang buổi 13