Navigation
← 10-Heap | → 12-Practice-Big-Tech-2 Giai đoạn: Big Tech Foundation | Loại: Practice Round
Buổi 11 — Practice Round 1: Big Tech Foundation
1. Mục tiêu buổi Practice
Timed Practice — Simulate Real Interview Conditions
Buổi này không dạy kiến thức mới. Mục tiêu duy nhất: luyện tập có thời gian giới hạn, giống hệt điều kiện phỏng vấn thực tế.
- Mỗi bài Warm-up: tối đa 15 phút
- Mỗi bài Core: tối đa 30 phút
- Không xem gợi ý trước khi hết thời gian
- Sau khi hết giờ: xem solution, phân tích điểm khác biệt
Mindset: Trong phỏng vấn thực, bạn sẽ bị áp lực thời gian. Buổi này rèn luyện khả năng suy nghĩ rõ ràng dưới áp lực đó.
Quy trình cho mỗi bài:
- Đọc đề — 2 phút, đọc kỹ, hỏi clarifying questions (tự hỏi)
- Brute force — nêu ra ngay, dù chưa tối ưu
- Tối ưu — tìm pattern, nêu approach trước khi code
- Code — viết sạch, có comments
- Test — chạy qua 2–3 test cases bằng tay
- Analyze — Big-O time và space
2. Warm-up Problems (15 phút mỗi bài)
Warm-up 1: Two Sum (Array + HashMap)
Đề bài: Cho mảng nums và số target. Trả về indices của 2 phần tử có tổng bằng target. Giả sử có đúng 1 đáp án.
Ví dụ:
Input: nums = [2, 7, 11, 15], target = 9
Output: [0, 1] // nums[0] + nums[1] = 2 + 7 = 9
Tư duy:
- Brute force: 2 vòng lặp lồng nhau — O(N²)
- Tối ưu: Với mỗi số
x, ta cần tìmtarget - x. Dùng HashMap lưu{value → index}, tra cứu O(1)
function twoSum(nums: number[], target: number): number[] {
// Map: value -> index
const seen = new Map<number, number>();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (seen.has(complement)) {
// Found the pair
return [seen.get(complement)!, i];
}
// Store current number for future lookups
seen.set(nums[i], i);
}
// Problem guarantees exactly one solution, so this is unreachable
return [];
}
// Test cases
console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]
console.log(twoSum([3, 2, 4], 6)); // [1, 2]
console.log(twoSum([3, 3], 6)); // [0, 1]Complexity: Time O(N) | Space O(N)
Warm-up 2: Reverse Linked List
Đề bài: Đảo ngược một singly linked list.
Ví dụ:
Input: 1 -> 2 -> 3 -> 4 -> 5 -> null
Output: 5 -> 4 -> 3 -> 2 -> 1 -> null
Tư duy: Dùng 3 con trỏ: prev, curr, next. Từng bước đảo ngược arrow.
class ListNode {
val: number;
next: ListNode | null;
constructor(val: number, next: ListNode | null = null) {
this.val = val;
this.next = next;
}
}
function reverseList(head: ListNode | null): ListNode | null {
let prev: ListNode | null = null;
let curr: ListNode | null = head;
while (curr !== null) {
const nextTemp = curr.next; // Save next before overwriting
curr.next = prev; // Reverse the arrow
prev = curr; // Move prev forward
curr = nextTemp; // Move curr forward
}
// prev is now the new head
return prev;
}
// Recursive version (bonus — good to know for interviews)
function reverseListRecursive(head: ListNode | null): ListNode | null {
// Base case: empty or single node
if (head === null || head.next === null) return head;
const newHead = reverseListRecursive(head.next);
head.next.next = head; // Node behind us points back to us
head.next = null; // We point to nothing (will be overwritten)
return newHead;
}Complexity: Time O(N) | Space O(1) iterative, O(N) recursive (call stack)
Warm-up 3: Valid Parentheses
Đề bài: Cho chuỗi s chứa các ký tự (, ), {, }, [, ]. Kiểm tra xem chuỗi có hợp lệ không (đóng mở đúng thứ tự và loại).
Ví dụ:
Input: s = "()[]{}" → Output: true
Input: s = "([)]" → Output: false
Input: s = "{[]}" → Output: true
Tư duy: Dùng Stack. Gặp mở ngoặc → push. Gặp đóng ngoặc → pop và kiểm tra khớp.
function isValid(s: string): boolean {
const stack: string[] = [];
// Map closing bracket -> expected opening bracket
const matchMap = new Map<string, string>([
[')', '('],
['}', '{'],
[']', '['],
]);
for (const char of s) {
if (!matchMap.has(char)) {
// It's an opening bracket — push to stack
stack.push(char);
} else {
// It's a closing bracket — check top of stack
const top = stack.pop();
if (top !== matchMap.get(char)) {
return false; // Mismatch
}
}
}
// Stack must be empty — all opened brackets were closed
return stack.length === 0;
}
// Test cases
console.log(isValid("()")); // true
console.log(isValid("()[]{}")); // true
console.log(isValid("(]")); // false
console.log(isValid("([)]")); // false
console.log(isValid("{[]}")); // true
console.log(isValid("")); // true (edge case: empty string)Complexity: Time O(N) | Space O(N)
3. Core Problems (30 phút mỗi bài)
Core 1: LRU Cache (HashMap + Doubly Linked List)
Đề bài: Thiết kế cấu trúc dữ liệu LRU Cache với capacity k. Implement get(key) và put(key, value). Cả hai phải chạy O(1).
Tư duy:
- Cần truy cập O(1) → HashMap
- Cần biết thứ tự “recently used” và xóa phần tử cũ nhất O(1) → Doubly Linked List
- Kết hợp: HashMap lưu
key → node, DLL duy trì thứ tự (head = most recent, tail = least recent)
class DLinkedNode {
key: number;
value: number;
prev: DLinkedNode | null = null;
next: DLinkedNode | null = null;
constructor(key: number = 0, value: number = 0) {
this.key = key;
this.value = value;
}
}
class LRUCache {
private capacity: number;
private cache: Map<number, DLinkedNode>;
// Sentinel nodes — simplify edge case handling
private head: DLinkedNode; // dummy head (most recently used side)
private tail: DLinkedNode; // dummy tail (least recently used side)
constructor(capacity: number) {
this.capacity = capacity;
this.cache = new Map();
// Initialize doubly linked list with two sentinel nodes
this.head = new DLinkedNode();
this.tail = new DLinkedNode();
this.head.next = this.tail;
this.tail.prev = this.head;
}
get(key: number): number {
if (!this.cache.has(key)) return -1;
const node = this.cache.get(key)!;
// Move to front (most recently used)
this.moveToFront(node);
return node.value;
}
put(key: number, value: number): void {
if (this.cache.has(key)) {
// Update existing node
const node = this.cache.get(key)!;
node.value = value;
this.moveToFront(node);
} else {
// Insert new node
const newNode = new DLinkedNode(key, value);
this.cache.set(key, newNode);
this.addToFront(newNode);
// Evict LRU if over capacity
if (this.cache.size > this.capacity) {
const lru = this.tail.prev!; // node just before tail sentinel
this.removeNode(lru);
this.cache.delete(lru.key);
}
}
}
private addToFront(node: DLinkedNode): void {
node.prev = this.head;
node.next = this.head.next;
this.head.next!.prev = node;
this.head.next = node;
}
private removeNode(node: DLinkedNode): void {
node.prev!.next = node.next;
node.next!.prev = node.prev;
}
private moveToFront(node: DLinkedNode): void {
this.removeNode(node);
this.addToFront(node);
}
}
// Test
const lru = new LRUCache(2);
lru.put(1, 1); // cache: {1=1}
lru.put(2, 2); // cache: {1=1, 2=2}
console.log(lru.get(1)); // 1 — cache: {2=2, 1=1} (1 moved to front)
lru.put(3, 3); // evicts key 2 — cache: {1=1, 3=3}
console.log(lru.get(2)); // -1 (not found)
lru.put(4, 4); // evicts key 1 — cache: {3=3, 4=4}
console.log(lru.get(1)); // -1
console.log(lru.get(3)); // 3
console.log(lru.get(4)); // 4Complexity: Time O(1) for both get and put | Space O(capacity)
Core 2: Binary Tree Level Order Traversal
Đề bài: Cho binary tree, trả về danh sách các levels từ trên xuống dưới, mỗi level là một mảng.
Ví dụ:
Input:
3
/ \
9 20
/ \
15 7
Output: [[3], [9, 20], [15, 7]]
Tư duy: BFS với Queue. Mỗi “round” của vòng lặp xử lý hết một level.
class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val: number, left: TreeNode | null = null, right: TreeNode | null = null) {
this.val = val;
this.left = left;
this.right = right;
}
}
function levelOrder(root: TreeNode | null): number[][] {
if (root === null) return [];
const result: number[][] = [];
const queue: TreeNode[] = [root];
let head = 0; // Index pointer (O(1) dequeue) — xem 03-Stack-and-Queue § Pitfall 2
while (head < queue.length) {
const levelSize = queue.length - head; // Snapshot: how many nodes are in this level
const currentLevel: number[] = [];
for (let i = 0; i < levelSize; i++) {
const node = queue[head++];
currentLevel.push(node.val);
// Add children for next level
if (node.left !== null) queue.push(node.left);
if (node.right !== null) queue.push(node.right);
}
result.push(currentLevel);
}
return result;
}Key Insight
Chìa khóa của BFS level-order là snapshot
levelSize = queue.lengthtrước vòng lặp inner. Điều này đảm bảo bạn chỉ xử lý các node của level hiện tại, không lẫn với level tiếp theo đã được push vào queue.
Complexity: Time O(N) | Space O(N) — queue holds at most one full level at a time (worst case: last level has N/2 nodes)
Core 3: Number of Islands
Đề bài: Cho lưới 2D gồm '1' (đất) và '0' (nước). Đếm số lượng đảo. Một đảo là tập hợp các ô '1' kết nối nhau theo 4 hướng.
Ví dụ:
Input:
11110
11010
11000
00000
Output: 1
Input:
11000
11000
00100
00011
Output: 3
Tư duy: DFS/BFS. Với mỗi ô '1' chưa thăm, đó là một đảo mới. DFS từ đó đánh dấu toàn bộ đảo.
function numIslands(grid: string[][]): number {
if (grid.length === 0) return 0;
const rows = grid.length;
const cols = grid[0].length;
let count = 0;
function dfs(r: number, c: number): void {
// Boundary check or water or already visited
if (r < 0 || r >= rows || c < 0 || c >= cols || grid[r][c] !== '1') {
return;
}
// Mark as visited by overwriting with '0'
// (Alternative: use a separate visited[][] boolean array)
grid[r][c] = '0';
// Explore all 4 directions
dfs(r + 1, c);
dfs(r - 1, c);
dfs(r, c + 1);
dfs(r, c - 1);
}
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (grid[r][c] === '1') {
count++;
dfs(r, c); // Sink the entire island
}
}
}
return count;
}
// BFS variant (iterative — avoids stack overflow on very large inputs)
function numIslandsBFS(grid: string[][]): number {
const rows = grid.length;
const cols = grid[0].length;
let count = 0;
const directions = [[1,0],[-1,0],[0,1],[0,-1]];
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (grid[r][c] === '1') {
count++;
const queue: [number, number][] = [[r, c]];
grid[r][c] = '0';
let head = 0; // Index pointer (O(1) dequeue)
while (head < queue.length) {
const [row, col] = queue[head++];
for (const [dr, dc] of directions) {
const nr = row + dr;
const nc = col + dc;
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === '1') {
grid[nr][nc] = '0';
queue.push([nr, nc]);
}
}
}
}
}
}
return count;
}Complexity: Time O(M×N) | Space O(M×N) worst case (DFS call stack or BFS queue)
4. Pattern Drill
Nhìn vào mô tả bài toán → xác định pattern cần dùng trong vòng 30 giây.
| # | Mô tả bài toán | Pattern cần dùng |
|---|---|---|
| 1 | Tìm 2 số trong mảng có tổng bằng target | HashMap / Two Pointers (if sorted) |
| 2 | Tìm subarray dài nhất không có ký tự lặp | Sliding Window |
| 3 | Kiểm tra có cycle trong linked list không | Fast/Slow Pointers (Floyd’s) |
| 4 | Trả về k phần tử lớn nhất trong mảng | Min-Heap (size k) |
| 5 | Serialize/deserialize binary tree | BFS Level Order / DFS Pre-order |
| 6 | Tìm đường ngắn nhất trong graph không trọng số | BFS |
| 7 | Implement cache với O(1) get/put, evict LRU | HashMap + Doubly Linked List |
| 8 | Tìm tất cả các hoán vị của một mảng | Backtracking |
| 9 | Kiểm tra mở ngoặc đóng ngoặc hợp lệ | Stack |
| 10 | Tính số đảo trong lưới 2D | DFS/BFS (Flood Fill) |
Cách luyện tập
Che cột “Pattern” lại. Đọc mô tả → suy nghĩ 30 giây → reveal đáp án. Lặp lại cho đến khi nhận ra ngay lập tức.
5. Self-Timer Challenge
Mock 45-Minute Interview Session
Đây là bước quan trọng nhất của buổi học này.
Setup:
- Đặt timer 45 phút
- Mở IDE sạch (không có gợi ý, autocomplete cơ bản thôi)
- Chọn 1 bài trong danh sách dưới — chọn ngẫu nhiên
- Không xem solution cho đến khi hết giờ
Danh sách bài thi thử:
- Merge Intervals — LeetCode #56
- Clone Graph — LeetCode #133
- Word Search — LeetCode #79
- Coin Change — LeetCode #322
- Validate BST — LeetCode #98
Trong 45 phút, thực hiện:
[ ] 0-2 phút: Đọc đề, hỏi clarifying questions (tự nói to ra)
[ ] 2-5 phút: Trình bày brute force approach
[ ] 5-10 phút: Tối ưu approach, phân tích Big-O
[ ] 10-40 phút: Code sạch, có comments
[ ] 40-45 phút: Test với các edge cases
Sau khi hết giờ:
- So sánh solution của mình với solution chuẩn
- Ghi chú: mình bị kẹt ở đâu? Tại sao?
- Refactor code theo chuẩn (clean code)
6. Common Mistakes Review
Mistake #1: Off-by-one errors trong Two Pointers
Hay quên kiểm tra
left < rightvsleft <= right. Trong binary search:mid = left + Math.floor((right - left) / 2)(không dùng(left + right) / 2— có thể overflow).
Mistake #2: Không xử lý null/empty input
Luôn check
if (!head),if (!root),if (nums.length === 0)trước khi làm bất cứ điều gì. Big Tech interviewer sẽ hỏi “what if input is empty/null?”
Mistake #3: Modify array/list trong khi đang iterate
Không được xóa/thêm phần tử vào array trong lúc đang loop qua nó. Nếu cần, dùng separate result array hoặc iterate ngược.
Mistake #4: Quên reset visited state trong DFS/BFS
Trong Number of Islands, nếu không đánh dấu visited (bằng cách set
'0'hoặc dùng Set), sẽ bị infinite loop hoặc đếm sai. Luôn mark visited ngay khi enqueue, không phải khi dequeue.
Mistake #5: Dùng array index thay vì Map cho character frequency
const freq = new Array(26).fill(0)chỉ hoạt động với lowercase English letters. Trong phỏng vấn thực tế, hỏi “input có Unicode không?” — nếu có, dùngMap<string, number>.
7. Progress Tracking
Đánh giá độ tự tin của mình sau khi ôn xong mỗi topic:
- ⭐ = Biết khái niệm nhưng chưa code được
- ⭐⭐ = Code được với gợi ý
- ⭐⭐⭐ = Code được trong 30 phút không cần gợi ý
- ⭐⭐⭐⭐ = Optimize được, biết edge cases
- ⭐⭐⭐⭐⭐ = Giải thích và dạy lại được
| # | Topic | Status | Confidence |
|---|---|---|---|
| 1 | Array & Two Pointers | [ ] Done | ⭐⭐⭐ |
| 2 | HashMap & HashSet | [ ] Done | ⭐⭐⭐ |
| 3 | Stack & Queue | [ ] Done | ⭐⭐⭐ |
| 4 | Linked List | [ ] Done | ⭐⭐⭐ |
| 5 | Binary Tree & BST | [ ] Done | ⭐⭐⭐ |
| 6 | BFS & DFS | [ ] Done | ⭐⭐⭐ |
| 7 | Recursion & Backtracking | [ ] Done | ⭐⭐⭐ |
| 8 | Dynamic Programming (Intro) | [ ] Done | ⭐⭐⭐ |
| 9 | Sorting & Binary Search | [ ] Done | ⭐⭐⭐ |
| 10 | Heap | [ ] Done | ⭐⭐⭐ |
Tổng điểm tự đánh giá:
- 40–50 điểm: Sẵn sàng tiến sang FAANG Mastery
- 30–39 điểm: Ôn lại 2–3 topic yếu trước
- Dưới 30: Ôn lại từ đầu, tập trung vào practice
Xem tiếp: 12-Practice-Big-Tech-2 — Mock Interview Final