VieTopik
Tiếng HànTiếng AnhIT
  • Góc học tập
Tải app
  • Thư viện
  • Luyện thi
  • Cẩm nang
  • Góc học tập
Cấu trúc dữ liệu và giải thuậtKỹ thuật giải bài
Bài 17/18
6 phút

Hai con trỏ và cửa sổ trượt

Nội dung bài · 6 mục
  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Cửa sổ trượt
  4. 4.Thử ngay
  5. 5.Lỗi hay gặp
  6. 6.Tóm tắt

Nhiều bài toán trên array có lời giải dễ nghĩ ra là hai vòng lặp lồng nhau, O(n²). Hai kỹ thuật trong bài này đưa chúng về một lượt duyệt, không cần thêm HashSet hay Dictionary. Riêng hai con trỏ là O(n) sau khi dãy đã sắp xếp.

Khái niệm

👉 Hai con trỏ (two pointers): dùng hai chỉ số cùng duyệt một array, thường một từ đầu và một từ cuối, mỗi bước dời một chỉ số dựa theo kết quả so sánh.

🎞️ Cửa sổ trượt (sliding window): đoạn liền nhau độ dài cố định, trượt sang phải từng ô bằng cách cộng phần tử mới và trừ phần tử cũ.

Ví dụ

Tìm hai món có tổng đúng 19.000đ trong dãy giá đã sắp xếp:

int[] prices = { 3000, 5000, 7000, 12000, 25000 };
int budget = 19000;
int left = 0;
int right = prices.Length - 1;
while (left < right)
{
    int sum = prices[left] + prices[right];
    if (sum == budget)
    {
        Console.WriteLine(
            $"{prices[left]} + {prices[right]}");
        break;
    }
    if (sum < budget)
    {
        left++;
    }
    else
    {
        right--;
    }
}
  • Tổng nhỏ quá thì dời left sang phải để lấy món đắt hơn. Lớn quá thì dời right sang trái để lấy món rẻ hơn.
  • Mỗi bước bỏ đi một món chắc chắn không ghép được, nên nhiều nhất n bước.
  • So với cách dùng HashSet ở chương 3, cách này không tốn thêm bộ nhớ, nhưng cần dãy đã sắp xếp như tìm nhị phân.
3000 + 25000: lớn quá, right lùi 3000 + 12000: nhỏ quá, left tiến 5000 + 12000: nhỏ quá, left tiến 7000 + 12000 = 19000: tìm thấy
Hai con trỏ tìm tổng 19000

Cửa sổ trượt

Doanh thu 7 ngày (triệu đồng). Tìm 3 ngày liên tiếp có tổng doanh thu cao nhất.

Cộng lại từ đầu cho mỗi đoạn 3 ngày thì tốn khoảng n × 3 bước. Cửa sổ trượt mỗi bước chỉ cộng một số và trừ một số.

int[] revenue = { 5, 8, 2, 9, 7, 1, 6 };
int k = 3;
int window = 0;
for (int i = 0; i < k; i++)
{
    window = window + revenue[i];
}
int best = window;
for (int i = k; i < revenue.Length; i++)
{
    window = window + revenue[i] - revenue[i - k];
    if (window > best)
    {
        best = window;
    }
}
Console.WriteLine(best);   // 19
  • Vòng đầu tính tổng 3 ngày đầu tiên.
  • Mỗi vòng sau, ngày i vào cửa sổ, ngày i - k rời cửa sổ.

Thử ngay

Trong ví dụ cửa sổ trượt, in tổng của từng cửa sổ: thêm Console.Write(window + " "); ngay sau dòng int best = window; và ngay sau dòng tính lại window trong vòng for thứ hai.

Đoán trước khi chạy: 7 ngày với cửa sổ 3 ngày thì có mấy cửa sổ, tổng từng cửa sổ là bao nhiêu?

Xem kết quả
15 19 18 17 14 19

Có 7 - 3 + 1 = 5 cửa sổ: 5+8+2, 8+2+9, 2+9+7, 9+7+1, 7+1+6. Số 19 cuối cùng là dòng in best. Cửa sổ thứ hai, ngày 2 tới ngày 4, có doanh thu cao nhất.

Lỗi hay gặp

Dùng hai con trỏ trên dãy chưa sắp xếp. Cách dời con trỏ dựa vào việc số bên phải luôn lớn hơn. Dãy chưa sắp xếp thì con trỏ có thể bỏ qua đúng cặp cần tìm.

// SAI — 7000 + 12000 có trong dãy nhưng không tìm ra
int[] prices = { 12000, 3000, 25000, 7000, 5000 };
// ĐÚNG — sắp xếp trước rồi mới dùng hai con trỏ
int[] prices = { 12000, 3000, 25000, 7000, 5000 };
Array.Sort(prices);

Tóm tắt

  • Hai con trỏ: một từ đầu, một từ cuối, dời theo kết quả so sánh. Cần dãy đã sắp xếp.
  • Cửa sổ trượt: đoạn liền nhau, mỗi bước cộng phần tử vào, trừ phần tử ra.
  • Cả hai chỉ duyệt một lượt, không tốn thêm bộ nhớ. Cửa sổ trượt là O(n), hai con trỏ là O(n) sau khi dãy đã sắp xếp.
  • Gặp bài "cặp có tổng", "đoạn liên tiếp" thì nghĩ tới hai kỹ thuật này.

Tự kiểm tra

0/3 câu
Câu 1

Hai con trỏ trên dãy giá đã sắp xếp: tổng prices[left] + prices[right] lớn hơn ngân sách. Nên làm gì?

Câu 2

Cửa sổ 7 ngày trượt trên dữ liệu 30 ngày. Có bao nhiêu cửa sổ?

Câu 3

Khi cửa sổ trượt sang phải một ô, tổng mới tính thế nào?

DFSQuy hoạch động

Nội dung bài

  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Cửa sổ trượt
  4. 4.Thử ngay
  5. 5.Lỗi hay gặp
  6. 6.Tóm tắt