Hướng dẫn giải của 1.Chuẩn hoá xâu
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ậpTác giả: , , ,
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:
- Loại bỏ các ký tự không phải là chữ cái hoặc dấu cách.
- 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.
- Đả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ư '.', '!', '?').
- Đả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).
- 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:
- 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.
- Sử dụng
stringstreamđể phân tách chuỗi đã dọn dẹp thành các từ. - 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 để
stringstreamhoạ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