算法刷题总结——字符串处理(一)

文章列举了7道来自AcWing在线编程平台的题目,涉及字符串处理、算法应用和数据解析。题目包括计算带千位分隔符的加法、数字转换为英文单词、签到签出管理、密码修正和性别人群分数差异等,展示了C++在解决问题中的应用。
摘要由CSDN通过智能技术生成

题目1:A+B格式(选自AcWing 1473)

题目描述:

计算 a+b并以标准格式输出总和----也就是说,从最低位开始每隔三位数加进一个逗号(千位分隔符),如果结果少于四位则不需添加。

输入格式

共一行,包含两个整数 a和 b。

输出格式

共一行,以标准格式输出 a+b的和。

数据范围

−10^6 < a, b < 10^6

输入样例:

-1000000   9

输出样例:

-999,991

代码:

#include<iostream>
using namespace std;

int main(){
    int a, b;
    cin >> a >> b;
    int c = a + b;
    string num = to_string(c);
    string res;
    for(int i = num.size() - 1, j = 0;i >= 0;i --)
    {
        res = num[i] + res;
        j ++;
        if(j % 3 == 0 && i && num[i-1] != '-')
        {
            res = ',' + res;
        }
    }
    cout << res << endl;
    return 0;
}

思路:

模拟即可,先求和再解析字符串

注意点:

①整数转string直接调用to_string()函数即可

②字符串拼接时是从后往前拼接,故应为res = ',' + res 而不是res += ','

题目2: 拼写正确(选自AcWing 1477)

题目描述:

给定一个非负整数 N,你的任务是计算 N的所有数字的总和,并以英语输出总和的每个数字。

输入格式

共一行,包含一个整数 N。

输出格式

共一行,用英语输出总和的每个数字,单词之间用空格隔开。

数据范围

0 ≤ N ≤ 10^100

输入样例:

12345

输出样例:

one five

代码:

#include<iostream>
using namespace std;

int main(){
    string n;
    cin >> n;
    int sum = 0;
    for(auto c : n){
        sum += c - '0';
    }
    string res = to_string(sum);
    char word[10][10] = {
        "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"
    };  //存放单词数组
    cout << word[res[0] - '0'];
    for(int i = 1; i < res.size(); i++){
        cout << ' ' << word[res[i] - '0'];
    }
    return 0;
}

思路:

模拟,同问题1,先求和再解析字符串

注意点:

采用二维数组存放单词,输出时数字的偏移量即对应二维数组内对应单词的下标,避免复杂的if条件语句的堆叠,代码更加美观

题目3:签到与签出(选自AcWing 1478)

题目描述:

每天第一个到机房的人负责开门,最后一个从机房离开的人负责锁门。

现在,给定每个人的签到与签出记录,请你找出当天开门的人以及锁门的人分别是谁。

输入格式

第一行包含整数 M,表示共有 M 个人的签到签出记录。

接下来 M行,每行的形式如下:

ID_number Sign_in_time Sign_out_time

时间以 HH:MM:SS 形式给出,ID_number 是一个长度不超过 1515 的字符串。

输出格式

共一行,输出开门人和锁门人的ID_number,用一个空格隔开。

数据范围

1 ≤ M ≤ 10,
数据保证每个人的签到时间早于签出时间,并且不会出现两个人同时签到或同时签出的情况。

输入样例:

3
CS301111 15:30:28 17:00:10
SC3021234 08:00:00 11:25:25
CS301133 21:45:00 21:58:40

​

输出样例:

SC3021234 CS301133

代码:

#include<iostream>
using namespace std;

int main(){
    string open_id, open_time;
    string close_id, close_time;
    int m;
    cin >> m;
    for(int i = 0; i < m; i++){
        string id, in_time, out_time;
        cin >> id >> in_time >> out_time;
        if(i == 0 || in_time < open_time){
            open_id = id;
            open_time = in_time;
        }
        if(i == 0 || out_time > close_time){
            close_id = id;
            close_time = out_time;
        }
    }
    cout << open_id << ' '<< close_id;
    return 0;
}

思路:

维护最早开门的人的id和time以及最晚锁门的人的id和time,遍历比较模拟即可

注意点:

比较时间时,由于时间的位数一致,比较时间可直接比较字符串的字典序(即直接进行字符串大小比较即可),仅适用于字符串位数一致的情况

题目:4:密码(选自AcWing 1519)

题目描述:

为了准备 PAT,系统不得不为用户生成随机密码。

但是有时一些数字和字母之间总是难以区分,比如 1(数字一)和 l(L 的小写),0(数字零)和 O(o 的大写)。

一种解决办法是将 1(数字一)替换为 @,将 0(数字零)替换为 %,将 l(L的小写)替换为 L,将 O(o的大写)替换为 o

现在,你的任务就是帮助系统检查这些用户的密码,并对难以区分的部分加以修改。

输入格式

第一行包含一个整数 N,表示用户数量。

接下来 N 行,每行包含一个用户名和一个密码,都是长度不超过 1010 且不含空格的字符串。

输出格式

首先输出一个整数 M,表示已修改的用户密码数量。

接下来 M行,每行输出一个用户名称和其修改后的密码。

用户的输出顺序和读入顺序必须相同。

如果没有用户的密码被修改,则输出 There are N accounts and no account is modified,其中 N是用户总数。

如果 N=1,则应该输出 There is 1 account and no account is modified

数据范围

1≤N≤1000

输入样例1:

3
Team000002 Rlsp0dfa
Team000003 perfectpwd
Team000001 R1spOdfa

输出样例1:

2
Team000002 RLsp%dfa
Team000001 R@spodfa

输入样例2:

1
team110 abcdefg332

输出样例2:

There is 1 account and no account is modified

输入样例3:

2
team110 abcdefg222
team220 abcdefg333

输出样例3:

​There are 2 accounts and no account is modified

​

代码:

#include <iostream>
using namespace std;

const int N = 1010;
string name[N], pwd[N];

//修改密码函数
string change(string str){
    string res;
    for(auto c : str){
        if (c == '1') res += '@';
        else if (c == '0') res += '%';
        else if (c == 'l') res += 'L';
        else if (c == 'O') res += 'o';
        else res += c;
    }
    return res;
}

int main(){
    int n;
    int m = 0;  //记录修改密码的用户个数
    cin >> n;  //输入用户的个数
    for(int i = 0; i < n; i ++){
        string cur_id, cur_pwd;
        cin >> cur_id >> cur_pwd;
        string res_pwd = change(cur_pwd);
        if (res_pwd != cur_pwd){
            name[m] = cur_id;
            pwd[m] = res_pwd;
            m++;
        }
    }
    //特判
    if(m == 0){
        if(n == 1)  puts("There is 1 account and no account is modified");
        else{
            printf("There are %d accounts and no account is modified", n);
        }
    }
    else{
        cout << m << endl;
        for(int i = 0; i < m; i++){
            cout << name[i] << ' ' << pwd[i] << endl; 
        }
    }
}

思路:

模拟

注意点:

注意临界情况

题目5:男孩女孩(选自AcWing 1520)

题目描述:

给定 N个学生的成绩信息,请你求出女生第一名与男生倒数第一名的分数差距。

输入格式

第一行输入整数 N,表示学生数量。

接下来 N行,每行包含一个学生的姓名,性别,ID和成绩。其中姓名和ID是长度不超过 1010 且不包含空格的字符串。性别为 F(女)或 M(男)。成绩是一个范围在 [0,100][0,100] 的整数。保证所有学生的成绩互不相同。

输出格式

输出共三行。

第一行输出女生第一名的姓名和ID。

第二行输出男生倒数第一名的姓名和ID。

第三行输出女生第一名的成绩减去男生倒数第一名的成绩的差。

如果不存在某个性别的学生,则在对应行输出 Absent

在第三行输出 NA

数据范围

1≤N≤101

输入样例1:

3
Joe M Math990112 89
Mike M CS991301 100
Mary F EE990830 95

输出样例1:

Mary EE990830
Joe Math990112
6

输入样例2:

1
Jean M AA980920 60

输出样例2:

​Absent
Jean AA980920
NA

​

代码:

#include <iostream>
using namespace std;

int main(){
    string girl_name, girl_id;
    int girl_score;   //第一名女同学的姓名,id,得分
    string boy_name, boy_id;
    int boy_score; //最后一名男同学的姓名,id,得分
    
    int n;
    cin >> n;  //总人数
    for (int i = 0; i < n; i++) {
        string name, sex, id;
        int score;
        cin >> name >> sex >> id >> score;
        if (sex == "F"){   //输入信息为女同学
            if (girl_name.empty() || score > girl_score) {  //更新第一名女同学信息
                girl_name = name;
                girl_id = id;
                girl_score = score;
            }   
        }
        else {   //输入信息为男同学
            if (boy_name.empty() || score < boy_score) { //更新最后一名男同学信息
                boy_name = name;
                boy_id = id;
                boy_score = score;
            }
        }
    }
    
    if (girl_name.empty()) cout << "Absent" << endl;
    else cout << girl_name << ' ' << girl_id << endl;;
    
    if (boy_name.empty()) cout << "Absent" << endl;
    else cout << boy_name << ' ' << boy_id << endl;
    
    if (girl_name.size() && boy_name.size()) cout << girl_score - boy_score;
    else cout << "NA";
    return 0;
}

题目6:字符串减法(选自AcWing 1534)

题目描述:

给定两个字符串 S1和 S2,S=S1−S2 定义为将 S1 中包含的所有在 S2中出现过的字符删除后得到的字符串。

你的任务就是计算 S1−S2。

输入格式

共两行,第一行包含字符串 S1,第二行包含字符串 S2。

输出格式

输出共一行,表示 S1−S2的结果。

数据范围

两个给定字符串的长度都不超过 10^4。

输入样例:

They are students.
aeiou

输出样例:

Thy r stdnts.

代码:

#include <iostream>
#include <unordered_set>

using namespace std;

int main() {
    string s1, s2;
    getline (cin, s1);   //输入的字符串可能含有空格,用getline输入
    getline (cin, s2); 
    
    unordered_set <char> hash;  //定义哈希表,unordered_set只存储元素的值(不以键值对存),且不重复
    for (auto c : s2) hash.insert(c);
    
    string res;
    for (auto a : s1) {
        if (hash.count(a) == 0) {   //未在s2中出现,加入结果
            res += a;
        }
    }
    cout << res;
    return 0;
}

思路:

如果直接对两个字符串进行遍历,则时间复杂度为O(n²),极有可能超时,判断一个字符串是否有字符在第二个字符串中出现,可采用哈希表的思想,哈希表增删查改的时间复杂度均为O(1),提高了程序的运行效率。

补充知识点:

①输入问题

本题输入的字符串可能含有字符串,直接用cin>>输入可能会报错,可采用getline(cin, str)方式输入

②STL中set和map小结

(1)

基于平衡树实现: O(logn)

有序

set<int> a;

map<int, int> b;

multiset<int> c;

multimap<int,int> d;

区别,multiset和multimap允许key重复,而set和map不允许重复元素

(2)

基于哈希表实现:O(1)

无序

unordered_set <int> e;
unordered_map <int, int> f;
unordered_multiset <int> g;
unordered_multimap <int, int> h;

区别同上

题目7:说话方式(选自AcWing 1557)

题目描述:

不同的人对描述同一种事物的同义词的偏爱程度可能不同。

例如,在说警察时,有人喜欢用 the police,有人喜欢用 the cops

分析说话方式有助于确定说话者的身份,这在验证诸如和你线上聊天的是否是同一个人十分有用。

现在,给定一段从某人讲话中提取的文字,你能确定他的最常用词吗?

输入格式

输入共一行,包含一个字符串,以回车符 \n 终止。

输出格式

共一行,输出最常用词以及其出现次数。

如果常用词有多个,则输出字典序最小的那个单词。

注意,单词在输出时,必须全部小写。

单词是指由连续的字母和数字构成的,被非字母数字字符或行首/行尾分隔开的,连续序列。

单词不区分大小写。

数据范围

输入字符串长度不超过 1048576,且至少包含一个大小写字母或数字。

输入样例:

Can1: "Can a can can a can?  It can!"

输出样例:

can 5

代码:

#include <iostream>
#include <ctype.h>
#include <unordered_map>

using namespace std;

//判断一个字符是否在一个单词内
bool check_word (char c) {
    if (c > '0' && c < '9') return true;
    if (c > 'a' && c > 'z') return true;
    if (c > 'A' && c < 'Z') return true;
    return false;
}

int main() {
    string str;
    getline (cin, str);
    unordered_map <string, int> hash;  //存放单词及其次数
    //双指针遍历寻找每个单词
    for (int i = 0; i < str.size(); i ++) {
        string res;
        for (int j = i; j < str.size(); j ++) {
            if (check_word(j)) res += tolower(str[j]);   //若有大写字母,则转小写
            else break;   //遍历到分隔符,跳出循环,当前单词遍历结束
        }
        i = j;
        hash[res] += 1;
    }
    //遍历哈希表,寻找出现次数最多的单词
    string word;
    int cnt = -1;
    for (auto item : hash) {
        if (item.second > cnt || (item.second == cnt && item.first < word)) {
            word = item.first;
            cnt = item.second;
        }
    }
    cout << word << ' ' << cnt;
    return 0;
}

思路:

本题需要从一个字符串中寻找每个单独的单词,将其存入哈希表,遍历字符串寻找单独单词的过程采用双指针可以提高效率。

双指针的核心思想如下:

外层指针i遍历整个字符串,内层指针j从当前i的位置开始向后遍历,遍历到单词的末尾结束,存储当前单词,并且将i指针移到j指针位置即可

注意:

大写字母转小写字母的两个方法:

①直接调用tolower()函数,头文件为ctype.h

②a的ASCII码为97,A的ASCII码为65,相差32,故可直接通过字符串+32的方式进行转换

总结:

字符串问题主要用模拟的思想,核心实质上是一个遍历的过程,要合理利用C++ STL的一些容器简化代码,以及掌握常见的降低时间复杂度的方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值