目录
PAT甲级字符串相关题目题解
PAT1001(知识点:C++中to_string()
的用法)
#include <iostream>
using namespace std;
int main()
{
int x, y;
cin >> x >> y;
string sum_str = to_string(x + y);
string res = "";
for (int i = sum_str.size() - 1, j = 0; i >= 0; i--) // j用来存储当前已经遍历了几位
{
res = sum_str[i] + res;
j++;
if (j % 3 == 0 && i != 0 && sum_str[i - 1] != '-')
{
res = ',' + res;
}
}
cout << res;
}
PAT1005(知识点:for-each遍历、字符ASCII码、数据长度估算)
知识点:for-each遍历、字符ASCII码、数据长度估算
-
c - '0’可以得到字符对应的int值,如果直接输出c,输出的是这个数字字符的ASCII码值,易错点。
-
题目输入范围是 1 0 100 10^{100} 10100,而int的大小通常是32位,范围是 − 2 31 -2^{31} −231到 2 31 − 1 2^{31}-1 231−1,这里要估算一下 1 0 100 10^{100} 10100有没有超过int的范围,最后决定使用string存储输入值,而不是int(因为会越界)
python代码计算:
import math
# 计算2的31次方
power_of_two = 2 ** 31
# 转换为10的次方
power_of_ten = math.log10(power_of_two)
power_of_ten
要证明 (2^{31}) 约等于 (10^{9.33}) 的推理正确性,我们可以使用对数的换底公式来进行计算。换底公式是:
log b a = log c a log c b \log_b a = \frac{\log_c a}{\log_c b} logba=logcblogca
其中,(a) 和 (b) 是大于0的实数,(c) 是正实数且 (c \neq 1)。我们想要计算的是 (2^{31}) 用 (10) 的次方来表示,即求 (10) 的多少次方等于 (2^{31})。
根据对数的定义,我们可以将 (2^{31}) 表示为 (10) 的对数:
1 0 x = 2 31 10^x = 2^{31} 10x=231
取对数得:
x = log 10 ( 2 31 ) x = \log_{10} (2^{31}) x=log10(231)
使用换底公式,将底数从 (10) 改为 (2)(因为 (2) 和 (10) 的对数我们更容易理解和计算):
x = log 2 ( 2 31 ) log 2 ( 10 ) x = \frac{\log_2 (2^{31})}{\log_2 (10)} x=log2(10)log2(231)
由于 (\log_2 (2^{31}) = 31)(因为 (2) 的 (31) 次方等于 (2) 的 (31) 次方),我们可以简化 (x) 的表达式为:
x = 31 log 2 ( 10 ) x = \frac{31}{\log_2 (10)} x=log2(10)31
我们知道 (\log_2 (10)) 大约等于 (3.32193),所以:
x ≈ 31 3.32193 x \approx \frac{31}{3.32193} x≈3.3219331
计算 (x) 的值:
x ≈ 9.33 x \approx 9.33 x≈9.33
因此,我们可以得出结论,(2^{31}) 约等于 (10^{9.33}),证明了推理的正确性。
#include <iostream>
using namespace std;
int main()
{
string num;
cin >> num;
int current_num = 0;
for (char c : num)
{
current_num += c - '0';
}
string eng_nums[] = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"};
string current_num_str = to_string(current_num);
cout << eng_nums[current_num_str[0] - '0'];
for (int i = 1; i < current_num_str.size(); i++)
{
cout << " " << eng_nums[current_num_str[i] - '0'];
}
return 0;
}
PAT1006(知识点:对于长度和格式相同的时间字符串,直接字典序比较(>
、<
)即可)
#include <iostream>
using namespace std;
int main()
{
int m; // total number of records
cin >> m;
string id, in_time, out_time;
string open_id, open_time, close_id, close_time;
for (int i = 0; i < m; i++)
{
cin >> id >> in_time >> out_time;
// find open time
if (i == 0 || in_time < open_time)
{
open_time = in_time;
open_id = id;
}
// find close time
if (i == 0 || out_time > close_time)
{
close_time = out_time;
close_id = id;
}
}
cout << open_id << " " << close_id;
return 0;
}
PAT1035
基础代码
复习puts()
#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;
cin >> n;
int m = 0;
for (int i = 0; i < n; i ++ )
{
string cur_name, cur_pwd;
cin >> cur_name >> cur_pwd;
string changed_pwd = change(cur_pwd);
if (changed_pwd != cur_pwd)
{
name[m] = cur_name;
pwd[m] = changed_pwd;
m ++ ;
}
}
if (!m)
{
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", n);
}
else
{
cout << m << endl;
for (int i = 0; i < m; i ++ ) cout << name[i] << ' ' << pwd[i] << endl;
}
return 0;
}
使用STL
优化:
可以省略 make_pair
并直接使用 emplace_back
的原因在于 emplace_back
函数的工作方式。emplace_back
是C++11引入的一个方法,它直接在容器的末尾就地构造一个元素,而不是先构造一个临时对象然后复制或移动到容器中。这意味着你可以直接传递构造函数的参数给 emplace_back
,它会使用这些参数来构造容器中的新元素。
对于 std::pair
,其构造函数可以接受两个参数——分别对应于 pair
的 first
和 second
成员变量的值。因此,当你使用 emplace_back
向一个存储 std::pair
对象的 std::vector
中添加元素时,可以直接传递这两个参数给 emplace_back
,而无需显式创建一个 std::pair
对象。这样做不仅简化了代码,还提高了效率,因为它避免了创建临时 pair
对象和随后的复制或移动操作。
在原始代码中:
records.emplace_back(make_pair(id, password));
使用 make_pair
创建了一个 pair
对象,然后传递给 emplace_back
。而简化后的代码:
modifiedRecords.emplace_back(id, password);
直接将 id
和 password
作为参数传递给 emplace_back
,它会利用这两个参数就地构造一个 std::pair
对象,避免了额外的构造和复制或移动开销。
这种优化是基于 emplace_back
的设计理念,即直接在容器内部构造元素,减少不必要的临时对象的创建和销毁,从而提高代码的执行效率。
#include <iostream>
#include <vector>
using namespace std;
// 1 to @, 0 to %, l to L, O to o
bool change_password(string &password) // 注意这里要是引用类型,才能更改到传入的原始string
{
bool changed_flag = false;
for (char &c : password)
{
switch (c)
{
case '1':
c = '@';
changed_flag = true;
break;
case '0':
c = '%';
changed_flag = true;
break;
case 'l':
c = 'L';
changed_flag = true;
break;
case 'O':
c = 'o';
changed_flag = true;
break;
}
}
return changed_flag;
}
int main()
{
int n;
cin >> n;
vector<pair<string, string>> records;
int m = 0; // be modified account
for (int i = 0; i < n; i++)
{
string id, password;
cin >> id >> password;
if (change_password(password)) // save changed accounts
{
records.emplace_back(id, password);
m++;
}
}
if (records.empty())
{
if (n == 1)
{
cout << "There is 1 account and no account is modified";
}
else
{
cout << "There are " << n << " accounts and no account is modified";
}
}
else
{
cout << m;
for (auto record : records)
{
cout << "\n"
<< record.first << " " << record.second;
}
}
return 0;
}
PAT1036
这一题自己做的时候有一个测试用例一直通过不了,这里暂存代码,以后寻找哪里有问题
#include <iostream>
using namespace std;
int main()
{
// lowest grade of male highest grade of female
int n;
cin >> n;
string low_m_name, high_f_name, low_m_id, high_f_id;
int low_grade = 2000, high_grade = -100;
bool exist_M, exist_F = false;
for (int i = 0; i < n; i++)
{
string name, sex, id;
int grade;
cin >> name >> sex >> id >> grade;
if (sex == "M" && grade < low_grade) // find lowest
{
exist_M = true;
low_m_name = name;
low_m_id = id;
low_grade = grade;
}
else if (sex == "F" && grade > high_grade) // find highest
{
exist_F = true;
high_f_name = name;
high_f_id = id;
high_grade = grade;
}
}
if (!exist_F || !exist_M) // 缺失任意性别
{
cout << "Absent\n";
if (exist_F)
{
cout << high_f_name << " " << high_f_id;
}
else
{
cout << low_m_name << " " << low_m_id;
}
cout << "\nNA";
}
else
{
cout << high_f_name << " " << high_f_id << "\n";
cout << low_m_name << " " << low_m_id << "\n";
cout << to_string(high_grade - low_grade);
}
return 0;
}
可以通过测试样例的答案,主要更正了输出部分,注意这部分的逻辑不能只用else不考虑各种可能的情况,一定要严谨,要与题目描述的流程和逻辑一致。
#include <iostream>
using namespace std;
int main()
{
// lowest grade of male highest grade of female
int n;
cin >> n;
string low_m_name, high_f_name, low_m_id, high_f_id;
int low_grade = 101, high_grade = -1;
bool exist_M = false, exist_F = false;
for (int i = 0; i < n; i++)
{
string name, sex, id;
int grade;
cin >> name >> sex >> id >> grade;
if (sex == "M" && grade < low_grade) // find lowest
{
exist_M = true;
low_m_name = name;
low_m_id = id;
low_grade = grade;
}
else if (sex == "F" && grade > high_grade) // find highest
{
exist_F = true;
high_f_name = name;
high_f_id = id;
high_grade = grade;
}
}
if (!exist_F) {
cout << "Absent\n";
} else {
cout << high_f_name << " " << high_f_id << "\n";
}
if (!exist_M) {
cout << "Absent\n";
} else {
cout << low_m_name << " " << low_m_id << "\n";
}
if (!exist_F || !exist_M) { // 如果缺少任意一种性别的学生
cout << "NA";
} else {
cout << high_grade - low_grade;
}
return 0;
}
PAT1050(unordered_set
、count()
、遍历时不能删除)
#include <iostream>
#include <unordered_set>
using namespace std;
int main()
{
string s1, s2;
getline(cin, s1);
getline(cin, s2);
// 判断某元素是否在某集合中出现过用hash
unordered_set<char> hash_set;
for (char c : s2) // 将s2中的每个元素存入hash_set
{
hash_set.insert(c);
}
string res = "";
for (char c : s1)
{
if (!hash_set.count(c)) // 遍历s1,没有在s2中出现过的元素添加到res中
{
res += c;
}
}
cout << res;
return 0;
}
unordered_set中方法count和find的区别
在C++中,std::unordered_set
是基于哈希表的集合容器,它提供了快速的元素查找、插入和删除操作。std::unordered_set
提供了 count
和 find
两个用于检查元素是否存在的成员函数,它们的主要区别在于它们的返回值和使用场景:
count
方法
- 原型:
size_type count(const Key& key) const;
- 功能:返回集合中等于
key
的元素的数量。对于unordered_set
,这个值只能是0
或1
,因为unordered_set
不允许重复的元素。 - 返回值:
1
表示集合中存在该元素,0
表示不存在。 - 使用场景:当你只需要知道某个元素是否存在于集合中时,可以使用
count
。由于unordered_set
中的元素唯一,count
方法实际上用来判断元素是否存在。
find
方法
- 原型:
iterator find(const Key& key) const;
- 功能:搜索集合中等于
key
的元素,并返回指向该元素的迭代器。如果找不到这样的元素,find
方法将返回一个指向集合末尾 (end()
) 的迭代器。 - 返回值:指向找到的元素的迭代器;如果未找到,则返回
end()
迭代器。 - 使用场景:当你不仅需要知道某个元素是否存在,而且还可能需要访问该元素时,应该使用
find
。通过检查find
的返回值是否等于end()
,可以判断元素是否存在,并且可以直接通过迭代器操作找到的元素。
总结:
- 使用
count
当你只需要判断元素是否存在,不需要访问元素本身。 - 使用
find
当你需要判断元素是否存在,并且在存在的情况下需要直接访问该元素。
find
方法比 count
更灵活,因为它提供了对找到的元素的直接访问,而 count
更适合于简单的存在性检查。在性能方面,两者在 unordered_set
中通常都是非常快的,因为底层的哈希表使得查找操作的时间复杂度接近于常数时间。
字符串中删除某个元素
在C++中,如果你想删除字符串中某个特定位置的字符,可以使用std::string
类的erase
成员函数。erase
函数有几个重载版本,但是为了删除特定位置的字符,你可以使用以下两种方式之一:
1. 删除指定位置的单个字符
你可以指定要删除的字符的位置(索引),使用erase
函数的这个版本:
string& erase(size_t pos = 0, size_t len = npos);
这里,pos
是要删除字符的位置(索引从0开始),len
是要删除的字符数量。如果你只想删除一个字符,可以将len
参数省略或设置为1。
示例代码:
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello, World!";
// 删除位置为6的字符(即空格)
str.erase(6, 1); // 注意:索引从0开始计数
cout << str << endl; // 输出: Hello,World!
return 0;
}
2. 删除由迭代器指定的单个字符
如果你更倾向于使用迭代器,erase
还有一个重载版本,接受一个指向要删除字符的迭代器:
iterator erase(iterator position);
示例代码:
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello, World!";
// 删除位置为6的字符(即空格),使用迭代器
str.erase(str.begin() + 6);
cout << str << endl; // 输出: Hello,World!
return 0;
}
在这两个示例中,我们都删除了字符串"Hello, World!"
中位置为6的字符(空格),结果是"Hello,World!"
。选择哪种方式取决于你的具体需求和个人偏好。
不能在遍历字符串的时候删除元素
在C++中,如果想在遍历字符串 s1 的同时删除在 s2 中出现过的字符,不能直接在遍历过程中修改 s1,因为这可能会导致迭代器失效或跳过某些字符。相反,一种常见的做法是创建一个新的字符串,只包含那些未出现在 s2 中的字符。
PAT1071(lexicographically)
lexicographically:按字典排序地