Algorithms: Interview Pattern Recognition Guide
1. Pattern Recognition Cheat Sheet
Khi thấy... → nghĩ đến...
| Dấu hiệu trong đề | Pattern |
|---|---|
| Subarray/substring liên tiếp, max/min/longest | Sliding Window |
| Array đã sort, tìm cặp/bộ ba thỏa điều kiện | Two Pointers |
| Array đã sort, tìm giá trị / vị trí | Binary Search |
| "Minimum feasible X" hoặc "Maximum possible X" | Binary Search on answer space |
| "Bao nhiêu cách", "tồn tại không", "tối ưu" | Dynamic Programming |
| Subproblems overlap, bài con nhỏ hơn | DP + Memoization |
| Dependency ordering, prerequisites | Topological Sort |
| Connected components, same group? | Union-Find |
| Shortest path, unweighted | BFS |
| Shortest path, weighted non-negative | Dijkstra |
| Tìm kết quả tức thời từ stream (running max, min, median) | Heap / Monotonic Stack |
| Tìm "next greater element" | Monotonic Stack |
| Prefix sum queries | Prefix Sum Array |
| Range sum/min/max queries | Segment Tree / Fenwick Tree |
2. Complexity Cheat Sheet
Data Structure Operations:
Array access: O(1)
Array search: O(n)
Hash map get/set: O(1) average, O(n) worst
Binary search tree: O(log n) balanced, O(n) worst
Heap push/pop: O(log n)
Heap peek: O(1)
Sorting:
QuickSort: O(n log n) avg, O(n²) worst
MergeSort: O(n log n) always, O(n) space
HeapSort: O(n log n) always, O(1) space
Counting/Radix: O(n) for small range
Graph:
BFS/DFS: O(V + E)
Dijkstra: O((V+E) log V)
Bellman-Ford: O(VE)
Floyd-Warshall: O(V³)
Topological Sort: O(V + E)
Union-Find: O(α(n)) ≈ O(1)
3. Approach Framework cho interview
RRTED — 5 bước:
1. Repeat — Nhắc lại bài, confirm constraints
2. Refine — Hỏi edge cases, input size, negative numbers?
3. Think — Nói ra brute force trước, rồi optimize
4. Execute — Code với variable names rõ ràng
5. Debug — Trace qua example nhỏ
Câu hỏi nên hỏi khi nhận đề
- Input size n? (ảnh hưởng O(n²) vs O(n log n))
- Có thể có negative numbers không?
- Array đã sorted chưa?
- Có duplicate không?
- Return value khi không tìm thấy? (-1, [], error?)
- Modify in-place hay return mới?
4. Nhận diện nhanh theo time complexity target
Nếu n ≤ 20: O(2ⁿ) chấp nhận được → backtracking, bitmask DP
Nếu n ≤ 500: O(n²) OK → DP 2D, brute force pairs
Nếu n ≤ 10⁵: O(n log n) cần → sorting, binary search, heap
Nếu n ≤ 10⁶: O(n) cần → sliding window, two pointers, hash map
Nếu n ≤ 10⁹: O(log n) cần → binary search, math
5. Common Patterns với Example
Monotonic Stack — Next Greater Element
nums = [2, 1, 2, 4, 3]
result = [4, 2, 4, -1, -1]
Stack duy trì các element chưa tìm được "next greater"
Khi gặp nums[i] > stack.top() → stack.top() tìm thấy answer
func nextGreaterElement(nums []int) []int {
n := len(nums)
result := make([]int, n)
for i := range result { result[i] = -1 }
stack := []int{} // lưu index
for i := 0; i < n; i++ {
for len(stack) > 0 && nums[i] > nums[stack[len(stack)-1]] {
idx := stack[len(stack)-1]
stack = stack[:len(stack)-1]
result[idx] = nums[i]
}
stack = append(stack, i)
}
return result
}
Prefix Sum — Range Query
nums = [1, 2, 3, 4, 5]
prefix = [0, 1, 3, 6, 10, 15]
sum(l, r) = prefix[r+1] - prefix[l] // O(1) query
func buildPrefix(nums []int) []int {
prefix := make([]int, len(nums)+1)
for i, v := range nums {
prefix[i+1] = prefix[i] + v
}
return prefix
}
func rangeSum(prefix []int, l, r int) int {
return prefix[r+1] - prefix[l]
}
Backtracking Template
func backtrack(path []int, choices []int, result *[][]int) {
if isSolution(path) {
*result = append(*result, append([]int{}, path...))
return
}
for _, choice := range choices {
if isValid(choice, path) {
path = append(path, choice) // choose
backtrack(path, nextChoices, result) // explore
path = path[:len(path)-1] // unchoose
}
}
}
6. Các lỗi phổ biến nhất
| Lỗi | Tần suất | Fix |
|---|---|---|
| Integer overflow khi tính mid | Rất cao | left + (right-left)/2 |
| Off-by-one trong binary search | Cao | Dùng template cố định |
| Không xử lý empty/nil input | Cao | Check trước khi dùng |
| Modify slice đang iterate | Trung bình | Copy trước hoặc iterate index |
| Forget to mark visited (graph) | Cao | Luôn mark ngay khi thêm vào queue |
| Stack overflow trong DFS sâu | Thấp | Dùng iterative DFS |
| Quên base case trong DP | Cao | Viết rõ base case đầu tiên |
| Trả về reference thay vì copy | Trung bình | append([]int{}, path...) |
7. Template "Big 5" — Học thuộc lòng
// 1. Binary Search (Lower Bound)
left, right := 0, n
for left < right {
mid := left + (right-left)/2
if condition(mid) { right = mid } else { left = mid + 1 }
}
return left
// 2. Sliding Window (Variable)
left := 0
for right := 0; right < n; right++ {
add(arr[right])
for invalid() { remove(arr[left]); left++ }
updateAnswer(right - left + 1)
}
// 3. BFS
queue := []int{start}; visited[start] = true
for len(queue) > 0 {
node := queue[0]; queue = queue[1:]
for _, nb := range graph[node] {
if !visited[nb] { visited[nb] = true; queue = append(queue, nb) }
}
}
// 4. DFS (recursive)
func dfs(node int) {
visited[node] = true
for _, nb := range graph[node] { if !visited[nb] { dfs(nb) } }
}
// 5. Union-Find
func find(x int) int {
if parent[x] != x { parent[x] = find(parent[x]) }
return parent[x]
}
func union(x, y int) { parent[find(x)] = find(y) }
💡 Interview: Nói ra pattern trước khi code. "Tôi thấy bài này yêu cầu tìm subarray liên tiếp tối ưu → sliding window. Complexity sẽ là O(n)." Interviewer evaluate tư duy nhiều hơn code syntax.