VieTopikKhoá học
  • Games
  • Cá nhân
Tải app
© 2026 VieTopikChính sách bảo mậtĐiều khoản sử dụngCài appEnglish
  • Trang chủ
  • Học
  • Luyện thi
  • Cẩm nang
  • Games
  • Cá nhân
Cấu trúc dữ liệu và giải thuậtBảng băm
Bài 8/18
6 phút

HashSet và bài toán đếm

Bình luận
Nội dung bài · 6 mục
  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Tìm hai món đúng bằng ngân sách
  4. 4.Thử ngay
  5. 5.Lỗi hay gặp
  6. 6.Tóm tắt

Nhiều bài toán hằng ngày chỉ là đếm và kiểm tra trùng: mỗi sản phẩm bán được bao nhiêu cái, có bao nhiêu khách khác nhau, có hai món nào đúng bằng ngân sách không. Làm bằng hai vòng lặp lồng nhau là O(n²). Dùng hash table thì chỉ còn O(n).

Khái niệm

🫧 HashSet: tập hợp các phần tử không trùng nhau, bên trong là hash table nên add, contains, remove trung bình là O(1).

HashSet<E> giống một HashMap chỉ có key, không có value. add trả về false nếu phần tử đã có, nên vừa kiểm tra trùng vừa thêm chỉ bằng một lời gọi.

Ví dụ

Đếm số lần mỗi sản phẩm được bán, dùng HashMap từ tên sang số lượng:

// Main.java
void main() {
    List<String> sold = List.of(
        "Bút bi", "Vở", "Bút bi",
        "Thước", "Bút bi", "Vở");

    Map<String, Integer> counts = new HashMap<>();
    for (String name : sold) {
        if (counts.containsKey(name)) {
            counts.put(name, counts.get(name) + 1);
        } else {
            counts.put(name, 1);
        }
    }

    for (var item : counts.entrySet()) {
        System.out.println("%s: %d".formatted(
            item.getKey(), item.getValue()));
    }
}
  • Duyệt list đúng một lần, mỗi lần tra và cập nhật map là O(1). Tổng cộng O(n).
  • Chương trình in Bút bi: 3, Vở: 2, Thước: 1. Thứ tự này do ô của từng key quyết định: HashMap không hứa giữ thứ tự thêm vào.

Tìm hai món đúng bằng ngân sách

Khách có 19.000đ, muốn mua đúng hai món cho hết số tiền đó. Với mỗi giá price, món còn lại phải có giá budget - price. Hỏi HashSet xem đã gặp giá đó chưa, thay vì so với mọi giá khác.

// Main.java
void main() {
    List<Long> prices = List.of(
        5000L, 12000L, 350000L, 7000L);
    long budget = 19000;

    Set<Long> seen = new HashSet<>();
    for (long price : prices) {
        long need = budget - price;
        if (seen.contains(need)) {
            System.out.println("%d + %d".formatted(
                need, price));
        }
        seen.add(price);
    }
}
  • Gặp 7000, cần thêm 12000, mà 12000 đã có trong seen. In ra 12000 + 7000.
  • Một vòng lặp, mỗi bước O(1), nên tổng là O(n). Hai vòng lồng nhau so từng cặp thì là O(n²).

▶ Mô phỏng: Hỏi HashSet thay vì so từng cặp

void main() {
    List<Long> prices = List.of(5000L, 12000L, 350000L, 7000L);
    long budget = 19000;
    Set<Long> seen = new HashSet<>();
    for (long price : prices) {
        long need = budget - price;
        if (seen.contains(need)) {
            System.out.println("%d + %d".formatted(need, price));
        }
        seen.add(price);
    }
}
Xem từng bước
  1. price = 5000: món còn lại phải có giá 19000 − 5000 = 14000.
  2. Hỏi seen có 14000 không: chưa có.
  3. Thêm 5000 vào seen để các giá sau hỏi tới.
  4. price = 12000: món còn lại phải có giá 19000 − 12000 = 7000.
  5. Hỏi seen có 7000 không: chưa có.
  6. Thêm 12000 vào seen để các giá sau hỏi tới.
  7. price = 350000: món còn lại phải có giá 19000 − 350000 = -331000.
  8. Hỏi seen có -331000 không: chưa có.
  9. Thêm 350000 vào seen để các giá sau hỏi tới.
  10. price = 7000: món còn lại phải có giá 19000 − 7000 = 12000.
  11. Hỏi seen có 12000 không: có! Một lần hỏi là O(1), không phải so với từng giá.
  12. In 12000 + 7000: đúng bằng ngân sách.
  13. Thêm 7000 vào seen để các giá sau hỏi tới.
  14. Duyệt hết 4 giá, mỗi giá hỏi seen một lần: O(n). So từng cặp bằng hai vòng lồng nhau thì là O(n²).

Thử ngay

Đếm số khách khác nhau đã đặt hàng hôm nay:

// Main.java
void main() {
    Set<String> customers = new HashSet<>();
    System.out.println(customers.add("An"));
    System.out.println(customers.add("Bình"));
    System.out.println(customers.add("An"));
    System.out.println(customers.size());
}

Đoán trước khi chạy: bốn dòng in ra là gì?

Xem kết quả
true
true
false
2

"An" và "Bình" được thêm lần đầu nên add trả true. "An" lần hai đã có, add trả false và không thêm. Tập chỉ có 2 khách.

Lỗi hay gặp

Lấy phần tử của HashSet theo vị trí. HashSet chỉ trả lời "có hay không", không đánh số vị trí cho phần tử, nên không có get(i).

// SAI — lỗi compile: HashSet không có get(i)
Set<String> customers = new HashSet<>();
customers.add("An");
System.out.println(customers.get(0));
// ĐÚNG — cần hỏi "có hay không" thì dùng contains
Set<String> customers = new HashSet<>();
customers.add("An");
System.out.println(customers.contains("An"));

Tóm tắt

  • HashSet<E> giữ phần tử không trùng, add trả false nếu đã có.
  • Đếm số lần xuất hiện bằng HashMap từ phần tử sang số đếm.
  • Tìm cặp có tổng cho trước: với mỗi phần tử, hỏi HashSet phần còn thiếu.
  • Các bài này từ O(n²) xuống O(n) nhờ tra hash table là O(1).
Hash tableTìm kiếm nhị phân

Bình luận

Đang tải bình luận…

Nội dung bài

  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Tìm hai món đúng bằng ngân sách
  4. 4.Thử ngay
  5. 5.Lỗi hay gặp
  6. 6.Tóm tắt