👉👈 Two Pointers – Kỹ thuật hai con trỏ
Dùng hai chỉ số di chuyển có quy luật để loại bỏ nhiều trạng thái không cần xét, thường giảm O(n²) xuống O(n).
🎯 Mục tiêu bài học
Cần hiểu
- Vì sao hai con trỏ có thể thay vòng lặp lồng nhau.
- Ba mẫu: đối đầu, cùng chiều, đọc–ghi.
- Tính đơn điệu quyết định di chuyển con trỏ.
- Vì sao một vòng while bên trong vẫn có thể là O(n).
Dấu hiệu nhận biết
- Mảng đã sắp xếp.
- Tìm cặp/bộ ba thỏa tổng.
- Đoạn con liên tiếp với điều kiện đơn điệu.
- Loại phần tử trùng tại chỗ.
1. Đặt vấn đề
Cho mảng đã sắp xếp tăng dần và số X. Hãy tìm hai phần tử ở hai vị trí khác nhau có tổng bằng X.
1.1. Cách trực tiếp
Duyệt mọi cặp (i,j), mất O(n²).
1.2. Quan sát
- Nếu
a[L]+a[R]<X, phải tăng L. - Nếu
a[L]+a[R]>X, phải giảm R. - Nếu bằng X, đã tìm thấy đáp án.
| Cách làm | Độ phức tạp | Ý tưởng |
|---|---|---|
| Hai vòng lặp | O(n²) | Xét mọi cặp. |
| Two Pointers | O(n) | L chỉ tăng, R chỉ giảm. |
2. Ba dạng Two Pointers
Đối đầu
L ở đầu, R ở cuối. Dùng cho Pair Sum, palindrome, container with most water.
Cùng chiều
L và R cùng đi sang phải. Dùng cho Sliding Window và đoạn thỏa điều kiện.
Đọc–ghi
read duyệt dữ liệu, write ghi phần hợp lệ. Dùng cho loại trùng, lọc phần tử.
3. Mô phỏng hai con trỏ đối đầu
4. Hai con trỏ cùng chiều – đoạn dài nhất có tổng ≤ K
5. Hai con trỏ đọc–ghi – loại phần tử trùng
Với mảng đã sắp xếp, read đọc từng phần tử, write chỉ tăng khi gặp giá trị mới.
6. Phân tích thuật toán
| Dạng | Điều kiện | Độ phức tạp | Lý do |
|---|---|---|---|
| Pair Sum | Mảng tăng | O(n) | L tăng, R giảm. |
| Longest sum ≤ K | Số không âm | O(n) | Mỗi con trỏ tăng tối đa n lần. |
| Remove duplicates | Mảng tăng | O(n) | Một lượt đọc–ghi. |
| 3Sum | Sắp xếp | O(n²) | Cố định một phần tử, Two Pointers cho hai phần tử còn lại. |
7. Code mẫu
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
long long target;
cin >> n >> target;
vector<long long> a(n);
for (long long &x : a) cin >> x;
sort(a.begin(), a.end());
int left = 0;
int right = n - 1;
while (left < right) {
long long sum = a[left] + a[right];
if (sum == target) {
cout << a[left] << ' ' << a[right];
return 0;
}
if (sum < target) left++;
else right--;
}
cout << "NO";
}int left = 0;
int best = 0;
long long sum = 0;
for (int right = 0; right < n; right++) {
sum += a[right];
while (left <= right && sum > k) {
sum -= a[left];
left++;
}
best = max(best, right - left + 1);
}int removeDuplicates(vector<int>& a) {
if (a.empty()) return 0;
int write = 1;
for (int read = 1; read < (int)a.size(); read++) {
if (a[read] != a[write - 1]) {
a[write] = a[read];
write++;
}
}
return write;
}sort(a.begin(), a.end());
for (int i = 0; i < n; i++) {
if (i > 0 && a[i] == a[i - 1]) continue;
int left = i + 1;
int right = n - 1;
while (left < right) {
long long sum = 1LL * a[i] + a[left] + a[right];
if (sum == target) {
// xử lý bộ ba
left++;
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}8. Giải thích code
8.1. Vì sao Pair Sum cần mảng đã sắp xếp?
Nhờ thứ tự tăng, tăng L chắc chắn không làm giá trị bên trái giảm; giảm R chắc chắn không làm giá trị bên phải tăng.
8.2. Vì sao điều kiện là left < right?
Hai vị trí phải khác nhau. Nếu bằng nhau, ta đang dùng cùng một phần tử hai lần.
8.3. Vì sao vòng while trong for vẫn O(n)?
8.4. Vì sao Sliding Window cần số không âm?
Nếu có số âm, khi mở rộng R tổng có thể giảm, nên không thể quyết định co L chỉ dựa trên sum > K.
8.5. Vì sao remove duplicates ghi vào a[write]?
Đoạn a[0..write-1] luôn chứa các giá trị duy nhất đã tìm thấy.
9. Lỗi thường gặp
- Dùng Pair Sum Two Pointers trên mảng chưa sắp xếp.
- Quên
long longcho tổng. - Dùng
left <= rightvà dùng một phần tử hai lần. - Di chuyển sai con trỏ khi tổng nhỏ/lớn hơn target.
- Dùng Sliding Window sum ≤ K với số âm.
- Không bỏ qua phần tử trùng trong 3Sum.
- Không chứng minh tính đơn điệu.
10. Quiz và bài tập
📘 Cơ bản
- Pair Sum.
- Palindrome.
- Loại trùng.
📗 Trung bình
- Đếm cặp tổng ≤ K.
- Longest sum ≤ K.
- Container With Most Water.
📙 Nâng cao
- 3Sum, 4Sum.
- Merge hai mảng tăng.
- Two Pointers + Prefix Sum.
🐉 HSG
- Đếm đoạn thỏa tính đơn điệu.
- Offline queries.
- Tối ưu DP theo cửa sổ.
💳 Quét mã ủng hộ tuỳ tâm nhé!