Thư Viện Câu Hỏi Phỏng Vấn
Tổng hợp các câu hỏi tuyển dụng thực tế theo nhiều cấp độ từ Entry đến Expert để bạn tự tin chinh phục nhà tuyển dụng.
Hàng đợi ưu tiên (Priority Queue) hoạt động như thế nào và sự khác biệt khi triển khai bằng Heap so với Mảng/Danh sách liên kết?
Hàng đợi ưu tiên là cấu trúc dữ liệu mà mỗi phần tử được gắn với một độ ưu tiên. Phần tử có độ ưu tiên cao nhất luôn được lấy ra trước, bất kể thời điểm thêm vào.
- Triển khai bằng Mảng/Danh sách liên kết:
- Nếu giữ mảng không sắp xếp: Thêm phần tử tốn O(1), lấy phần tử lớn nhất tốn O(n) vì phải quét toàn bộ mảng.
- Nếu giữ mảng sắp xếp: Thêm phần tử tốn O(n) vì phải dịch chuyển các phần tử khác, lấy ra tốn O(1).
- Triển khai bằng Binary Heap (Tối ưu nhất):
- Heap là cây nhị phân gần hoàn chỉnh thỏa mãn tính chất Heap (cha luôn lớn hơn hoặc nhỏ hơn con).
- Thao tác thêm phần tử
enqueuetốn O(log n) nhờ quá trình sift-up. - Thao tác lấy ra phần tử đầu
dequeuetốn O(log n) nhờ quá trình sift-down. - Xem phần tử đầu
peektốn O(1). - Đây là sự cân bằng tuyệt vời giúp quản lý các tác vụ ưu tiên động cực kỳ hiệu quả.
Cấu trúc dữ liệu Heap (Min-Heap / Max-Heap) là gì? Thao tác thêm phần tử (Heapify-up) và xóa phần tử gốc (Heapify-down) có độ phức tạp là bao nhiêu?
Heap là một cây nhị phân gần như hoàn chỉnh (thường được biểu diễn dưới dạng mảng để tiết kiệm bộ nhớ) thỏa mãn tính chất:
- Max-Heap: Giá trị của node cha luôn lớn hơn hoặc bằng giá trị các con của nó (Gốc là giá trị lớn nhất).
- Min-Heap: Giá trị của node cha luôn nhỏ hơn hoặc bằng giá trị các con của nó (Gốc là giá trị nhỏ nhất).
- Thao tác Thêm (Heapify-up): Phần tử mới được chèn vào cuối cây (cuối mảng). Sau đó, nó được so sánh với cha và hoán vị đi lên nếu vi phạm thuộc tính Heap. Chiều cao tối đa của cây là log n, nên độ phức tạp là O(log n).
- Thao tác Xóa gốc (Heapify-down): Thay thế phần tử gốc bằng phần tử cuối cùng của mảng, sau đó xóa phần tử cuối cùng này. Tiếp theo, so sánh gốc mới với các con và hoán vị đi xuống với con lớn hơn (hoặc nhỏ hơn đối với Min-Heap) để tái lập cấu trúc Heap. Độ phức tạp là O(log n).
Làm thế nào để kiểm tra một số nguyên dương N có phải là lũy thừa của 2 (Power of Two) chỉ bằng các phép toán Bitwise trong O(1)?
Một số nguyên dương N là lũy thừa của 2 nếu và chỉ nếu biểu diễn nhị phân của nó chỉ chứa duy nhất một bit 1 (ví dụ: 4 = 100_2, 8 = 1000_2, 16 = 10000_2).
- Giải pháp Bitwise: Sử dụng biểu thức
(N & (N - 1)) == 0. - Giải thích cơ chế:
- Số N (lũy thừa của 2) có dạng:
100...0. - Số N - 1 sẽ có dạng:
011...1(tất cả các bit sau bit 1 của N đều bị đảo ngược). - Khi thực hiện phép toán AND bitwise (
&):(100...0) & (011...1) = 000...0. - Đối với bất kỳ số nào không phải là lũy thừa của 2, biểu diễn nhị phân của nó sẽ có nhiều hơn một bit 1. Do đó, phép toán
N & (N - 1)sẽ giữ lại các bit 1 phía trước và không thể cho kết quả bằng 0.
- Số N (lũy thừa của 2) có dạng:
- Lưu ý: Cần kiểm tra thêm điều kiện N phải lớn hơn 0. Hàm hoàn chỉnh:
return (N > 0) && ((N & (N - 1)) == 0). Độ phức tạp thời gian và không gian đều là O(1).
Cấu trúc dữ liệu Trie (Prefix Tree) hoạt động như thế nào? Tại sao nó cực kỳ hiệu quả cho tính năng Autocomplete và Search Suggestions?
Trie (Cây tiền tố) là một cây có cấu trúc phân nhánh đặc biệt dùng để lưu trữ một tập hợp các chuỗi ký tự, trong đó các node chia sẻ chung tiền tố (prefix) với nhau.
- Cơ chế hoạt động: Mỗi node của Trie đại diện cho một ký tự. Node gốc không chứa ký tự nào. Đường đi từ gốc đến một node cụ thể biểu diễn tiền tố hoặc từ hoàn chỉnh được lưu trữ. Mỗi node sẽ chứa một mảng liên kết các con trỏ tới các ký tự tiếp theo và một biến boolean đánh dấu kết thúc từ (isEndOfWord).
- Tại sao hiệu quả cho Autocomplete:
- Tốc độ tìm kiếm từ có độ dài L chỉ là O(L), hoàn toàn độc lập với số lượng từ N được lưu trong Trie (ví dụ: tìm từ trong 1 triệu từ vẫn chỉ tốn số bước bằng số ký tự của từ đó).
- Dễ dàng tìm kiếm tất cả các từ bắt đầu bằng một tiền tố cho trước bằng cách đi đến node đại diện cho tiền tố đó, sau đó duyệt DFS/BFS để thu thập tất cả các từ con bên dưới. Điều này giúp tối ưu hóa tối đa thời gian phản hồi cho các hệ thống gợi ý từ khóa thời gian thực.
Giải thuật Quay lui (Backtracking) khác gì so với duyệt vét cạn (Brute Force)? Giải thích cơ chế Pruning (Cắt tỉa nhánh)?
Đệ quy đuôi (Tail Recursion) là gì và tại sao nó giúp tối ưu hóa bộ nhớ ngăn xếp (Stack) hơn đệ quy thông thường?
Đệ quy đuôi là dạng đệ quy mà lời gọi đệ quy là thao tác cuối cùng được thực thi trong hàm trước khi trả về kết quả. Không có bất kỳ phép toán nào khác (như cộng, nhân) được thực hiện sau cuộc gọi đệ quy đó.
- Đệ quy thường:
return n * fact(n - 1). Sau khifact(n - 1)chạy xong, chương trình vẫn phải quay lại để thực hiện phép nhân vớin. Vì vậy, JVM/Compiler bắt buộc phải giữ lại khung ngăn xếp (stack frame) của hàm hiện tại để lưu trữ biếnn. - Đệ quy đuôi:
return fact_tail(n - 1, n * accumulator). Kết quả của lời gọi đệ quy tiếp theo cũng chính là kết quả cuối cùng của hàm hiện tại. - Tối ưu hóa đệ quy đuôi (TCO - Tail Call Optimization): Các trình biên dịch thông minh (như GCC, Kotlin, Swift, Safari JS Engine) sẽ phát hiện đệ quy đuôi và tái sử dụng lại stack frame hiện tại thay vì tạo ra stack frame mới. Thao tác đệ quy đuôi được chuyển đổi ngầm thành vòng lặp (loop), giúp giải quyết triệt để nguy cơ tràn bộ nhớ ngăn xếp (Stack Overflow) với độ phức tạp không gian O(1).
So sánh các cách tính số Fibonacci thứ N bằng Đệ quy thường, Quy hoạch động, và Nhân ma trận về mặt độ phức tạp thời gian và không gian?
Có 3 phương pháp phổ biến để tính số Fibonacci thứ N với hiệu năng khác nhau rõ rệt:
- Đệ quy thông thường:
- Cơ chế: Gọi đệ quy song song
F(n) = F(n-1) + F(n-2). - Hiệu năng: Tốn O(2^n) thời gian vì tính lại các bài toán con trùng lặp quá nhiều. Tốn O(n) không gian cho Stack đệ quy.
- Cơ chế: Gọi đệ quy song song
- Quy hoạch động (DP - Iterative):
- Cơ chế: Lưu 2 giá trị Fibonacci trước đó và tính tiến lên bằng vòng lặp.
- Hiệu năng: Tốn O(n) thời gian (chỉ duyệt 1 vòng lặp) và O(1) không gian (chỉ lưu 2 biến tạm).
- Nhân ma trận (Matrix Exponentiation):
- Cơ chế: Dựa trên công thức toán học luỹ thừa ma trận [ [1, 1], [1, 0] ]^n. Ta tính luỹ thừa bằng phương pháp Chia để trị (Binary Exponentiation).
- Hiệu năng: Tốn O(log n) thời gian và O(log n) không gian (hoặc O(1) nếu lặp). Cực kỳ hữu ích khi N rất lớn (vài tỷ).
Làm thế nào để triển khai và tối ưu hóa thuật toán hoặc cấu trúc dữ liệu liên quan đến Circular Queue trong chủ đề Queues?
Trong lập trình giải thuật với Queues, việc làm chủ Circular Queue yêu cầu lập trình viên hiểu rõ cấu trúc vật lý trong bộ nhớ và độ phức tạp tính toán:
- Độ phức tạp: Luôn đánh giá Time Complexity (thời gian) và Space Complexity (không gian) tối ưu nhất (ví dụ: tối ưu từ O(n^2) xuống O(n log n)).
- Trường hợp biên (Edge Cases): Xử lý kỹ các giá trị null, mảng rỗng, giá trị giới hạn cực đại/cực tiểu của kiểu dữ liệu.
- Mã nguồn mẫu: Triển khai giải pháp rõ ràng, súc tích bằng các cấu trúc dữ liệu cơ bản, tránh lạm dụng bộ nhớ phụ khi không cần thiết.
Làm thế nào để triển khai và tối ưu hóa thuật toán hoặc cấu trúc dữ liệu liên quan đến TreeMap vs HashMap trong chủ đề Heaps and Maps?
Trong lập trình giải thuật với Heaps and Maps, việc làm chủ TreeMap vs HashMap yêu cầu lập trình viên hiểu rõ cấu trúc vật lý trong bộ nhớ và độ phức tạp tính toán:
- Độ phức tạp: Luôn đánh giá Time Complexity (thời gian) và Space Complexity (không gian) tối ưu nhất (ví dụ: tối ưu từ O(n^2) xuống O(n log n)).
- Trường hợp biên (Edge Cases): Xử lý kỹ các giá trị null, mảng rỗng, giá trị giới hạn cực đại/cực tiểu của kiểu dữ liệu.
- Mã nguồn mẫu: Triển khai giải pháp rõ ràng, súc tích bằng các cấu trúc dữ liệu cơ bản, tránh lạm dụng bộ nhớ phụ khi không cần thiết.
Làm thế nào để triển khai và tối ưu hóa thuật toán hoặc cấu trúc dữ liệu liên quan đến Undo/Redo systems trong chủ đề Stacks?
Trong lập trình giải thuật với Stacks, việc làm chủ Undo/Redo systems yêu cầu lập trình viên hiểu rõ cấu trúc vật lý trong bộ nhớ và độ phức tạp tính toán:
- Độ phức tạp: Luôn đánh giá Time Complexity (thời gian) và Space Complexity (không gian) tối ưu nhất (ví dụ: tối ưu từ O(n^2) xuống O(n log n)).
- Trường hợp biên (Edge Cases): Xử lý kỹ các giá trị null, mảng rỗng, giá trị giới hạn cực đại/cực tiểu của kiểu dữ liệu.
- Mã nguồn mẫu: Triển khai giải pháp rõ ràng, súc tích bằng các cấu trúc dữ liệu cơ bản, tránh lạm dụng bộ nhớ phụ khi không cần thiết.
Làm thế nào để triển khai và tối ưu hóa thuật toán hoặc cấu trúc dữ liệu liên quan đến LCS & LIS trong chủ đề Dynamic Programming?
Trong lập trình giải thuật với Dynamic Programming, việc làm chủ LCS & LIS yêu cầu lập trình viên hiểu rõ cấu trúc vật lý trong bộ nhớ và độ phức tạp tính toán:
- Độ phức tạp: Luôn đánh giá Time Complexity (thời gian) và Space Complexity (không gian) tối ưu nhất (ví dụ: tối ưu từ O(n^2) xuống O(n log n)).
- Trường hợp biên (Edge Cases): Xử lý kỹ các giá trị null, mảng rỗng, giá trị giới hạn cực đại/cực tiểu của kiểu dữ liệu.
- Mã nguồn mẫu: Triển khai giải pháp rõ ràng, súc tích bằng các cấu trúc dữ liệu cơ bản, tránh lạm dụng bộ nhớ phụ khi không cần thiết.
Làm thế nào để triển khai và tối ưu hóa thuật toán hoặc cấu trúc dữ liệu liên quan đến XOR operations trong chủ đề Bit Manipulation?
Trong lập trình giải thuật với Bit Manipulation, việc làm chủ XOR operations yêu cầu lập trình viên hiểu rõ cấu trúc vật lý trong bộ nhớ và độ phức tạp tính toán:
- Độ phức tạp: Luôn đánh giá Time Complexity (thời gian) và Space Complexity (không gian) tối ưu nhất (ví dụ: tối ưu từ O(n^2) xuống O(n log n)).
- Trường hợp biên (Edge Cases): Xử lý kỹ các giá trị null, mảng rỗng, giá trị giới hạn cực đại/cực tiểu của kiểu dữ liệu.
- Mã nguồn mẫu: Triển khai giải pháp rõ ràng, súc tích bằng các cấu trúc dữ liệu cơ bản, tránh lạm dụng bộ nhớ phụ khi không cần thiết.
Làm thế nào để triển khai và tối ưu hóa thuật toán hoặc cấu trúc dữ liệu liên quan đến Public/Private Keys trong chủ đề Blockchain?
Trong lập trình giải thuật với Blockchain, việc làm chủ Public/Private Keys yêu cầu lập trình viên hiểu rõ cấu trúc vật lý trong bộ nhớ và độ phức tạp tính toán:
- Độ phức tạp: Luôn đánh giá Time Complexity (thời gian) và Space Complexity (không gian) tối ưu nhất (ví dụ: tối ưu từ O(n^2) xuống O(n log n)).
- Trường hợp biên (Edge Cases): Xử lý kỹ các giá trị null, mảng rỗng, giá trị giới hạn cực đại/cực tiểu của kiểu dữ liệu.
- Mã nguồn mẫu: Triển khai giải pháp rõ ràng, súc tích bằng các cấu trúc dữ liệu cơ bản, tránh lạm dụng bộ nhớ phụ khi không cần thiết.
Làm thế nào để triển khai và tối ưu hóa thuật toán hoặc cấu trúc dữ liệu liên quan đến Trie Node optimization trong chủ đề Trie?
Trong lập trình giải thuật với Trie, việc làm chủ Trie Node optimization yêu cầu lập trình viên hiểu rõ cấu trúc vật lý trong bộ nhớ và độ phức tạp tính toán:
- Độ phức tạp: Luôn đánh giá Time Complexity (thời gian) và Space Complexity (không gian) tối ưu nhất (ví dụ: tối ưu từ O(n^2) xuống O(n log n)).
- Trường hợp biên (Edge Cases): Xử lý kỹ các giá trị null, mảng rỗng, giá trị giới hạn cực đại/cực tiểu của kiểu dữ liệu.
- Mã nguồn mẫu: Triển khai giải pháp rõ ràng, súc tích bằng các cấu trúc dữ liệu cơ bản, tránh lạm dụng bộ nhớ phụ khi không cần thiết.
Làm thế nào để triển khai và tối ưu hóa thuật toán hoặc cấu trúc dữ liệu liên quan đến Permutations trong chủ đề Backtracking?
Trong lập trình giải thuật với Backtracking, việc làm chủ Permutations yêu cầu lập trình viên hiểu rõ cấu trúc vật lý trong bộ nhớ và độ phức tạp tính toán:
- Độ phức tạp: Luôn đánh giá Time Complexity (thời gian) và Space Complexity (không gian) tối ưu nhất (ví dụ: tối ưu từ O(n^2) xuống O(n log n)).
- Trường hợp biên (Edge Cases): Xử lý kỹ các giá trị null, mảng rỗng, giá trị giới hạn cực đại/cực tiểu của kiểu dữ liệu.
- Mã nguồn mẫu: Triển khai giải pháp rõ ràng, súc tích bằng các cấu trúc dữ liệu cơ bản, tránh lạm dụng bộ nhớ phụ khi không cần thiết.



.png)
.png)