Hướng dẫn giải của sắp xếp 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
Cho một xâu S chỉ chứa các ký tự số và chữ cái thường. Yêu cầu sắp xếp tất cả các số nguyên xuất hiện trong S (theo thứ tự tăng dần) và thay thế chúng vào đúng vị trí cũ trong xâu. Ví dụ: S = 'abc123de45f01' thì các số là 123, 45, 01 (tương ứng 123, 45, 1). Sau khi sắp xếp (1, 45, 123), xâu kết quả là 'abc1de45f123'.
Các cách tiếp cận
Cách Tách chuỗi và so sánh trực tiếp
#include <bits/stdc++.h>
using namespace std;
// So sánh theo độ dài trước, sau đó theo thứ tự từ điển
bool sschuoi(string a, string b) {
if (a.size() > b.size()) return 0;
else if (a.size() < b.size()) return 1;
return a < b;
}
int main() {
freopen("bai2.inp", "r", stdin);
freopen("bai2.out", "w", stdout);
string s; cin >> s;
vector<string> v;
// Tách các số từ xâu S
for (int i = 0; i < s.size(); i++) {
if (s[i] >= '0' && s[i] <= '9') {
string k = "";
while (i < s.size() && s[i] >= '0' && s[i] <= '9') {
k += s[i];
i++;
}
v.push_back(k);
}
}
// Sắp xếp các số đã tách
sort(v.begin(), v.end(), sschuoi);
// Xây dựng kết quả
int idx = 0;
string kq = "";
for (int i = 0; i < s.size(); i++) {
if (s[i] >= '0' && s[i] <= '9') {
kq += v[idx];
idx++;
while (i + 1 < s.size() && s[i + 1] >= '0' && s[i + 1] <= '9') i++;
} else {
kq += s[i];
}
}
cout << kq;
}
- Time Complexity: O(N log N)
- Space Complexity: O(N)
Phương pháp này chia làm 3 bước: 1) Duyệt xâu S để tách ra các chuỗi số (ví dụ '012' -> "012"). 2) Sắp xếp các chuỗi số này bằng hàm so sánh tùy chỉnh: độ dài ngắn hơn thì nhỏ hơn, nếu bằng độ dài thì so sánh theo thứ tự từ điển. 3) Duyệt lại xâu S, thay thế các block số bằng các số đã sắp xếp trong danh sách. Hàm so sánh này đúng với yêu cầu của đề bài là so sánh số dựa trên độ dài và giá trị.
Cách Chuẩn hóa số trước khi so sánh
#include <bits/stdc++.h>
using namespace std;
// Chuẩn hóa số (bớt số 0 ở đầu) trước khi so sánh
bool cmp(const string &a, const string &b) {
string sa = a, sb = b;
// Xóa các số 0 ở đầu
sa.erase(0, sa.find_first_not_of('0'));
sb.erase(0, sb.find_first_not_of('0'));
// Nếu rỗng thì coi là "0"
if (sa.empty()) sa = "0";
if (sb.empty()) sb = "0";
if (sa.size() != sb.size()) return sa.size() < sb.size();
return sa < sb;
}
int main() {
freopen("bai2.inp", "r", stdin);
freopen("bai2.out", "w", stdout);
string S;
getline(cin, S);
vector<string> numbers;
int i = 0;
while (i < (int)S.size()) {
if (isdigit(S[i])) {
string numStr;
while (i < (int)S.size() && isdigit(S[i])) {
numStr += S[i];
i++;
}
numbers.push_back(numStr);
} else i++;
}
sort(numbers.begin(), numbers.end(), cmp);
string result = "";
int numIdx = 0;
for (int i = 0; i < S.size(); i++) {
if (isdigit(S[i])) {
result += numbers[numIdx++];
while (i + 1 < S.size() && isdigit(S[i + 1])) i++;
} else {
result += S[i];
}
}
cout << result;
}
- Time Complexity: O(N log N)
- Space Complexity: O(N)
Cách tiếp cận này tương tự cách 1 nhưng hàm so sánh khác biệt hơn. Nó loại bỏ các số 0 vô nghĩa ở đầu trước khi so sánh giá trị thực của số. Ví dụ "001" được chuẩn hóa thành "1". Điều này đảm bảo việc so sánh số học chính xác tuyệt đối dù các chuỗi đầu vào có nhiều số 0.
Cách Đánh dấu vị trí bằng ký tự đặc biệt
#include <bits/stdc++.h>
using namespace std;
bool cmp(const string &a, const string &b) {
if (a.size() != b.size()) return a.size() < b.size();
return a < b;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
ifstream fin("bai2.inp");
ofstream fout("bai2.out");
string S;
fin >> S;
vector<string> nums;
string temp = "";
string result = "";
// Tách số và xây dựng chuỗi kết quả có ký tự thay thế
for (char c : S) {
if (isdigit(c)) {
temp += c;
} else {
if (!temp.empty()) {
// Chuẩn hóa số trước khi lưu (xóa số 0 đầu)
temp.erase(0, temp.find_first_not_of('0'));
if (temp == "") temp = "0";
nums.push_back(temp);
temp.clear();
result += '@'; // Dấu @ đại diện cho vị trí của số
}
result += c;
}
}
// Xử lý số cuối cùng nếu có
if (!temp.empty()) {
temp.erase(0, temp.find_first_not_of('0'));
if (temp == "") temp = "0";
nums.push_back(temp);
result += '@';
}
// Sắp xếp các số
sort(nums.begin(), nums.end(), cmp);
// Thay thế các ký tự @ bằng số đã sắp xếp
string finalResult = "";
int numIdx = 0;
for (char c : result) {
if (c == '@') {
finalResult += nums[numIdx++];
} else {
finalResult += c;
}
}
fout << finalResult;
}
- Time Complexity: O(N log N)
- Space Complexity: O(N)
Phương pháp này dùng kỹ thuật 'dấu vết'. Trong khi duyệt xâu S để tách số, ta vừa chuẩn hóa số (xóa 0 đầu), vừa xây dựng một xâu trung gian (result) trong đó các block số được thay bằng ký tự đặc biệt '@'. Sau khi sắp xếp các số đã tách, ta chỉ việc chạy lại xâu trung gian và thay thế lần lượt các ký tự '@' bằng các số đã sắp xếp. Cách này giúp việc tái tạo xâu kết quả dễ dàng và logic hơn.
Phân tích độ phức tạp
| Cách tiếp cận | Time | Space | Tên |
|---|---|---|---|
| 1 | O(N log N) | O(N) | Tách chuỗi và so sánh trực tiếp |
| 2 | O(N log N) | O(N) | Chuẩn hóa số trước khi so sánh |
| 3 | O(N log N) | O(N) | Đánh dấu vị trí bằng ký tự đặc biệt |
Bài học kinh nghiệm
- Vấn đề cốt lõi là tách các token số và sắp xếp chúng, sau đó gắn lại vào vị trí cũ.
- Việc so sánh hai số dưới dạng chuỗi yêu cầu so sánh độ dài trước (số nào có nhiều chữ số hơn thì lớn hơn), nếu bằng độ dài thì so sánh từ điển.
- Xử lý số 0 ở đầu: '001' có giá trị bằng '1', nhưng độ dài chuỗi '001' lớn hơn '1'. Tùy vào cách giải thích đề bài, có thể cần chuẩn hóa (bỏ 0 đầu) trước khi so sánh.
Lỗi thường gặp
- Quên xử lý các số nằm sát nhau hoặc ở đầu/cuối xâu.
- So sánh trực tiếp theo thứ tự từ điển ('10' < '2') thay vì so sánh theo giá trị số học.
- Không xử lý đúng các số '0' đầu vào (ví dụ '00' hoặc '001').
Bình luận