Hướng dẫn giải của 1.Chuẩn hoá xâu


Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
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ập

Tác giả: Hiếu Nguyễn, asenen_kiet, KhaNguyent123, binhcode8

Hiểu bài toán

Bài toán yêu cầu chuẩn hóa một xâu ký tự đầu vào theo các quy tắc sau:

  1. Loại bỏ các ký tự không phải là chữ cái hoặc dấu cách.
  2. Giữ lại duy nhất một dấu cách giữa các từ, loại bỏ các dấu cách thừa.
  3. Đảm bảo các từ trong câu bắt đầu bằng chữ cái in hoa (chữ cái đầu tiên của câu và chữ cái đầu tiên sau các dấu câu kết thúc như '.', '!', '?').
  4. Đảm bảo các dấu câu như ',', '.', ':', '!', '?' có đúng một khoảng trắng ở trước (trừ trường hợp là ký tự đầu tiên) và không có khoảng trắng ở sau (trừ trường hợp cuối câu).
  5. Các ký tự viết hoa/thường cần được xử lý: các từ thông thường viết thường, các từ đầu câu/in hoa viết hoa chữ cái đầu tiên.

Các cách tiếp cận

Cách Xử lý từng ký tự và kiểm tra điều kiện (State-based)
#include <iostream>
#include <string>
#include <cctype>
using namespace std;

bool is_end_punct(char c) {
    return c == '.' || c == '!' || c == '?' || c == ':';
}

bool is_punct(char c) {
    return is_end_punct(c) || c == ',' || c == ';';
}

string normalize(const string &s) {
    string res;
    bool need_cap = true;      // Cờ đánh dấu cần viết hoa chữ cái tiếp theo
    bool prev_space = false;   // Cờ đánh dấu đã có dấu cách trước đó
    bool is_start = true;      // Cờ đánh dấu là ký tự đầu tiên

    for (size_t i = 0; i < s.size(); i++) {
        char c = s[i];

        // Bỏ qua các ký tự không phải là chữ cái, dấu cách hoặc dấu câu
        if (!isalpha(c) && c != ' ' && !is_punct(c)) continue;

        if (c == ' ') {
            // Chỉ thêm dấu cách nếu chưa có dấu cách nào trước đó
            if (!prev_space && !is_start) {
                res += ' ';
                prev_space = true;
            }
            continue;
        }

        // Nếu gặp ký tự không phải dấu cách, reset cờ prev_space
        prev_space = false;

        if (isalpha(c)) {
            if (need_cap) {
                res += toupper(c);
                need_cap = false;
            } else {
                res += tolower(c);
            }
            is_start = false;
        } else if (is_punct(c)) {
            // Xử lý dấu câu
            // Bỏ dấu cách trước đó nếu có
            if (!res.empty() && res.back() == ' ') {
                res.pop_back();
            }
            res += c;
            is_start = false;

            // Nếu là dấu câu kết thúc câu, bật cờ cần viết hoa
            if (is_end_punct(c)) {
                need_cap = true;
            }
        }
    }

    // Xóa dấu cách cuối cùng nếu có
    if (!res.empty() && res.back() == ' ') res.pop_back();
    return res;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    // freopen("bai1.inp", "r", stdin);
    // freopen("bai1.out", "w", stdout);

    string s;
    getline(cin, s);
    cout << normalize(s) << endl;

    return 0;
}
  • Time Complexity: O(n)
  • Space Complexity: O(n)

Phương pháp này đi qua chuỗi đầu vào một lần duy nhất. Nó sử dụng các biến trạng thái (need_cap, prev_space) để theo dõi các yêu cầu chuẩn hóa hiện tại (viết hoa bắt đầu câu, loại bỏ dấu cách thừa).

  • Khi gặp ký tự thường: Kiểm tra xem có cần viết hoa không, xử lý và thêm vào kết quả.
  • Khi gặp dấu cách: Chỉ thêm vào nếu đây là dấu cách đầu tiên giữa các từ.
  • Khi gặp dấu câu: Xóa dấu cách thừa trước đó, thêm dấu câu, sau đó cập nhật trạng thái viết hoa nếu là dấu kết thúc câu.
  • Các ký tự không hợp lệ bị bỏ qua. Cách tiếp cận này khá trực quan và kiểm soát tốt luồng xử lý.
Cách Phân tách từ và xử lý Logic
#include <bits/stdc++.h>
using namespace std;

int main() {
    freopen ("bai1.inp" , "r" , stdin);
    freopen ("bai1.out" , "w" , stdout);

    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    string s;
    getline(cin, s);

    // Bước 1: Loại bỏ ký tự rác và chuẩn hóa dấu cách thô
    string t;
    for (int i = 0; i < s.size(); i++) {
        if (isalpha(s[i]) || s[i] == ' ') {
            // Giữ lại chữ cái và dấu cách, nhưng loại bỏ dấu cách thừa ngay lập tức
            if (s[i] == ' ' && (i == 0 || t.empty() || t.back() == ' ')) continue;
            t += s[i];
        } else if (ispunct(s[i])) {
            // Gom nhóm các dấu câu
            // Thêm một dấu cách đặc biệt để dễ xử lý sau này, hoặc thêm trực tiếp vào t
            // Ở cách này, ta sẽ thêm dấu câu và một dấu cách vào t (sẽ xử lý sau)
            if (!t.empty() && t.back() == ' ') t.pop_back();
            t += s[i];
            t += ' '; // Đánh dấu vùng chứa dấu câu
        }
    }

    // Bước 2: Tách các từ và xử lý hoa thường
    stringstream ss(t);
    string word;
    string result;
    bool need_cap = true;

    while (ss >> word) {
        // Kiểm tra xem word có phải là dấu câu đơn lẻ không
        if (word.size() == 1 && ispunct(word[0])) {
             result += word[0];
             if (word[0] == '.' || word[0] == '!' || word[0] == '?') {
                 need_cap = true;
             }
        } else {
            // Xử lý từ
            if (need_cap) {
                result += toupper(word[0]);
                result += word.substr(1);
                need_cap = false;
            } else {
                result += word;
            }
        }
        result += ' ';
    }

    // Xóa dấu cách cuối
    if (!result.empty() && result.back() == ' ') result.pop_back();
    cout << result;

    return 0;
}
  • Time Complexity: O(n)
  • Space Complexity: O(n)

Cách tiếp cận này chia vấn đề thành các bước:

  1. Dọn dẹp dữ liệu thô: loại bỏ ký tự rác, đảm bảo một dấu cách giữa các từ, và tách biệt các dấu câu.
  2. Sử dụng stringstream để phân tách chuỗi đã dọn dẹp thành các từ.
  3. Duyệt qua các từ đã tách, áp dụng quy tắc hoa/thư và thêm vào kết quả. Cách này tận dụng thư viện để phân tách từ, giúp việc xử lý từ trở nên dễ dàng hơn, nhưng đòi hỏi bước tiền xử lý kỹ lưỡng để stringstream hoạt động chính xác với các yêu cầu về dấu câu.
Cách Chuẩn hóa dựa trên các quy tắc thay thế (Regex - Tham khảo)
#include <iostream>
#include <string>
#include <regex>
using namespace std;

int main() {
    // Code tham khảo logic Regex
    string s;
    getline(cin, s);

    // 1. Loại bỏ ký tự không phải chữ cái, dấu cách, dấu câu
    // Regex: [^a-zA-Z .,:!?] thay bằng ""

    // 2. Chuẩn hóa dấu cách
    // Thay "  " bằng " "

    // 3. Xử lý dấu câu (không có dấu cách trước, 1 dấu cách sau trừ cuối)
    // Rất phức tạp với Regex thông thường

    // 4. Xử lý hoa/thư
    // Phải duyệt thủ công

    // Regex không phải là công cụ tối ưu cho bài này do độ phức tạp của các quy tắc
    cout << "Approach not recommended for full solution due to complexity." << endl;
    return 0;
}
  • Time Complexity: O(n)
  • Space Complexity: O(n)

Lưu ý: Regex thường được đề cập trong các bài xử lý xâu, nhưng đối với bài này có quá nhiều quy tắc đặc biệt (viết hoa đầu câu, xử lý dấu câu không có khoảng trắng sau...) mà Regex thực hiện rất cồng kềnh và khó bảo trì. Các giải pháp C++ chấp nhận thường là các giải pháp duyệt mảng ký tự.

Phân tích độ phức tạp

Cách tiếp cận Time Space Tên
1 O(n) O(n) Xử lý từng ký tự và kiểm tra điều kiện (State-based)
2 O(n) O(n) Phân tách từ và xử lý Logic
3 O(n) O(n) Chuẩn hóa dựa trên các quy tắc thay thế (Regex - Tham khảo)

Bài học kinh nghiệm

  • Cần xử lý từng ký tự (state machine) để kiểm soát chính xác việc thêm/xóa dấu cách và viết hoa.
  • Cờ trạng thái (flags) là chìa khóa: need_cap để quản lý viết hoa đầu câu/từ, prev_space để loại bỏ dấu cách thừa.
  • Luôn kiểm tra điều kiện biên (string rỗng, phần tử cuối, phần tử tiếp theo) trước khi thao tác.

Lỗi thường gặp

  • Để lại dấu cách thừa ở cuối câu hoặc sau các dấu câu.
  • Viết hoa sai vị trí: viết hoa các từ ở giữa câu (không phải sau dấu câu kết thúc).
  • Bỏ qua các ký tự đặc biệt hoặc xử lý sai thứ tự ưu tiên (ví dụ: xử lý dấu cách trước khi xử lý loại bỏ ký tự rác).
  • Quên xử lý trường hợp xâu rỗng hoặc chỉ chứa khoảng trắng.

Bình luận

Please read the guidelines before commenting.


Không có bình luận tại thời điểm này.