Big O Notation & Phân Tích Độ Phức Tạp: Hướng Dẫn Toàn Diện

Giới Thiệu
Tưởng tượng bạn đang ở thư viện với 1 triệu cuốn sách. Mất bao lâu để tìm một cuốn sách cụ thể?
- Thư viện không sắp xếp: Có thể phải kiểm tra mọi cuốn → chậm
- Sắp xếp theo tác giả: Dùng binary search → nhanh
- Hệ thống số hóa: Tra cứu tức thì → instant
Đây chính là Big O notation: đo lường cách thuật toán scale khi input size tăng. Đây là ngôn ngữ developers dùng để thảo luận về hiệu suất thuật toán, và nó cần thiết cho việc viết code nhanh, scalable và ace technical interviews.
Những Gì Bạn Sẽ Học
✅ Hiểu Big O notation và tại sao nó quan trọng
✅ Thành thạo common time complexities: O(1), O(log n), O(n), O(n log n), O(n²), O(2ⁿ)
✅ Tính Big O cho loops, recursion, và nested structures
✅ Phân tích space complexity và memory usage
✅ Hiểu best, average, và worst-case scenarios
✅ Học time vs space trade-offs
✅ Áp dụng complexity analysis trong TypeScript và Python
✅ Thành thạo interview techniques cho việc thảo luận algorithm efficiency
1. Big O Notation Là Gì?
Big O notation mô tả cách runtime hoặc space requirements của thuật toán tăng khi input size tăng. Nó trả lời: "Thuật toán này scale như thế nào?"
Khái Niệm Chính
Input Size (n): Kích thước data bạn đang làm việc với
- Array 10 items → n = 10
- Array 1 triệu items → n = 1,000,000
Big O (O): Mô tả worst-case upper bound của growth
- O(n) có nghĩa "at most linear growth"
- O(1) có nghĩa "constant, không tăng"
Đọc Big O Notation
O(1) → "O of 1" hoặc "constant time"
O(n) → "O of n" hoặc "linear time"
O(log n) → "O of log n" hoặc "logarithmic time"
O(n²) → "O of n squared" hoặc "quadratic time"
O(2ⁿ) → "O of 2 to the n" hoặc "exponential time"2. Common Time Complexities
O(1) - Constant Time
Định nghĩa: Operation mất cùng thời gian bất kể input size.
Ví dụ:
- Truy cập array element theo index
- Lấy value từ hash table/object
- Phép toán số học đơn giản
TypeScript:
// O(1) - Constant time
function getFirstElement<T>(arr: T[]): T | undefined {
return arr[0]; // Luôn một operation, bất kể array size
}
// O(1) - Hash table lookup
function getUserAge(users: Map<string, number>, userId: string): number | undefined {
return users.get(userId); // Instant lookup
}Python:
# O(1) - Constant time
def get_first_element(arr: list) -> any:
return arr[0] if arr else None
# O(1) - Dictionary lookup
def get_user_age(users: dict, user_id: str) -> int:
return users.get(user_id)O(log n) - Logarithmic Time
Định nghĩa: Thuật toán chia problem làm đôi mỗi iteration.
Ví dụ: Binary search trong sorted array
TypeScript:
// O(log n) - Binary search
function binarySearch(arr: number[], target: number): number {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
// Với 1,000,000 items, chỉ cần ~20 comparisons!Python:
# O(log n) - Binary search
def binary_search(arr: list[int], target: int) -> int:
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1O(n) - Linear Time
Định nghĩa: Thuật toán xử lý mỗi element một lần.
Ví dụ: Single loop qua array, linear search
TypeScript:
// O(n) - Linear search
function linearSearch<T>(arr: T[], target: T): number {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === target) {
return i;
}
}
return -1;
}
// O(n) - Sum all elements
function sum(arr: number[]): number {
let total = 0;
for (const num of arr) {
total += num;
}
return total;
}O(n log n) - Linearithmic Time
Định nghĩa: Common trong efficient sorting algorithms.
Ví dụ: Merge sort, Quick sort
TypeScript:
// O(n log n) - Merge sort
function mergeSort(arr: number[]): number[] {
if (arr.length <= 1) {
return arr;
}
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
}
// Native sort cũng là O(n log n)
console.log([...arr].sort((a, b) => a - b));O(n²) - Quadratic Time
Định nghĩa: Nested loops qua cùng dataset.
Ví dụ: Bubble sort, checking all pairs
TypeScript:
// O(n²) - Bubble sort
function bubbleSort(arr: number[]): number[] {
const n = arr.length;
for (let i = 0; i < n; i++) { // Outer loop: n times
for (let j = 0; j < n - i - 1; j++) { // Inner loop: n times
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}Python:
# O(n²) - Bubble sort
def bubble_sort(arr: list[int]) -> list[int]:
n = len(arr)
for i in range(n):
for j in range(n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arrO(2ⁿ) - Exponential Time
Định nghĩa: Thuật toán nhân đôi với mỗi additional input.
Ví dụ: Recursive Fibonacci (naive)
TypeScript:
// O(2ⁿ) - Naive recursive Fibonacci
function fibonacci(n: number): number {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Với n=30: ~2 triệu calls!Growth Rate Comparison
| Input Size (n) | O(1) | O(log n) | O(n) | O(n log n) | O(n²) | O(2ⁿ) |
|---|---|---|---|---|---|---|
| 10 | 1 | 3 | 10 | 33 | 100 | 1,024 |
| 100 | 1 | 7 | 100 | 664 | 10,000 | 1.27×10³⁰ |
| 1,000 | 1 | 10 | 1,000 | 9,966 | 1,000,000 | ∞ |
Key Takeaway: O(1) và O(log n) scale cực tốt. O(n²) và O(2ⁿ) trở nên unusable cho large inputs.
3. Cách Tính Big O
Quy Tắc 1: Drop Constants
Constants không ảnh hưởng đến growth rate.
// O(2n) → đơn giản hóa thành O(n)
function printTwice(arr: number[]): void {
arr.forEach(x => console.log(x)); // O(n)
arr.forEach(x => console.log(x)); // O(n)
// Total: O(2n) → O(n)
}Quy Tắc 2: Dominant Term Wins
Giữ lại term tăng nhanh nhất.
// O(n² + n) → đơn giản hóa thành O(n²)
function example(arr: number[]): void {
// Loop đơn: O(n)
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
// Nested loops: O(n²)
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length; j++) {
console.log(arr[i], arr[j]);
}
}
// Total: O(n²)
}Quy Tắc 3: Different Inputs = Different Variables
// O(n + m) - KHÔNG PHẢI O(n)
function printBothArrays(arr1: number[], arr2: number[]): void {
arr1.forEach(x => console.log(x)); // O(n)
arr2.forEach(x => console.log(x)); // O(m)
// Total: O(n + m)
}
// O(n × m) - KHÔNG PHẢI O(n²)
function printPairs(arr1: number[], arr2: number[]): void {
for (const x of arr1) { // O(n)
for (const y of arr2) { // O(m)
console.log(x, y);
}
}
// Total: O(n × m)
}4. Space Complexity
Space complexity đo memory usage khi input tăng.
O(1) - Constant Space
// O(1) space - Chỉ vài variables
function sumArray(arr: number[]): number {
let total = 0; // O(1) space
for (const num of arr) {
total += num;
}
return total;
}O(n) - Linear Space
// O(n) space - Tạo new array
function doubleArray(arr: number[]): number[] {
const result: number[] = []; // O(n) space
for (const num of arr) {
result.push(num * 2);
}
return result;
}Recursion Stack Space
// O(n) space do call stack
function factorial(n: number): number {
if (n <= 1) return 1;
return n * factorial(n - 1); // Mỗi call dùng stack space
}
// Maximum stack depth: n → O(n) space5. Built-In Operations Complexity
TypeScript/JavaScript
| Operation | Time Complexity | Ghi chú |
|---|---|---|
arr[i] | O(1) | Array access |
arr.push(x) | O(1) | Add to end |
arr.pop() | O(1) | Remove from end |
arr.unshift(x) | O(n) | Add to start |
arr.shift() | O(n) | Remove from start |
arr.sort() | O(n log n) | Sort array |
map.get(key) | O(1) | Hash table lookup |
set.has(x) | O(1) | Set membership |
Python
| Operation | Time Complexity | Ghi chú |
|---|---|---|
arr[i] | O(1) | List access |
arr.append(x) | O(1) | Add to end |
arr.pop() | O(1) | Remove from end |
arr.insert(0, x) | O(n) | Insert at start |
sorted(arr) | O(n log n) | Sort list |
dict[key] | O(1) | Dictionary lookup |
x in set | O(1) | Set membership |
6. Time vs Space Trade-offs
Thường bạn có thể trade space cho time (hoặc ngược lại).
Ví Dụ: Two Sum
// Brute force: O(n²) time, O(1) space
function twoSumBrute(arr: number[], target: number): [number, number] | null {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] + arr[j] === target) {
return [i, j];
}
}
}
return null;
}
// Optimized: O(n) time, O(n) space
function twoSumHash(arr: number[], target: number): [number, number] | null {
const seen = new Map<number, number>();
for (let i = 0; i < arr.length; i++) {
const complement = target - arr[i];
if (seen.has(complement)) {
return [seen.get(complement)!, i];
}
seen.set(arr[i], i);
}
return null;
}Trade-off: Chúng ta dùng extra space (hash table) để đạt faster time.
7. Interview Tips
Cách Communicate Complexity
Tốt:
"Solution này có O(n) time complexity vì chúng ta iterate qua array một lần. Space complexity là O(1) vì chỉ dùng vài variables."
Không tốt:
"Nó khá nhanh."
Red Flags trong Interviews
- O(n²) hoặc tệ hơn khi O(n) hoặc O(n log n) có thể
- O(2ⁿ) không có memoization
- Không xem xét edge cases
Optimization Strategy
- Bắt đầu với brute force: Có working solution trước
- Analyze complexity: "Đây là O(n²), có thể tốt hơn không?"
- Identify bottlenecks: Nested loops? Repeated work?
- Apply patterns: Hash tables cho lookups, two pointers, binary search
- Xem xét trade-offs: O(n) space có chấp nhận được cho O(n log n) → O(n) time?
Tổng Kết và Key Takeaways
✅ Big O đo scalability, không phải absolute speed
✅ Common complexities: O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ)
✅ Drop constants và giữ dominant term: O(2n + 5) → O(n)
✅ Analyze loops: Nested loops nhân complexity
✅ Space complexity quan trọng: Xem xét memory usage
✅ Recursion dùng stack space: O(n) space cho depth n
✅ Built-in operations có complexity: Biết operations của ngôn ngữ
✅ Trade-offs tồn tại: Time vs space, simplicity vs efficiency
✅ Focus vào worst-case cho interviews và production
Bước Tiếp Theo
Giờ bạn đã hiểu complexity analysis, bạn đã sẵn sàng cho:
Tiếp tục DSA series:
- Problem-Solving Patterns & Techniques - Áp dụng complexity analysis
- Arrays & Strings - Học data structure operations
Quay lại roadmap:
- Data Structures & Algorithms Roadmap - Lộ trình học hoàn chỉnh
Practice Resources:
- LeetCode - Filter by "Array" và "Easy"
- HackerRank - Data Structures track
Thuộc chuỗi bài Data Structures & Algorithms Roadmap
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.