Hướng dẫn giải của Tìm kiếm nhị phân 1
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.
Lời giải này đang bị ẩn cho đến khi bạn chọn mở ra.
Chúng tôi khuyên bạn nên tự thử giải bài trước. Việc mở lời giải có thể làm lộ mất ý tưởng chính trước khi bạn có cơ hội tự giải.
Bạn phải đăng nhập để mở lời giải này.
Đăng nhậpTác giả: , , ,
Editorial for search1: Tìm kiếm nhị phân 1
Hiểu bài toán
Cho hai dãy số A (có độ dài n) và B (có độ dài m). Dãy A đã được sắp xếp không giảm. Nhiệm vụ là với mỗi phần tử trong dãy B, kiểm tra xem nó có xuất hiện trong dãy A hay không. Xuất ra một xâu nhị phân độ dài m, trong đó ký tự thứ i là '1' nếu b[i] có trong A, ngược lại là '0'. Giới hạn n, m lên tới 100,000.
Các cách tiếp cận
Cách Brute Force (Duyệt tuần tự)
#include <stdio.h>
int main() {
int n, m;
scanf("%d %d", &n, &m);
int a[n], b[m];
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
for(int i = 0; i < m; i++) scanf("%d", &b[i]);
for(int i = 0; i < m; i++) {
int found = 0;
for(int j = 0; j < n; j++) {
if(a[j] == b[i]) {
found = 1;
break;
}
}
printf("%d", found);
}
return 0;
}
- Time Complexity: O(n * m) ~ 10^10 operations (Quá chậm)
- Space Complexity: O(n + m)
Với mỗi phần tử trong mảng B, ta duyệt qua toàn bộ mảng A để tìm kiếm. Do cả n và m đều có thể lên tới 10^5, độ phức tạp O(n*m) sẽ dẫn đến thời gian chạy quá giới hạn (Time Limit Exceeded).
Cách Binary Search (Tìm kiếm nhị phân)
#include <stdio.h>
#include <stdlib.h>
int binarySearch(int a[], int n, int x) {
int l = 0, r = n - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (a[mid] == x) return 1;
if (a[mid] < x) l = mid + 1;
else r = mid - 1;
}
return 0;
}
int main() {
int n, m;
scanf("%d %d", &n, &m);
int *a = (int*)malloc(n * sizeof(int));
int *b = (int*)malloc(m * sizeof(int));
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
for(int i = 0; i < m; i++) scanf("%d", &b[i]);
for(int i = 0; i < m; i++) {
printf("%d", binarySearch(a, n, b[i]));
}
free(a); free(b);
return 0;
}
- Time Complexity: O(m * log n)
- Space Complexity: O(n + m)
Tận dụng việc mảng A đã được sắp xếp. Với mỗi phần tử trong B, ta thực hiện tìm kiếm nhị phân trong A. Độ phức tạp là O(m * log n), với n, m <= 10^5 thì khoảng 1.7 triệu thao tác, hoàn toàn chấp nhận được. Đây là phương pháp tối ưu nhất cho yêu cầu bài toán.
Cách Hash Map (Đếm tần suất)
#include <stdio.h>
#include <stdlib.h>
// Giả lập Hash Map bằng mảng quy hoạch động hoặc cây (đối với số lớn)
// Ở đây dùng cây hoặc map chuẩn của C++ là tốt nhất.
// Code minh họa logic:
int main() {
int n, m;
scanf("%d %d", &n, &m);
// Sử dụng mảng đánh dấu (nếu số nhỏ) hoặc hash map (nếu số lớn)
// Do |a_i| <= 10^9, ta cần dùng map hoặc set.
// Dưới đây là code minh họa logic dùng Set/Cây (thay vì hash map trực tiếp trong C)
// Logic: Duyệt mảng A, chèn vào Cây/Trie/Hash Map. Sau đó kiểm tra B.
// Độ phức tạp O((n+m) log n) hoặc O(n+m) nếu dùng hash tốt.
// Code C++ tham khảo:
/*
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m; cin >> n >> m;
unordered_set<int> s;
for(int i=0; i<n; i++) { int x; cin >> x; s.insert(x); }
for(int i=0; i<m; i++) { int x; cin >> x; cout << (s.count(x) ? "1" : "0"); }
}
*/
return 0;
}
- Time Complexity: O(n + m) (trung bình) hoặc O((n+m) log n)
- Space Complexity: O(n)
Sử dụng Hash Map (hoặc Set) để lưu các phần tử của mảng A. Sau đó, với mỗi phần tử trong B, ta kiểm tra sự tồn tại trong Hash Map với thời gian O(1) trung bình. Phương pháp này cũng hiệu quả nhưng thường cần nhiều bộ nhớ hơn và phức tạp hơn trong lập trình C thuần so với Binary Search.
Phân tích độ phức tạp
| Cách tiếp cận | Time | Space | Tên |
|---|---|---|---|
| 1 | O(n * m) ~ 10^10 operations (Quá chậm) | O(n + m) | Brute Force (Duyệt tuần tự) |
| 2 | O(m * log n) | O(n + m) | Binary Search (Tìm kiếm nhị phân) |
| 3 | O(n + m) (trung bình) hoặc O((n+m) log n) | O(n) | Hash Map (Đếm tần suất) |
Bài học kinh nghiệm
- Mảng A đã được sắp xếp là manh mối quan trọng nhất, cho phép áp dụng tìm kiếm nhị phân.
- Với bài toán tìm kiếm nhiều lần (m truy vấn) trên một mảng tĩnh, tìm kiếm nhị phân là lựa chọn tối ưu về cân bằng giữa thời gian và bộ nhớ.
Lỗi thường gặp
- Lỗi chia đôi tràn số khi tính mid: Nên dùng
mid = left + (right - left) / 2thay vì(left + right) / 2để tránh tràn số int. - Quên kiểm tra biên hoặc vòng lặp vô hạn trong binary search nếu điều kiện
left <= rightkhông đúng.
Bình luận