Tuyển sinh 🎓 Mình nhận đào tạo lập trình giải thuật thi đấu: thi vào lớp 10 chuyên tin · thi học sinh giỏi tin học cấp tỉnh/thành · thi vào đội tuyển quốc gia · Olympic tin học sinh viên / ICPC · phỏng vấn thuật toán. Bấm vào đây để xem thông tin khóa học → Xem khóa học
Thi thử miễn phí
🏆 Thi thử HSG Tin học tuần này trên ClueOJ
Mở công khai cho mọi người · đề do thầy Nguyễn Việt Nam Sơn biên soạn
Lịch thi, đề cũ & lời giải →

Bài 8: Xâu ký tự (chuỗi) trong C++ và Python: char, mã ASCII, string và các bài toán xử lý xâu

Bài 8 – Xâu ký tự (chuỗi) trong C++ và Python
Series: Nhập môn lập trình thuật toán Độ khó: Dễ Bài 8 / Kiến thức nền tảng Xâu ký tự
// Nhập môn lập trình · Bài 8
Xâu ký tự (chuỗi): char, bảng mã ASCII, string và các bài toán xử lý xâu kinh điển
Tên người, mật khẩu, tin nhắn, một số có 1000 chữ số… đều là xâu ký tự. Xử lý xâu xuất hiện trong gần như mọi đề thi, từ câu dễ nhất tới câu khó nhất.

Lời mở đầu

Bốn bài trước, chương trình của chúng ta chỉ làm việc với con số. Nhưng dữ liệu đời thực phần lớn là chữ: họ tên học sinh, địa chỉ email, một câu văn cần đếm từ, hay một số nguyên dài tới 1000 chữ số mà long long không chứa nổi. Kiểu dữ liệu dành cho chúng là xâu ký tự (string, hay còn gọi là chuỗi). Điều thú vị: xâu thực chất là mảng các ký tự, nên mọi kỹ năng duyệt mảng ở bài 6 đều áp dụng y nguyên. Học xong bài này là các bạn đã có đủ “đồ nghề” cơ bản; sáu bài tiếp theo (Bài 9 → Bài 14) sẽ giúp các bạn viết chương trình đủ nhanh và làm quen với đề thi thật.

Trong bài này bạn sẽ học: kiểu char và bảng mã ASCII (đổi chữ hoa – thường, đổi ký tự số sang số); kiểu string, cách nhập xâu bằng cin và getline; duyệt xâu, các hàm xử lý xâu có sẵn (đối chiếu C++ với Python); các bài toán kinh điển: đếm loại ký tự, xâu đối xứng, đếm từ. Có khung chạy tay xem từng ký tự và 10 bài tập kèm gợi ý.

Lộ trình bài viết, bấm vào từng phần để nhảy tới:

Phần 1Ký tự char và bảng mã ASCII

Kiểu char lưu một ký tự, viết trong cặp nháy đơn: char c = 'A';. Điều quan trọng nhất cần biết: máy tính không lưu chữ cái mà lưu một con số đại diện cho nó, theo bảng mã ASCII. Ví dụ 'A' được lưu là 65, 'a' là 97, chữ số '0' là 48. Vì vậy ký tự hoàn toàn có thể đem đi cộng, trừ, so sánh như số!

' '32 '0' .. '9'48 .. 57 'A' .. 'Z'65 .. 90 'a' .. 'z'97 .. 122 dấu cáchchữ sốchữ hoachữ thường Các chữ cái, chữ số nằm LIÊN TIẾP trong bảng mã, nên: '7' - '0' = 55 - 48 = 7 → đổi ký tự số sang con số 'a' - 'A' = 97 - 65 = 32 → chữ thường = chữ hoa + 32 'd' - 'a' = 3 → 'd' là chữ cái thứ 3 (đếm từ 0)

4 vùng mã ASCII cần thuộc lòng. Không cần nhớ con số cụ thể, chỉ cần nhớ chúng liên tiếp và viết '0', 'a', 'A' trong code.

Code C++ và Python
char c = 'A';
cout << (int)c << endl;          // 65: ép sang int để xem mã ASCII
cout << (char)(c + 1) << endl;   // B
char d = '7';
int so = d - '0';                // 7 (con số thật, đem đi tính toán được)
if(c >= 'A' && c <= 'Z') cout << "chu hoa";   // kiểm tra c là chữ hoa
char t = c - 'A' + 'a';          // đổi sang chữ thường: 'a'
c = 'A'                          # Python không có kiểu char, ký tự là xâu độ dài 1
print(ord(c))                    # 65: ord() lấy mã ASCII
print(chr(ord(c) + 1))           # B: chr() đổi mã sang ký tự
d = '7'
so = ord(d) - ord('0')           # 7 (hoặc đơn giản int(d))
if 'A' <= c <= 'Z': print("chu hoa")
t = chr(ord(c) - ord('A') + ord('a'))   # 'a' (hoặc c.lower())

Ứng dụng đầu tiên rất hay gặp: tính tổng chữ số của một số khổng lồ có tới hàng nghìn chữ số. Số này không kiểu số nguyên nào chứa nổi, nên ta đọc nó như một xâu rồi đổi từng ký tự sang chữ số bằng c - '0':

Code C++ và Python
#include <iostream>
#include <string>
using namespace std;

int main(){
    string s;
    cin >> s;
    int tong = 0;
    for(int i = 0; i < s.size(); i++){
        int cs = s[i] - '0';
        tong += cs;
    }
    cout << tong;
    return 0;
}
s = input().strip()
tong = 0
for c in s:
    cs = ord(c) - ord('0')            # hoặc int(c)
    tong += cs
print(tong)
C++▶ Chạy tay từng dòng codeTổng chữ số của một số rất lớn: đọc số như một xâu, mỗi ký tự s[i] đổi sang chữ số bằng s[i] - '0' (trừ mã ASCII).
Dữ liệu mẫu:
1#include <iostream>
2#include <string>
3using namespace std;
4
5int main(){
6 string s;
7 cin >> s;
8 int tong = 0;
9 for(int i = 0; i < s.size(); i++){
10 int cs = s[i] - '0';
11 tong += cs;
12 }
13 cout << tong;
14 return 0;
15}

Mô phỏng trực quan. Câu "ký tự chỉ là một con số" nghe thì dễ nhưng nhiều bạn vẫn chưa thấy nó có ích gì. Em gõ một xâu không dấu rồi bấm Tiếp: mỗi ký tự hiện kèm mã ASCII, xem máy dựa vào mã để phân loại và đổi hoa – thường chỉ bằng ±32.

MÔ PHỎNGMã ASCII: phân loại ký tự và đảo hoa – thườngNhập xâu tối đa 16 ký tự không dấu. Số nhỏ dưới mỗi ô là mã ASCII của ký tự đó.
ký tự đang xétđã xử lýký tự vừa ghi ra

Phần 2Kiểu string: khai báo, nhập xâu, duyệt xâu

C++ có 2 cách lưu xâu. Cách cũ kế thừa từ ngôn ngữ C là mảng ký tự char s[100];, kết thúc bằng ký tự đặc biệt '\0'; cách này rắc rối và dễ lỗi nên ta chỉ cần biết là có. Cách hiện đại, dùng trong mọi bài thi, là kiểu string (thư viện <string>): tự co giãn độ dài, nối bằng dấu +, so sánh bằng ==, < như số.

s X0i1n2␣3c4h5a6o7 string s = "Xin chao"; s.size() = 8, s[0] = 'X', s[3] = ' ' (dấu cách cũng là 1 ký tự), s[7] = 'o'.

Xâu là mảng ký tự: chỉ số từ 0 đến s.size() - 1, duyệt bằng vòng for y như mảng.

Nhập xâu có 2 cách và đây là chỗ rất nhiều bạn bị sai:

cin >> s: đọc một TỪ
string s;
cin >> s;
// gõ: Nguyen Van An
// s = "Nguyen"
Dừng lại ở dấu cách hoặc xuống dòng đầu tiên. Dùng khi xâu chắc chắn không có dấu cách.
getline(cin, s): đọc cả DÒNG
string s;
getline(cin, s);
// gõ: Nguyen Van An
// s = "Nguyen Van An"
Đọc tới hết dòng, giữ nguyên các dấu cách. Python: s = input() luôn đọc cả dòng.
Cái bẫy cin rồi getline
Đề cho dòng 1 là số $n$, dòng 2 là một câu. Nếu viết cin >> n; getline(cin, s); thì s sẽ là xâu rỗng! Lý do: cin >> n đọc xong số nhưng để lại ký tự xuống dòng '\n', getline gặp ngay ký tự đó và tưởng dòng đã hết. Cách sửa: thêm cin.ignore(); (bỏ qua 1 ký tự) ngay sau cin >> n;.

Ví dụ: đọc một dòng bất kỳ, đếm số chữ hoa, chữ thường, chữ số và ký tự khác (dấu cách, dấu câu…). Đây là bài kinh điển kết hợp duyệt xâu với kiểm tra vùng mã ASCII ở phần 1:

Code C++ và Python
#include <iostream>
#include <string>
using namespace std;

int main(){
    string s;
    getline(cin, s);
    int hoa = 0, thuong = 0, so = 0, khac = 0;
    for(int i = 0; i < s.size(); i++){
        char c = s[i];
        if(c >= 'A' && c <= 'Z') hoa++;
        else if(c >= 'a' && c <= 'z') thuong++;
        else if(c >= '0' && c <= '9') so++;
        else khac++;
    }
    cout << hoa << " " << thuong << " " << so << " " << khac;
    return 0;
}
s = input()
hoa = thuong = so = khac = 0
for c in s:
    if 'A' <= c <= 'Z':
        hoa += 1
    elif 'a' <= c <= 'z':
        thuong += 1
    elif '0' <= c <= '9':
        so += 1
    else:
        khac += 1
print(hoa, thuong, so, khac)
# Python có sẵn: c.isupper(), c.islower(), c.isdigit()
C++▶ Chạy tay từng dòng codeĐếm chữ hoa, chữ thường, chữ số và ký tự khác trong một dòng: mỗi ký tự rơi vào đúng 1 nhánh của chuỗi if – else if.
Dữ liệu mẫu:
1#include <iostream>
2#include <string>
3using namespace std;
4
5int main(){
6 string s;
7 getline(cin, s);
8 int hoa = 0, thuong = 0, so = 0, khac = 0;
9 for(int i = 0; i < s.size(); i++){
10 char c = s[i];
11 if(c >= 'A' && c <= 'Z') hoa++;
12 else if(c >= 'a' && c <= 'z') thuong++;
13 else if(c >= '0' && c <= '9') so++;
14 else khac++;
15 }
16 cout << hoa << " " << thuong << " " << so << " " << khac;
17 return 0;
18}

Một thao tác cơ bản khác: biến đổi từng ký tự của xâu. Ví dụ đảo chữ hoa thành thường, thường thành hoa, tự cài bằng phép cộng trừ mã ASCII (không dùng hàm có sẵn):

Code C++ và Python
#include <iostream>
#include <string>
using namespace std;

int main(){
    string s;
    cin >> s;
    for(int i = 0; i < s.size(); i++){
        if(s[i] >= 'a' && s[i] <= 'z')
            s[i] = s[i] - 'a' + 'A';
        else if(s[i] >= 'A' && s[i] <= 'Z')
            s[i] = s[i] - 'A' + 'a';
    }
    cout << s;
    return 0;
}
s = list(input().strip())        # xâu Python không sửa được từng ký tự → đổi sang list
for i in range(len(s)):
    if 'a' <= s[i] <= 'z':
        s[i] = chr(ord(s[i]) - ord('a') + ord('A'))
    elif 'A' <= s[i] <= 'Z':
        s[i] = chr(ord(s[i]) - ord('A') + ord('a'))
print("".join(s))
# Cách ngắn: print(input().swapcase())
C++▶ Chạy tay từng dòng codeĐảo hoa – thường: chữ thường thì trừ 'a' cộng 'A' (tức là trừ 32), chữ hoa thì ngược lại; ký tự khác giữ nguyên. Sửa trực tiếp s[i].
Dữ liệu mẫu:
1#include <iostream>
2#include <string>
3using namespace std;
4
5int main(){
6 string s;
7 cin >> s;
8 for(int i = 0; i < s.size(); i++){
9 if(s[i] >= 'a' && s[i] <= 'z')
10 s[i] = s[i] - 'a' + 'A';
11 else if(s[i] >= 'A' && s[i] <= 'Z')
12 s[i] = s[i] - 'A' + 'a';
13 }
14 cout << s;
15 return 0;
16}
Xâu trong Python là bất biến
Python không cho sửa trực tiếp s[i] = 'A' (báo lỗi TypeError). Muốn sửa từng ký tự thì đổi sang list: a = list(s), sửa xong ghép lại s = "".join(a); hoặc tạo xâu mới bằng phép cộng. Còn string của C++ thì sửa thoải mái.

Phần 3Các hàm xử lý xâu có sẵn (C++ và Python)

Tự cài thủ công giúp hiểu bản chất, nhưng khi làm bài ta tận dụng các hàm có sẵn cho nhanh. Bảng dưới đây ví dụ với s = "lap trinh". Chú ý: trong C++, substr nhận vị trí bắt đầu và độ dài, còn lát cắt Python s[a:b] nhận vị trí bắt đầu và vị trí kết thúc (không lấy $b$).

Việc cần làmC++ (string)Python (str)Kết quả
Độ dàis.size()len(s)9
Nối xâus + "!"s + "!""lap trinh!"
Lấy xâu cons.substr(4, 3)s[4:7]"tri"
Tìm xâu cons.find("trinh")s.find("trinh")4
Không tìm thấys.find("x") == string::nposs.find("x") == -1true
Chèns.insert(3, "!")s[:3] + "!" + s[3:]"lap! trinh"
Xóas.erase(0, 4)s[4:]"trinh"
Thay thếs.replace(0, 3, "hoc")s.replace("lap", "hoc")"hoc trinh"
Đảo ngượcreverse(s.begin(), s.end())s[::-1]"hnirt pal"
Số → xâuto_string(123)str(123)"123"
Xâu → sốstoi("123"), stoll(...)int("123")123
So sánh từ điển"abc" < "abd""abc" < "abd"true

Lưu ý s.insert, s.erase, s.replace của C++ sửa trực tiếp xâu s; còn Python luôn tạo ra xâu mới (vì xâu Python bất biến), muốn giữ kết quả phải gán lại s = s.replace(...). Chạy thử đoạn code tổng hợp sau và đối chiếu với bảng:

Code C++ và Python
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int main(){
    string s = "lap trinh";
    cout << s.size() << endl;            // 9
    cout << s.substr(4, 3) << endl;      // tri
    cout << s.find("trinh") << endl;     // 4
    if(s.find("x") == string::npos) cout << "khong co x" << endl;
    s.insert(3, "!");                    // s = "lap! trinh"
    s.erase(3, 1);                       // xoá 1 ký tự tại vị trí 3: "lap trinh"
    s.replace(0, 3, "hoc");              // s = "hoc trinh"
    cout << s << endl;
    string t = s;
    reverse(t.begin(), t.end());         // t = "hnirt coh"
    sort(t.begin(), t.end());            // sắp xếp các ký tự: " choihnrt"
    int x = stoi("2026") + 1;            // 2027
    cout << to_string(x) + "!" << endl;  // 2027!
    return 0;
}
s = "lap trinh"
print(len(s))                    # 9
print(s[4:7])                    # tri
print(s.find("trinh"))           # 4
if s.find("x") == -1: print("khong co x")
s = s[:3] + "!" + s[3:]          # "lap! trinh"
s = s[:3] + s[4:]                # "lap trinh"
s = s.replace("lap", "hoc")      # "hoc trinh"
print(s)
t = s[::-1]                      # "hnirt coh"
t = "".join(sorted(t))           # " choihnrt"
x = int("2026") + 1              # 2027
print(str(x) + "!")              # 2027!
# Python còn: s.split(), " ".join(...), s.upper(), s.lower(), s.count("a")
Xâu và chữ tiếng Việt có dấu
Kiểu string của C++ thực chất lưu từng byte. Chữ có dấu như "ă", "ệ" trong bảng mã UTF-8 chiếm 2 – 3 byte, nên s.size() của "Việt" là 6 chứ không phải 4, và s[1] chỉ là một mảnh của chữ "i". Vì thế đề thi tin học hầu như luôn cho xâu không dấu. Python 3 thì xử lý được chữ có dấu đúng như mong đợi: len("Việt") == 4.

Phần 4Bài toán kinh điển: xâu đối xứng, đếm từ

Xâu đối xứng (palindrome) là xâu đọc xuôi hay ngược đều giống nhau: "level", "abba", "racecar". Cách kiểm tra đẹp nhất là dùng hai con trỏ: $l$ ở đầu xâu, $r$ ở cuối xâu, so sánh $s[l]$ với $s[r]$ rồi cùng tiến vào giữa. Chỉ cần một cặp khác nhau là kết luận không đối xứng.

Code C++ và Python
#include <iostream>
#include <string>
using namespace std;

bool doiXung(string s){
    int l = 0, r = s.size() - 1;
    while(l < r){
        if(s[l] != s[r]) return false;
        l++; r--;
    }
    return true;
}

int main(){
    string s;
    cin >> s;
    if(doiXung(s)) cout << "YES";
    else cout << "NO";
    return 0;
}
def doi_xung(s):
    l, r = 0, len(s) - 1
    while l < r:
        if s[l] != s[r]:
            return False
        l += 1
        r -= 1
    return True

s = input().strip()
print("YES" if doi_xung(s) else "NO")
# Cách ngắn: print("YES" if s == s[::-1] else "NO")
C++▶ Chạy tay từng dòng codeKiểm tra xâu đối xứng bằng 2 con trỏ: l từ đầu, r từ cuối, so sánh s[l] và s[r] rồi cùng tiến vào giữa.
Dữ liệu mẫu:
1#include <iostream>
2#include <string>
3using namespace std;
4
5bool doiXung(string s){
6 int l = 0, r = s.size() - 1;
7 while(l < r){
8 if(s[l] != s[r]) return false;
9 l++; r--;
10 }
11 return true;
12}
13
14int main(){
15 string s;
16 cin >> s;
17 if(doiXung(s)) cout << "YES";
18 else cout << "NO";
19 return 0;
20}

Đếm số từ trong một câu, câu có thể có nhiều dấu cách liền nhau hoặc dấu cách ở đầu, cuối. Mẹo: một từ mới bắt đầu tại vị trí $i$ khi $s[i]$ không phải dấu cách và (hoặc $i = 0$, hoặc $s[i-1]$ là dấu cách). Đếm số vị trí bắt đầu là ra số từ, không cần tách từ ra.

Code C++ và Python
#include <iostream>
#include <string>
using namespace std;

int main(){
    string s;
    getline(cin, s);
    int dem = 0;
    for(int i = 0; i < s.size(); i++){
        if(s[i] != ' ' && (i == 0 || s[i - 1] == ' '))
            dem++;
    }
    cout << dem;
    return 0;
}
s = input()
dem = 0
for i in range(len(s)):
    if s[i] != ' ' and (i == 0 or s[i - 1] == ' '):
        dem += 1
print(dem)
# Cách ngắn: print(len(s.split()))   (split() tự bỏ mọi dấu cách thừa)
C++▶ Chạy tay từng dòng codeĐếm từ: vị trí i là chữ cái đầu tiên của một từ khi s[i] khác dấu cách và ký tự đứng trước là dấu cách (hoặc i = 0).
Dữ liệu mẫu:
1#include <iostream>
2#include <string>
3using namespace std;
4
5int main(){
6 string s;
7 getline(cin, s);
8 int dem = 0;
9 for(int i = 0; i < s.size(); i++){
10 if(s[i] != ' ' && (i == 0 || s[i - 1] == ' '))
11 dem++;
12 }
13 cout << dem;
14 return 0;
15}
Tách từ bằng stringstream
Muốn lấy từng từ ra để xử lý (tìm từ dài nhất, viết hoa chữ cái đầu…), C++ có cách rất gọn: #include <sstream> rồi stringstream ss(s); string w; while(ss >> w) { ... }, mỗi vòng w là một từ, mọi dấu cách thừa tự bị bỏ qua. Python tương ứng là for w in s.split():.

Mô phỏng trực quan. Đọc code hai con trỏ rồi mà vẫn khó hình dung i, j chạy thế nào? Em bấm Tiếp để xem hai con trỏ đi từ hai đầu vào giữa, thử cả xâu độ dài chẵn, lẻ và một xâu không đối xứng để thấy vòng lặp dừng sớm ra sao.

MÔ PHỎNGKiểm tra xâu đối xứng bằng hai con trỏNhập xâu tối đa 20 ký tự. Con trỏ i (trái) và j (phải) đi dần vào giữa.
cặp đang so sánhcặp đã khớpcặp lệch

Đếm từ cũng là chỗ rất hay sai khi xâu có dấu cách thừa. Em xem máy nhận ra "chỗ bắt đầu một từ" như thế nào, rồi tự gõ thêm dấu cách ở đầu, ở cuối, ở giữa để thử:

MÔ PHỎNGĐếm từ: chỉ đếm những chỗ một từ bắt đầuNhập xâu tối đa 30 ký tự, có thể có nhiều dấu cách liền nhau (hiện là ␣).
chữ cái đầu của một từđang xétthuộc một từdấu cách đã qua

Bài tậpLuyện xử lý xâu

Nếu không nói gì thêm, xâu chỉ gồm chữ cái tiếng Anh không dấu, chữ số và dấu cách, độ dài không quá $10^5$. Bài nào đọc cả dòng thì nhớ dùng getline.

Bài 1Đếm nguyên âmDễ

Đọc một dòng, đếm số nguyên âm (a, e, i, o, u, kể cả chữ hoa).

Input
Hoc Lap Trinh Cung Thay
Output
5
💡 Gợi ý
Đổi từng ký tự về chữ thường (dùng tolower(c) hoặc cộng 32 như phần 2) rồi so với 5 nguyên âm. Có thể dùng string("aeiou").find(c) != string::npos cho gọn.
Xem code tham khảo (C++ / Python)
#include <iostream>
#include <string>
using namespace std;

int main(){
    string s;
    getline(cin, s);
    int dem = 0;
    for(char c : s){
        c = tolower(c);
        if(c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') dem++;
    }
    cout << dem;
    return 0;
}
s = input()
print(sum(1 for c in s.lower() if c in "aeiou"))
Bài 2Đảo ngược xâuDễ

Đọc một dòng, in xâu theo thứ tự ngược lại. Yêu cầu tự đảo bằng vòng lặp (không dùng reverse).

Input
tin hoc 10
Output
01 coh nit
💡 Gợi ý
Giống hệt bài đảo mảng tại chỗ ở bài 6: đổi chỗ s[i] với s[n - 1 - i] cho $i$ từ 0 tới $n/2 - 1$. Hoặc đơn giản in ra từ cuối về đầu.
Xem code tham khảo (C++ / Python)
#include <iostream>
#include <string>
using namespace std;

int main(){
    string s;
    getline(cin, s);
    int n = s.size();
    for(int i = 0; i < n / 2; i++){
        char t = s[i];
        s[i] = s[n - 1 - i];
        s[n - 1 - i] = t;
    }
    cout << s;
    return 0;
}
s = input()
print(s[::-1])
Bài 3Tổng các số trong xâuDễ

Xâu gồm chữ cái và chữ số lẫn lộn (không có dấu cách). Tính tổng các chữ số xuất hiện trong xâu.

Input
a1b22c333
Output
14
💡 Gợi ý
Duyệt xâu, ký tự nào nằm trong khoảng '0' tới '9' thì cộng c - '0' vào tổng. Nâng cao: tính tổng các số (a1b22c333 → 1 + 22 + 333 = 356) bằng cách ghép dần so = so * 10 + (c - '0'), gặp chữ cái thì cộng so vào tổng rồi đặt lại bằng 0.
Xem code tham khảo (C++ / Python)
#include <iostream>
#include <string>
using namespace std;

int main(){
    string s;
    cin >> s;
    int tong = 0;
    for(char c : s){
        if(c >= '0' && c <= '9') tong += c - '0';
    }
    cout << tong;
    return 0;
}
s = input().strip()
print(sum(int(c) for c in s if c.isdigit()))
Bài 4Chuẩn hoá họ tênTrung bình

Đọc một dòng là họ tên viết lộn xộn: thừa dấu cách ở đầu, cuối, giữa các từ, chữ hoa thường lẫn lộn. In họ tên đã chuẩn hoá: các từ cách nhau đúng 1 dấu cách, chữ cái đầu mỗi từ viết hoa, các chữ còn lại viết thường.

Input
   nGUYEN   vAN    an  
Output
Nguyen Van An
💡 Gợi ý
Tách từng từ bằng stringstream (mẹo ở phần 4). Với mỗi từ w: w[0] = toupper(w[0]), các ký tự còn lại tolower. Ghép các từ, giữa 2 từ thêm đúng một dấu cách. Đây là bài "đặc sản" của các đề thi tin học trẻ, HSG cấp huyện.
Xem code tham khảo (C++ / Python)
#include <iostream>
#include <string>
#include <sstream>
using namespace std;

int main(){
    string s, w, kq = "";
    getline(cin, s);
    stringstream ss(s);
    while(ss >> w){
        w[0] = toupper(w[0]);
        for(int i = 1; i < w.size(); i++) w[i] = tolower(w[i]);
        if(kq != "") kq += " ";
        kq += w;
    }
    cout << kq;
    return 0;
}
s = input()
print(" ".join(w[0].upper() + w[1:].lower() for w in s.split()))
Bài 5Tần suất chữ cáiTrung bình

Đọc một dòng. Với mỗi chữ cái (không phân biệt hoa thường) có xuất hiện, in ra chữ cái đó (viết thường) và số lần xuất hiện, theo thứ tự a → z.

Input
Hello World
Output
d 1
e 1
h 1
l 3
o 2
r 1
w 1
💡 Gợi ý
Dùng mảng đếm 26 phần tử như bài 6: chữ c (đã đổi về thường) ứng với ô dem[c - 'a']. Cuối cùng duyệt k từ 0 tới 25, ô nào khác 0 thì in (char)('a' + k) và dem[k].
Xem code tham khảo (C++ / Python)
#include <iostream>
#include <string>
using namespace std;

int dem[26];

int main(){
    string s;
    getline(cin, s);
    for(char c : s){
        c = tolower(c);
        if(c >= 'a' && c <= 'z') dem[c - 'a']++;
    }
    for(int k = 0; k < 26; k++){
        if(dem[k] > 0) cout << (char)('a' + k) << " " << dem[k] << endl;
    }
    return 0;
}
s = input().lower()
dem = [0] * 26
for c in s:
    if 'a' <= c <= 'z':
        dem[ord(c) - ord('a')] += 1
for k in range(26):
    if dem[k] > 0:
        print(chr(ord('a') + k), dem[k])
Bài 6Từ dài nhấtTrung bình

Đọc một câu, in ra từ dài nhất (nếu nhiều từ cùng độ dài lớn nhất thì in từ xuất hiện đầu tiên) và độ dài của nó.

Input
toi thich hoc lap trinh thuat toan
Output
thich 5
💡 Gợi ý
Tách từ bằng stringstream, giữ biến best; từ nào có w.size() > best.size() thì cập nhật (dấu > để giữ từ đầu tiên khi bằng nhau). Chính là bài tìm max ở bài 6, chỉ khác là so sánh độ dài.
Xem code tham khảo (C++ / Python)
#include <iostream>
#include <string>
#include <sstream>
using namespace std;

int main(){
    string s, w, best = "";
    getline(cin, s);
    stringstream ss(s);
    while(ss >> w){
        if(w.size() > best.size()) best = w;
    }
    cout << best << " " << best.size();
    return 0;
}
s = input()
best = ""
for w in s.split():
    if len(w) > len(best):
        best = w
print(best, len(best))
Bài 7Nén xâuTrung bình

Xâu chỉ gồm chữ thường. Nén xâu bằng cách thay mỗi đoạn ký tự giống nhau liên tiếp bằng ký tự đó kèm số lần lặp.

Input
aaabccdddd
Output
a3b1c2d4
💡 Gợi ý
Duyệt xâu với biến đếm dem. Nếu s[i] == s[i - 1] thì dem++, ngược lại in ký tự trước cùng dem rồi đặt lại dem = 1. Đừng quên in đoạn cuối cùng sau vòng lặp! Ghép số vào xâu dùng to_string(dem).
Xem code tham khảo (C++ / Python)
#include <iostream>
#include <string>
using namespace std;

int main(){
    string s, kq = "";
    cin >> s;
    int dem = 1;
    for(int i = 1; i <= s.size(); i++){
        if(i < s.size() && s[i] == s[i - 1]) dem++;
        else{
            kq += s[i - 1] + to_string(dem);
            dem = 1;
        }
    }
    cout << kq;
    return 0;
}
s = input().strip()
kq, dem = "", 1
for i in range(1, len(s) + 1):
    if i < len(s) and s[i] == s[i - 1]:
        dem += 1
    else:
        kq += s[i - 1] + str(dem)
        dem = 1
print(kq)
Bài 8Hai xâu đảo chữTrung bình

Hai xâu gọi là đảo chữ (anagram) của nhau nếu sắp xếp lại các ký tự của xâu này thì được xâu kia (ví dụ listen và silent). Nhập 2 xâu chữ thường, in YES hoặc NO.

Input
listen silent
Output
YES
💡 Gợi ý
Cách 1: sắp xếp các ký tự của cả hai xâu (sort(s.begin(), s.end())) rồi so sánh bằng ==, độ phức tạp $O(n \log n)$. Cách 2 nhanh hơn: mảng đếm 26 ô, xâu thứ nhất cộng vào, xâu thứ hai trừ ra, cuối cùng mọi ô bằng 0 thì là đảo chữ, $O(n)$.
Xem code tham khảo (C++ / Python)
#include <iostream>
#include <string>
using namespace std;

int dem[26];

int main(){
    string s, t;
    cin >> s >> t;
    for(char c : s) dem[c - 'a']++;
    for(char c : t) dem[c - 'a']--;
    bool ok = true;
    for(int k = 0; k < 26; k++)
        if(dem[k] != 0) ok = false;
    cout << (ok ? "YES" : "NO");
    return 0;
}
s, t = input().split()
print("YES" if sorted(s) == sorted(t) else "NO")
Bài 9Đếm xâu conTrung bình

Dòng 1 là xâu $s$, dòng 2 là xâu $t$ (không chứa dấu cách, $|t| \le |s| \le 1000$). Đếm số lần $t$ xuất hiện trong $s$ (các lần xuất hiện được phép chồng lên nhau).

Input
aaaa
aa
Output
3
💡 Gợi ý
Thử mọi vị trí bắt đầu $i$ từ 0 tới $|s| - |t|$, kiểm tra s.substr(i, t.size()) == t. Hoặc dùng find trong vòng lặp: pos = s.find(t, pos + 1) cho tới khi gặp npos. Chú ý: s.count(t) của Python đếm các lần không chồng nhau ("aaaa".count("aa") = 2), nên không dùng được cho bài này.
Xem code tham khảo (C++ / Python)
#include <iostream>
#include <string>
using namespace std;

int main(){
    string s, t;
    cin >> s >> t;
    int dem = 0;
    for(int i = 0; i + t.size() <= s.size(); i++){
        if(s.substr(i, t.size()) == t) dem++;
    }
    cout << dem;
    return 0;
}
s = input().strip()
t = input().strip()
print(sum(1 for i in range(len(s) - len(t) + 1) if s[i:i + len(t)] == t))
Bài 10Cộng hai số lớnKhá

Nhập 2 số nguyên không âm $a, b$, mỗi số có tới 1000 chữ số. In ra $a + b$.

Input
99999999999999999999 1
Output
100000000000000000000
💡 Gợi ý
Lưu 2 số dưới dạng xâu và làm y như cộng tay ở tiểu học: đi từ chữ số cuối lên đầu, cộng từng cặp chữ số cùng biến nhớ nho, chữ số kết quả là tong % 10, nhớ sang cột sau tong / 10. Đầu ngắn hơn coi như chữ số 0. Kết quả thu được bị ngược, nhớ đảo lại. Python có số nguyên vô hạn nên chỉ cần print(a + b), nhưng hãy thử tự cài để luyện tư duy. Đây là nền móng của chủ đề Số lớn.
Xem code tham khảo (C++ / Python)
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

string cong(string a, string b){
    string kq = "";
    int i = a.size() - 1, j = b.size() - 1, nho = 0;
    while(i >= 0 || j >= 0 || nho > 0){
        int x = (i >= 0) ? a[i] - '0' : 0;
        int y = (j >= 0) ? b[j] - '0' : 0;
        int tong = x + y + nho;
        kq += (char)('0' + tong % 10);
        nho = tong / 10;
        i--; j--;
    }
    reverse(kq.begin(), kq.end());
    return kq;
}

int main(){
    string a, b;
    cin >> a >> b;
    cout << cong(a, b);
    return 0;
}
def cong(a, b):
    kq = []
    i, j, nho = len(a) - 1, len(b) - 1, 0
    while i >= 0 or j >= 0 or nho > 0:
        x = int(a[i]) if i >= 0 else 0
        y = int(b[j]) if j >= 0 else 0
        tong = x + y + nho
        kq.append(str(tong % 10))
        nho = tong // 10
        i -= 1
        j -= 1
    return "".join(reversed(kq))

a, b = input().split()
print(cong(a, b))      # hoặc đơn giản: print(int(a) + int(b))

Học tiếp gì sau bài xâu ký tự?

Chúc mừng các bạn đã đi hết 8 bài đầu của series! Với các viên gạch: nhập xuất, rẽ nhánh, vòng lặp, hàm, mảng 1 chiều, mảng 2 chiều và xâu, các bạn đã viết được phần lớn chương trình cơ bản. Sáu bài tiếp theo giúp các bạn đi từ “viết được” sang “viết đủ nhanh để được AC”: độ phức tạp và cách bài được chấm → struct và sắp xếp nhiều tiêu chí → STL cơ bản → số học → mảng đánh dấu, cộng dồn → tổng ôn và đề thi thử. Học xong Bài 14, các bạn đi tiếp theo lộ trình học thuật toán.

Rất vui khi anh em nào đã đọc đến những dòng cuối cùng này. Anh em đọc chắc cũng mỏi mắt luôn chứ 😅 vậy anh em nghĩ người viết ra hết đống ở trên còn thế nào nữa 🤪. Vậy nên đừng tiếc gì cho mình một lời bình luận hoặc chia sẻ bài viết để động viên mình nè. Chúc anh em học tốt và gặt hái nhiều thành công 😇
Bản quyền thuộc về Thầy Nguyễn Việt Nam Sơn. Bài viết, lời giải, hình minh hoạ và mã nguồn trên trang là công sức biên soạn của thầy qua nhiều năm giảng dạy. Vui lòng không sao chép, đăng lại hay sử dụng dưới mọi hình thức khi chưa được sự đồng ý của tác giả. Nếu thấy hữu ích, hãy chia sẻ bằng đường link bài gốc nhé. Cảm ơn bạn!
Tự học thuật toán: khó ở đâu?
Đọc xong một bài giải là bước đầu. Phần lớn học sinh tự học sẽ gặp những trở ngại sau:
  • Không biết học gì trước, học gì sau, học lan man nên kiến thức bị hổng.
  • Bí bài không ai gỡ, ngồi cả buổi vẫn không ra rồi bỏ cuộc.
  • Code chạy được nhưng không đủ điểm, không biết sai ở đâu, chậm ở đâu.
  • Không có đề thi thử và người chấm, nên không biết mình đang ở mức nào.
  • Dễ nản giữa chừng khi không có ai theo sát.
Học cùng thầy Nam Sơn, các trở ngại đó được gỡ bỏ:
  • Lộ trình 111 buổi video sắp sẵn từ dòng code đầu tiên đến đề HSG thật của các tỉnh.
  • Thầy trực tiếp gỡ bí, chữa bài cho từng em qua nhóm Zalo riêng.
  • Hệ thống chấm bài tự động 24/7 và thi thử HSG hằng tuần để biết chính xác mình đang ở đâu.
  • Đóng học phí một lần, thầy kèm lâu dài: từ thi chuyên Tin đến HSG quốc gia và cả khi lên đại học.

Nhận xét