Bài 10.1. Các thao tác cơ bản trên chuỗi

Chào mừng trở lại với chuỗi bài viết về Cấu trúc dữ liệu và Giải thuật!

Trong thế giới lập trình, chúng ta không chỉ làm việc với các con số khô khan hay cấu trúc dữ liệu phức tạp như cây, đồ thị, mà còn phải xử lý một loại dữ liệu vô cùng phổ biến và thiết yếu: đó chính là chuỗi ký tự (string). Từ tên người dùng, nội dung bài viết, câu lệnh code, cho đến dữ liệu truyền qua mạng, tất cả đều có thể biểu diễn dưới dạng chuỗi.

Dù bản thân chuỗi ký tự có vẻ đơn giản, nhưng việc nắm vững các thao tác cơ bản trên chúng là nền tảng cực kỳ quan trọng. Nó không chỉ giúp bạn xử lý dữ liệu văn bản hàng ngày mà còn là kỹ năng cần thiết khi làm việc với các thuật toán xử lý chuỗi phức tạp hơn sau này (ví dụ: tìm kiếm mẫu, nén chuỗi, phân tích cú pháp...).

Trong bài viết này, chúng ta sẽ cùng nhau đi sâu vào các thao tác cơ bản nhất trên chuỗi trong ngôn ngữ C++, sử dụng đối tượng std::string mạnh mẽ và linh hoạt.

Hãy cùng bắt đầu!

1. Khởi tạo và Gán giá trị cho Chuỗi

Trước khi thực hiện bất kỳ thao tác nào, chúng ta cần biết cách tạo ra một chuỗi. Trong C++, std::string cung cấp nhiều cách để làm điều này.

  • Khởi tạo chuỗi rỗng:

    #include <iostream>
    #include <string>
    
    int main() {
        std::string empty_string; // Chuỗi rỗng
        std::cout << "Chuoi rong: '" << empty_string << "'" << std::endl; // In ra chuỗi rỗng
        return 0;
    }
    

    Giải thích: Dòng std::string empty_string; gọi constructor mặc định của std::string, tạo ra một chuỗi không chứa ký tự nào.

  • Khởi tạo từ một chuỗi ký tự cố định (string literal):

    #include <iostream>
    #include <string>
    
    int main() {
        std::string greeting = "Xin chao!"; // Khởi tạo trực tiếp từ literal
        std::string message("Lap trinh that thu vi."); // Sử dụng constructor
        std::cout << "Greeting: " << greeting << std::endl;
        std::cout << "Message: " << message << std::endl;
        return 0;
    }
    

    Giải thích: Bạn có thể dùng toán tử gán = hoặc truyền literal vào constructor () để tạo chuỗi từ một chuỗi ký tự cố định.

  • Khởi tạo từ một chuỗi khác:

    #include <iostream>
    #include <string>
    
    int main() {
        std::string original = "Hello World";
        std::string copy_of_original(original); // Sử dụng constructor sao chép
        std::string another_copy = original;    // Sử dụng toán tử gán
        std::cout << "Original: " << original << std::endl;
        std::cout << "Copy 1: " << copy_of_original << std::endl;
        std::cout << "Copy 2: " << another_copy << std::endl;
        return 0;
    }
    

    Giải thích: Chuỗi có thể được sao chép dễ dàng bằng constructor hoặc toán tử gán =.

  • Khởi tạo với N ký tự lặp lại:

    #include <iostream>
    #include <string>
    
    int main() {
        std::string stars(10, '*'); // Tạo chuỗi gồm 10 ký tự '*'
        std::cout << "Stars: " << stars << std::endl;
        return 0;
    }
    

    Giải thích: Constructor std::string(count, char) tạo ra một chuỗi gồm count lần lặp lại của ký tự char.

  • Gán giá trị sau khi khởi tạo:

    #include <iostream>
    #include <string>
    
    int main() {
        std::string my_string;
        my_string = "Day la mot chuoi moi."; // Gán giá trị
        std::cout << "My string: " << my_string << std::endl;
        return 0;
    }
    

    Giải thích: Bạn có thể dùng toán tử gán = để thay đổi giá trị của một chuỗi đã tồn tại.

2. Truy cập các ký tự trong Chuỗi

Mỗi ký tự trong chuỗi được lưu trữ tại một "vị trí" hay chỉ mục (index). Trong C++ (và hầu hết các ngôn ngữ khác), chỉ mục bắt đầu từ 0. Ký tự đầu tiên có chỉ mục là 0, ký tự thứ hai là 1, và cứ thế tiếp diễn.

Bạn có thể truy cập các ký tự riêng lẻ bằng hai cách chính:

  • Sử dụng toán tử []:

    #include <iostream>
    #include <string>
    
    int main() {
        std::string text = "Hello";
        char first_char = text[0]; // Truy cập ký tự đầu tiên (chỉ mục 0)
        char third_char = text[2];  // Truy cập ký tự thứ ba (chỉ mục 2)
    
        std::cout << "Chuoi: " << text << std::endl;
        std::cout << "Ky tu dau tien: " << first_char << std::endl;
        std::cout << "Ky tu thu ba: " << third_char << std::endl;
    
        // Thay đổi ký tự
        text[0] = 'J';
        std::cout << "Chuoi sau khi thay doi: " << text << std::endl; // Output: Jello
    
        // Cẩn thận với chỉ mục nằm ngoài phạm vi! Hành vi không xác định.
        // char invalid_char = text[10]; // Lỗi tiềm ẩn nếu chỉ mục không hợp lệ
        return 0;
    }
    

    Giải thích: Toán tử [] cho phép truy cập nhanh đến ký tự tại chỉ mục được chỉ định. Tuy nhiên, nó không kiểm tra xem chỉ mục đó có hợp lệ (nằm trong phạm vi từ 0 đến length() - 1) hay không. Nếu bạn truy cập một chỉ mục không hợp lệ, chương trình có thể gặp lỗi hoặc có hành vi không mong muốn.

  • Sử dụng phương thức .at():

    #include <iostream>
    #include <string>
    
    int main() {
        std::string text = "World";
        try {
            char second_char = text.at(1); // Truy cập ký tự thứ hai (chỉ mục 1)
            std::cout << "Ky tu thu hai: " << second_char << std::endl;
    
            // Thay đổi ký tự
            text.at(4) = 'D';
            std::cout << "Chuoi sau khi thay doi: " << text << std::endl; // Output: WorlD
    
            // Thử truy cập chỉ mục không hợp lệ
            char invalid_char = text.at(10); // Sẽ ném ra ngoại lệ std::out_of_range
            std::cout << "Ky tu khong hop le (se khong in ra): " << invalid_char << std::endl;
    
        } catch (const std::out_of_range& e) {
            std::cerr << "Loi truy cap: " << e.what() << std::endl; // In ra thông báo lỗi
        }
        return 0;
    }
    

    Giải thích: Phương thức .at() cũng truy cập ký tự tại chỉ mục, nhưng nó có kiểm tra phạm vi chỉ mục. Nếu chỉ mục không hợp lệ, nó sẽ ném ra một ngoại lệ std::out_of_range, cho phép bạn bắt và xử lý lỗi một cách an toàn hơn. Trong các ứng dụng cần độ tin cậy cao, .at() thường được ưu tiên.

3. Lấy chiều dài (độ dài) của Chuỗi

Biết được số lượng ký tự trong chuỗi là một thao tác rất thường gặp. std::string cung cấp hai phương thức cho mục đích này:

  • .length(): Trả về số lượng ký tự trong chuỗi.
  • .size(): Cũng trả về số lượng ký tự trong chuỗi.

Hai phương thức này thường tương đương nhau trong các triển khai std::string hiện đại.

#include <iostream>
#include <string>

int main() {
    std::string sentence = "Day la mot cau van ban.";
    int len1 = sentence.length();
    int len2 = sentence.size();

    std::cout << "Chuoi: '" << sentence << "'" << std::endl;
    std::cout << "Chieu dai (length()): " << len1 << std::endl;
    std::cout << "Kich thuoc (size()): " << len2 << std::endl;

    std::string empty;
    std::cout << "Chieu dai chuoi rong: " << empty.length() << std::endl; // Output: 0
    return 0;
}

Giải thích: Cả .length().size() đều trả về một giá trị kiểu size_t (một kiểu số nguyên không âm, thường là unsigned long) biểu diễn số ký tự. Kết quả này rất hữu ích khi bạn muốn lặp qua chuỗi hoặc kiểm tra điều kiện dựa trên độ dài.

4. Nối Chuỗi (Concatenation)

Kết hợp hai hoặc nhiều chuỗi thành một là một thao tác phổ biến.

  • Sử dụng toán tử ++=:

    #include <iostream>
    #include <string>
    
    int main() {
        std::string part1 = "Hello, ";
        std::string part2 = "World";
        std::string part3 = "!";
    
        std::string full_message = part1 + part2 + part3; // Nối nhiều chuỗi
        std::cout << "Full message: " << full_message << std::endl; // Output: Hello, World!
    
        std::string greeting = "Hi";
        greeting += ", ";          // Nối thêm literal
        greeting += part2;         // Nối thêm chuỗi khác
        greeting += '!';           // Nối thêm ký tự
        std::cout << "Greeting: " << greeting << std::endl; // Output: Hi, World!
        return 0;
    }
    

    Giải thích: Toán tử + tạo ra một chuỗi mới là kết quả nối của hai chuỗi/literal/ký tự. Toán tử += nối chuỗi/literal/ký tự vào cuối chuỗi hiện tại (thay đổi chuỗi gốc).

  • Sử dụng phương thức .append():

    #include <iostream>
    #include <string>
    
    int main() {
        std::string base = "Learning";
        base.append(" C++");        // Nối thêm literal
        base.append(" is fun!");    // Nối thêm literal khác
        base.append(5, '!');       // Nối thêm 5 ký tự '!'
        std::cout << "Base string: " << base << std::endl; // Output: Learning C++ is fun!!!!!
        return 0;
    }
    

    Giải thích: Phương thức .append() cũng thực hiện nối chuỗi vào cuối chuỗi hiện tại, tương tự như +=. Nó cung cấp các phiên bản khác nhau để nối chuỗi khác, literal, ký tự, hoặc một phần của chuỗi khác.

5. Trích xuất Chuỗi con (Substring)

Thường bạn chỉ cần làm việc với một phần của chuỗi gốc. Phương thức .substr() cho phép bạn làm điều này.

Cú pháp cơ bản là: str.substr(pos, len)

  • pos: Chỉ mục bắt đầu của chuỗi con (0-based).
  • len: Số lượng ký tự bạn muốn trích xuất từ vị trí pos. Nếu bỏ qua len, .substr() sẽ trích xuất từ pos đến hết chuỗi gốc.
#include <iostream>
#include <string>

int main() {
    std::string sentence = "Programming is interesting and useful.";

    // Trích xuất từ chỉ mục 0, lấy 11 ký tự
    std::string part1 = sentence.substr(0, 11);
    std::cout << "Part 1: '" << part1 << "'" << std::endl; // Output: 'Programming'

    // Trích xuất từ chỉ mục 12 (sau khoảng trắng), lấy 2 ký tự
    std::string part2 = sentence.substr(12, 2);
    std::cout << "Part 2: '" << part2 << "'" << std::endl; // Output: 'is'

    // Trích xuất từ chỉ mục 25 đến hết chuỗi
    std::string part3 = sentence.substr(25);
    std::cout << "Part 3: '" << part3 << "'" << std::endl; // Output: 'useful.'

    // Xử lý trường hợp chỉ mục không hợp lệ (có thể ném ngoại lệ out_of_range)
    try {
        std::string invalid_part = sentence.substr(100); // Chỉ mục 100 nằm ngoài phạm vi
    } catch (const std::out_of_range& e) {
        std::cerr << "Loi khi trich xuat chuoi con: " << e.what() << std::endl;
    }

    return 0;
}

Giải thích: .substr(pos, len) trả về một chuỗi mới là chuỗi con được trích xuất. Chuỗi gốc không bị thay đổi. Lưu ý rằng .substr() có thể ném ngoại lệ std::out_of_range nếu chỉ mục pos không hợp lệ.

6. Tìm kiếm trong Chuỗi

Tìm kiếm sự xuất hiện của một ký tự hoặc một chuỗi con khác trong chuỗi là một thao tác rất phổ biến. Phương thức .find() là công cụ chính cho việc này.

Cú pháp cơ bản: str.find(substring, pos)

  • substring: Chuỗi con hoặc ký tự bạn muốn tìm.
  • pos (tùy chọn): Chỉ mục bắt đầu tìm kiếm (mặc định là 0).

Phương thức .find() trả về chỉ mục của lần xuất hiện đầu tiên của substring kể từ vị trí pos. Nếu không tìm thấy, nó trả về giá trị đặc biệt std::string::npos.

#include <iostream>
#include <string>

int main() {
    std::string sentence = "Ban co the tim kiem mot chuoi trong mot chuoi khac.";

    // Tìm kiếm chuỗi con "tim kiem"
    size_t found_pos1 = sentence.find("tim kiem");
    if (found_pos1 != std::string::npos) {
        std::cout << "Tim thay 'tim kiem' tai chi muc: " << found_pos1 << std::endl; // Output: 10
    } else {
        std::cout << "'tim kiem' khong duoc tim thay." << std::endl;
    }

    // Tìm kiếm ký tự 'o'
    size_t found_pos2 = sentence.find('o');
    if (found_pos2 != std::string::npos) {
        std::cout << "Tim thay ky tu 'o' tai chi muc: " << found_pos2 << std::endl; // Output: 4
    }

    // Tìm kiếm "chuoi" bat dau tu chi muc 20
    size_t found_pos3 = sentence.find("chuoi", 20);
     if (found_pos3 != std::string::npos) {
        std::cout << "Tim thay 'chuoi' bat dau tu chi muc 20 tai chi muc: " << found_pos3 << std::endl; // Output: 36
    } else {
        std::cout << "'chuoi' khong duoc tim thay tu chi muc 20." << std::endl;
    }

    // Tìm kiếm mot chuoi khong ton tai
    size_t not_found_pos = sentence.find("xyz");
    if (not_found_pos == std::string::npos) {
        std::cout << "'xyz' khong duoc tim thay trong chuoi." << std::endl;
    }

    return 0;
}

Giải thích: std::string::npos là một hằng số đặc biệt, thường có giá trị lớn nhất có thể của kiểu size_t, được sử dụng để biểu thị rằng thao tác tìm kiếm đã không thành công. Luôn so sánh kết quả của .find() với std::string::npos để biết liệu chuỗi con có được tìm thấy hay không.

Ngoài .find(), std::string còn có các biến thể khác như:

  • .rfind(): Tìm kiếm lần xuất hiện cuối cùng.
  • .find_first_of(): Tìm vị trí của bất kỳ ký tự nào trong một tập hợp ký tự cho trước (tìm ký tự đầu tiên trong tập).
  • .find_last_of(): Tìm vị trí của bất kỳ ký tự nào trong một tập hợp ký tự cho trước (tìm ký tự cuối cùng trong tập).
  • .find_first_not_of(): Tìm vị trí của ký tự đầu tiên không nằm trong tập hợp ký tự cho trước.
  • .find_last_not_of(): Tìm vị trí của ký tự cuối cùng không nằm trong tập hợp ký tự cho trước.

Các phương thức tìm kiếm này rất linh hoạt và hữu ích cho nhiều bài toán xử lý chuỗi.

7. So sánh Chuỗi

So sánh hai chuỗi không chỉ đơn giản là kiểm tra xem chúng có bằng nhau hoàn toàn hay không, mà còn là so sánh theo thứ tự từ điển (lexicographical order), giống như cách các từ được sắp xếp trong từ điển.

  • Sử dụng các toán tử so sánh (==, !=, <, >, <=, >=):

    #include <iostream>
    #include <string>
    
    int main() {
        std::string s1 = "apple";
        std::string s2 = "banana";
        std::string s3 = "apple";
        std::string s4 = "Apple"; // 'A' khác 'a'
    
        if (s1 == s3) {
            std::cout << "s1 va s3 bang nhau." << std::endl; // Output: s1 va s3 bang nhau.
        }
    
        if (s1 != s2) {
            std::cout << "s1 va s2 khac nhau." << std::endl; // Output: s1 va s2 khac nhau.
        }
    
        if (s1 < s2) {
            std::cout << "s1 dung truoc s2 trong tu dien." << std::endl; // Output: s1 dung truoc s2 trong tu dien. ('a' < 'b')
        }
    
         if (s1 > s4) {
            std::cout << "s1 dung sau s4 trong tu dien." << std::endl; // Output: s1 dung sau s4 trong tu dien. ('a' > 'A' trong ASCII)
        }
    
        return 0;
    }
    

    Giải thích: Các toán tử so sánh thực hiện so sánh theo thứ tự từ điển dựa trên giá trị ASCII (hoặc bộ mã ký tự khác) của các ký tự. So sánh này phân biệt chữ hoa và chữ thường.

  • Sử dụng phương thức .compare():

    Phương thức .compare() cung cấp khả năng so sánh chi tiết hơn và linh hoạt hơn. Cú pháp cơ bản: str.compare(other_str)

    • Trả về 0 nếu chuỗi gốc bằng other_str.
    • Trả về một số âm nếu chuỗi gốc đứng trước other_str trong từ điển.
    • Trả về một số dương nếu chuỗi gốc đứng sau other_str trong từ điển.
    #include <iostream>
    #include <string>
    
    int main() {
        std::string s1 = "hello";
        std::string s2 = "world";
        std::string s3 = "hello";
    
        int comp1 = s1.compare(s2); // s1 < s2
        int comp2 = s1.compare(s3); // s1 == s3
        int comp3 = s2.compare(s1); // s2 > s1
    
        std::cout << "s1 so sanh voi s2: " << comp1 << " (Am neu s1 < s2)" << std::endl;
        std::cout << "s1 so sanh voi s3: " << comp2 << " (0 neu s1 == s3)" << std::endl;
        std::cout << "s2 so sanh voi s1: " << comp3 << " (Duong neu s2 > s1)" << std::endl;
    
        // So sánh một phần của chuỗi
        std::string full = "programming";
        std::string prefix = "pro";
        // So sánh 3 ký tự đầu tiên của full với prefix
        int comp4 = full.compare(0, prefix.length(), prefix);
        if (comp4 == 0) {
             std::cout << "'" << full << "' bat dau bang '" << prefix << "'." << std::endl;
        }
    
        return 0;
    }
    

    Giải thích: Phương thức .compare() hữu ích khi bạn cần biết chính xác mối quan hệ từ điển giữa hai chuỗi (nhỏ hơn, bằng, hay lớn hơn) hoặc khi bạn muốn so sánh chỉ một phần của chuỗi.

8. Nhập và Xuất Chuỗi

Làm việc với chuỗi thường bao gồm việc đọc chúng từ bàn phím (input) hoặc hiển thị chúng lên màn hình (output).

  • Xuất chuỗi ra console: Sử dụng std::cout với toán tử <<.

    #include <iostream>
    #include <string>
    
    int main() {
        std::string my_name = "FullhouseDev";
        std::cout << "Ten cua toi la: " << my_name << std::endl;
        return 0;
    }
    

    Giải thích: Tương tự như in các kiểu dữ liệu cơ bản, bạn chỉ cần dùng std::cout và toán tử <<.

  • Nhập chuỗi từ console:

    • Sử dụng std::cin với toán tử >>: Phương pháp này đơn giản nhưng có một hạn chế quan trọng: nó chỉ đọc đến khoảng trắng đầu tiên (dấu cách, tab, xuống dòng).

      #include <iostream>
      #include <string>
      
      int main() {
          std::string first_word;
          std::cout << "Nhap mot tu: ";
          std::cin >> first_word; // Chi doc den khoang trang
          std::cout << "Tu da nhap: " << first_word << std::endl;
      
          // Nếu bạn nhập "Lap trinh C++", chỉ "Lap" sẽ được đọc vào first_word
          return 0;
      }
      

      Giải thích: Hữu ích khi bạn chỉ cần đọc một từ duy nhất.

    • Sử dụng std::getline(): Đây là cách an toàn và phổ biến nhất để đọc toàn bộ một dòng văn bản từ input, bao gồm cả khoảng trắng, cho đến khi gặp ký tự xuống dòng.

      #include <iostream>
      #include <string>
      #include <limits> // Can de xoa bo dem cin neu can
      
      int main() {
          std::string full_line;
          std::cout << "Nhap mot dong van ban: ";
      
          // Can xu ly bo dem neu co cac lenh cin >> truoc do
          // std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
      
          std::getline(std::cin, full_line); // Doc ca dong
          std::cout << "Dong da nhap: " << full_line << std::endl;
      
          // Neu ban nhap "Lap trinh C++ that tuyet", toan bo dong se duoc doc vao full_line
          return 0;
      }
      

      Giải thích: std::getline(input_stream, string_variable) đọc từ input_stream (thường là std::cin) vào string_variable cho đến khi gặp ký tự kết thúc dòng (mặc định là '\n'). Nếu trước getline có sử dụng cin >>, có thể còn ký tự xuống dòng '\n' còn lại trong bộ đệm input, khiến getline đọc ngay ký tự đó và kết thúc mà không chờ nhập liệu. Dòng std::cin.ignore(...) được dùng để xóa bỏ bộ đệm này trước khi gọi getline nếu cần.

9. Các thao tác khác (Nâng cao hơn một chút nhưng vẫn cơ bản)

std::string còn cung cấp nhiều phương thức khác như:

  • .insert(pos, str): Chèn chuỗi str vào vị trí pos.
    std::string s = "Lap trinh!";
    s.insert(3, " toi yeu"); // s tro thanh "Lap toi yeu trinh!"
    
  • .erase(pos, len): Xóa len ký tự bắt đầu từ vị trí pos. Nếu bỏ qua len, xóa từ pos đến hết.
    std::string s = "Xoa mot phan";
    s.erase(4, 4); // Xoa " mot" -> s tro thanh "Xoa phan"
    
  • .replace(pos, len, str): Thay thế len ký tự bắt đầu từ vị trí pos bằng chuỗi str.
    std::string s = "Toi thich tao.";
    s.replace(4, 5, "cam"); // Thay "thich" bang "cam" -> s tro thanh "Toi cam tao."
    
  • .clear(): Xóa tất cả nội dung, biến chuỗi thành rỗng.
    std::string s = "Co noi dung";
    s.clear(); // s tro thanh rong
    
  • .empty(): Trả về true nếu chuỗi rỗng, false nếu ngược lại.
    std::string s1 = "";
    std::string s2 = "abc";
    std::cout << "s1 rong? " << s1.empty() << std::endl; // Output: 1 (true)
    std::cout << "s2 rong? " << s2.empty() << std::endl; // Output: 0 (false)
    

Các thao tác này cho phép bạn tùy chỉnh và biến đổi chuỗi theo nhiều cách khác nhau.

Bài tập ví dụ:

[Xâu kí tự].Tần suất xuất hiện của ký tự.

Cho một xâu kí tự s ,hãy đếm tần suất xuất hiện của các kí tự trong xâu và in ra theo yêu cầu.

Input Format

Xâu kí tự có độ dài không quá 1000 chỉ bao gồm chữ cái.

Constraints

.

Output Format

Đầu tiên in ra các ký tự và tần suất xuất hiện của các ký tự ở trong xâu theo thứ tự từ điển tăng dần, sau đó cách ra một dòng và in ra tần suất xuất hiện của các ký tự theo thứ tự xuất hiện trong xâu(mỗi kí tự chỉ in 1 lần)

Ví dụ:

Dữ liệu vào
bacedcasbdf
Dữ liệu ra
a 2
b 2
c 2
d 2
e 1
f 1
s 1

b 2
a 2
c 2
e 1
d 2
s 1
f 1

Tuyệt vời! Đây là hướng dẫn giải bài tập này bằng C++ một cách ngắn gọn, tập trung vào ý tưởng chính mà không đưa ra code hoàn chỉnh:

  1. Đếm tần suất: Dùng một std::map<char, int> (hoặc một mảng nếu chỉ xử lý chữ cái tiếng Anh a-z) để lưu trữ tần suất xuất hiện của mỗi ký tự. Duyệt qua xâu đầu vào, với mỗi ký tự, tăng giá trị tương ứng trong map lên 1.

  2. In theo thứ tự từ điển: Duyệt qua std::map từ đầu đến cuối. std::map tự động sắp xếp các phần tử theo khóa (ở đây là ký tự), nên việc duyệt tuần tự sẽ cho kết quả theo thứ tự từ điển tăng dần. In ra ký tự và tần suất của nó.

  3. In dòng trống: Sau khi in xong phần đầu tiên, in ra một dòng trống.

  4. In theo thứ tự xuất hiện (duy nhất): Duyệt lại xâu gốc từ đầu. Cần một cách để theo dõi các ký tự đã được in ra để tránh in lặp. Có thể dùng một std::set<char> hoặc một std::map<char, bool>. Với mỗi ký tự trong xâu gốc:

    • Kiểm tra xem ký tự đó đã có trong tập/map theo dõi các ký tự đã in chưa.
    • Nếu chưa, in ký tự đó và tần suất của nó (lấy từ map đã tính ở bước 1), sau đó thêm ký tự đó vào tập/map theo dõi.
    • Nếu đã có rồi (nghĩa là nó là lần xuất hiện thứ 2 trở đi của ký tự đó trong xâu), thì bỏ qua.

Làm thêm nhiều bài tập miễn phí tại đây

Comments

There are no comments at the moment.