Hướng dẫn giải của sắp xếp 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, thanhbtm1, Nguyenhoang150908, vantienngo39

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

Please read the guidelines before commenting.


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