每个常用字符都对应一个-128~127的数字 ,二者之间可以相互转化: (int)97 , (char)a
常用ASCII值:’A’-‘Z’ 是65~90,’a’-‘z’是97-122,’0’-‘9’是 48-57
字符可以参与运算,运算时会将其当做整数
字符数组:
字符串就是字符数组加上结束符’\0’
可以使用字符串来初始化字符数组,但此时要注意,每个字符串结尾会暗含一个’\0’字符,因此字符数组的长度至少要比字符串的长度多1!
给定一行长度不超过 100 的非空字符串,请你求出它的具体长度。
输入格式
输入一行,表示一个字符串。注意字符串中可能包含空格。
输出格式
输出一个整数,表示它的长度。
数据范围
1≤字符串长度≤100
字符串末尾无回车
输入样例:
I love Beijing.
输出样例:
15
字符串就用cin和cout【string与scanf读入类型不匹配CE】
C++ 代码(常用):getline(cin,str)读取一行
#include
#include
using namespace std;
int main()
{
string str;
getline(cin,str);
cout << str.size() << endl;
return 0;
}
cin.get()
#include
#include
using namespace std;
const int N = 105;
char str[N];
int main()
{
cin.get(str, N);//需要注意cin.get()不会把换行符取出删除,影响下一次读入!
//cin.getline(str,N);//需要注意cin.getline()会把换行符取出删除,不影响下一次读入!
cout << strlen(str) << endl;
return 0;
}
//C语言代码
// #include
// #include
// int main()
// {
// char s[105];
// gets(s); //c++17, 总之之后被禁用了
// printf("%d",strlen(s));
// return 0;
// }
输入一行字符,长度不超过 100,请你统计一下其中的数字字符的个数。
输入格式
输入一行字符。注意其中可能包含空格。
输出格式
输出一个整数,表示字数字字符的个数。
输入样例:
I am 18 years old this year.
输出样例:
2
纯c++11
#include
using namespace std;
int cnt;
int main()
{
string word;
while(cin >> word) //或者getline(cin,str); 空格不是数字不影响
for(auto x: word) cnt += isdigit(x);
cout << cnt;
}
朴素版
#include
using namespace std;
int main()
{
string a;
getline(cin,a);
int ans = 0;
for(int i = 0; i < a.size(); i ++)
{ //if(a[i] <= '9' && a[i] >= '0')
if(isdigit(a[i]))
{
ans ++;
}
}
cout << ans << endl;
return 0;
}
scanf一次读取版
scanf不能接收空格符,遇到空格就结束读入,
不能像C语言的gets()函数读取一行字符串(包括空格), 但scanf使用%[^\n]可以读取一行,直到碰到’\n’才结束读入
#include
#include
using namespace std;
const int N = 105;
char str[N];
int res;
int main()
{
scanf("%[^\n]",str); //只读取某些字符 [^\n]即过滤'\n' : 只读入非'\n'
int len = strlen(str);
for(int i = 0;i < len; i++)res += isdigit(str[i]); //是数字返回true 即1
printf("%d", res);
return 0;
}
scanf分次读取版
isdigit(s[i])函数 :判断第i位是否为数字 ,是返回true
#include
#include
using namespace std; //scanf("%s",s)!=EOF , 判断数字函数 isdigit
const int N = 105;
char word[N];
int cnt;
int main()
{
while(scanf("%s", word) != EOF) //每个单词判断
{
int len = strlen(word);
for(int i = 0;i < strlen(word); i++) cnt += isdigit(word[i]);
}
printf("%d", cnt);
return 0;
}
ch单个字符读取版
单字符char ch不断取位判断 ,if(ch==EOF)break; 结束
#include
#include
using namespace std;
int res;
char ch;
int main()
{
while(ch = getchar())
if(ch == EOF) break;
else res += isdigit(ch);
cout << res;
}
L1y佬的补充
#include
using namespace std;
int main(){
string s;
getline(cin,s);
cout<<count_if(s.begin(),s.end(),[&](char c) { return isdigit(c); });
}
给定两个长度相同的字符串 a 和字符串 b。
如果在某个位置 i 上,满足字符串 a 上的字符 a[i] 和字符串 b 上的字符 b[i] 相同,那么这个位置上的字符就是匹配的。
如果两个字符串的匹配位置的数量与字符串总长度的比值大于或等于 k,则称两个字符串是匹配的。
现在请你判断给定的两个字符串是否匹配。
输入格式
第一行包含一个浮点数 k,第二行包含字符串 a,第三行包含字符串 b。
输入的字符串中不包含空格。
输出格式
如果两个字符串匹配,则输出 yes。
否则,输出 no。
数据范围
0≤k≤1,
字符串的长度不超过 100。
输入样例:
0.4
abcde
xbacd
输出样例:
no
如果两个长度相同字符串的匹配位置的数量与字符串总长度的比值大于或等于 k,则称两个字符串是匹配的
#include
using namespace std;
double k, cnt;
string s1, s2;
int main()
{
cin >> k >> s1 >> s2;
for(int i = 0;i < s1.size(); i++) if(s1[i] == s2[i]) cnt++; //统计相同字符个数
if(cnt / s1.size() >= k) puts("yes"); //相同部分所长比值是否超过k
else puts("no");
}
循环相克令是一个两人玩的小游戏。
令词为“猎人、狗熊、枪”,两人同时说出令词,同时做出一个动作——猎人的动作是双手叉腰;狗熊的动作是双手搭在胸前;枪的动作是双手举起呈手枪状。
双方以此动作判定输赢,猎人赢枪、枪赢狗熊、狗熊赢猎人,动作相同则视为平局。
现在给定你一系列的动作组合,请你判断游戏结果。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
接下来 T 行,每行包含两个字符串,表示一局游戏中两人做出的动作,字符串为 Hunter, Bear, Gun 中的一个,这三个单词分别代表猎人,狗熊和枪。
输出格式
如果第一个玩家赢了,则输出 Player1。
如果第二个玩家赢了,则输出 Player2。
如果平局,则输出 Tie。
数据范围
1≤N≤100
输入样例
3
Hunter Gun
Bear Bear
Hunter Bear
输出样例
Player1
Tie
Player2
unordered_map
#include
#include
using namespace std;
int n;
string s1, s2;
int main()
{ //写全局会和库函数中的hash冲突: 解决法:可以写heap 或者map(映射)
unordered_map<int , string> hash; //映射的下一个(取模) 等于s2则 s1 < s2, 先判断是不是相等 ,
hash[0] = "Hunter", hash[1] = "Bear", hash[2] = "Gun"; //在取模3的值域映射下标:关系: 0H < 1S < 2G , 2 < 0
cin >> n;
while (n -- )
{
cin >> s1 >> s2;
int a, b;//Player1和Player2的映射值
for(int i = 0; i < 3; i++)
{
if(hash[i] == s1) a = i;
if(hash[i] == s2) b = i;
}
if(a == b) puts("Tie");
else if((a + 1) % 3 == b) puts("Player2"); //(a + 1) % 3 == b : 判断Player1下一位是否为Player2, 否则Player1
else puts("Player1");
}
return 0;
}
闫式封装函数-映射
#include
using namespace std;
int get(string s) //映射值
{//转换成数字 ,再依据规则 x == y ,(x + 1) % 3 == y【关键】
if (s == "Hunter") return 0;
if (s == "Bear") return 1;
return 2;
}
int main()
{
int n;
cin >> n;
while (n -- )
{
string a, b;
cin >> a >> b;
int x = get(a), y = get(b);
if (x == y) puts("Tie");
else if ((x + 1) % 3 == y) puts("Player2"); //关键点 - 逻辑
else puts("Player1"); ;
}
return 0;
}
old_code-朴素
观察长度不同
#include
using namespace std;
int main()
{
int n;
cin >> n;
string x, y;
while (n --)
{
cin >> x >> y; //string ,scanf不能直接读
int a = x.size(), b = y.size();
if (a - b == -1 || a - b == -2 || a - b == 3) //观察长度不同
cout << "Player1" << endl;
else if (a == b)
cout << "Tie" << endl;
else cout << "Player2" << endl;
}
return 0;
}
朴素枚举胜利情况
#include
using namespace std;
int main (){
int t;
cin>>t;
while(t--){
string a,b; //string ---cin
cin>>a>>b;
if(a==b)puts("Tie");
else if(a=="Hunter"&&b=="Gun"||a=="Bear"&&b=="Hunter"||a=="Gun"&&b=="Bear")
puts("Player1");
else puts("Player2");
}
return 0;
}
给定一个字符串 a,请你按照下面的要求输出字符串 b。
给定字符串 a 的第一个字符的 ASCII 值加第二个字符的 ASCII 值,得到 b 的第一个字符;
给定字符串 a 的第二个字符的 ASCII 值加第三个字符的 ASCII 值,得到 b 的第二个字符;
…
给定字符串 a 的倒数第二个字符的 ASCII 值加最后一个字符的 ASCII 值,得到 b 的倒数第二个字符;
给定字符串 a 的最后一个字符的 ASCII 值加第一个字符的 ASCII 值,得到 b 的最后一个字符。
输入格式
输入共一行,包含字符串 a。注意字符串中可能包含空格。
数据保证字符串内的字符的 ASCII 值均不超过 63。
输出格式
输出共一行,包含字符串 b。
数据范围
2≤a的长度≤100
输入样例:
1 2 3
输出样例:
QRRSd
模拟
#include
using namespace std;
int main()
{
string a;
getline(cin, a);
for(int i = 0; i < a.size() - 1; i++)
{
cout << char(a[i] + a[i + 1]); //直接输出
}
cout << char(a[a.size() - 1] + a[0]); //特别处理最后一个
return 0;
}
规律映射做:[是选择别难为自己还是提升自己]
#include
using namespace std;
int main() {
string a;
getline(cin, a);
for(int i = 0; i < a.size(); i ++)
cout << (char)(a[i] + a[(i + 1) % a.size()]); //最后一个a[a.size() - 1] + a[0] :(映射可能值区间)总长度 [尾部-首部]
return 0;
}
给定一个字符串,在字符串的每个字符之间都加一个空格。
输出修改后的新字符串。
输入格式
共一行,包含一个字符串。注意字符串中可能包含空格。
输出格式
输出增加空格后的字符串。
数据范围
1≤字符串长度≤100
输入样例:
test case
输出样例:
t e s t c a s e
简单版
#include
using namespace std;
int main()
{
string str;
getline(cin, str);
for(char &c : str) cout << c << ' '; //对string中的单个字符修改的时候才用&【可以不加】
}
试试scanf(“%[^\n]”, str);
#include
#include
using namespace std;
const int N = 105;
char str[N];
string res; //res[i]为char类型
int main()
{
scanf("%[^\n]", str);
int len = strlen(str);
for(int i = 0; i < len; i++)
{
res += str[i];
res += ' ';
}
cout << res ;
return 0;
}
输入一个字符串,字符串中可能包含多个连续的空格,请将多余的空格去掉,只留下一个空格。
输入格式
共一行,包含一个字符串。
输出格式
输出去掉多余空格后的字符串,占一行。
数据范围
输入字符串的长度不超过 200。
保证输入字符串的开头和结尾没有空格。
输入样例:
Hello world.This is c language.
输出样例:
Hello world.This is c language.
最简版
方法一:利用cin在输入时不会读入[空格|Tab|回车]。
#include
using namespace std;
int main()
{
string s;
while(cin >> s)
cout << s <<" ";
return 0;
}
与运算-2022mycode版
不能有两个连续空格
#include
#include
using namespace std;
int main()
{
string str, res;
getline(cin, str);
for(int i = 0 ; i < str.size() ; i++)
{
while(str[i] == ' ' && str[i + 1] == ' ') i++;//遇到多个空格跳过,只保留最后一个
res += str[i];
}
cout << res;
return 0;
}
或运算
#include
using namespace std;
int main()
{
string s;
getline(cin, s);
for(int i = 0; i < s.size(); i++)
if(s[i] != ' ' || s[i+1] != ' ') cout << s[i]; //一连串空格, 最后一个空格保留
return 0;
}
复杂版hh
#include
using namespace std;
int main()
{
string a ;
getline(cin,a);
for(int i = 0 ; i < a.size() ; i ++ )
{
if(a[i] == ' ') //检测到空格
{
cout << ' ';//只输出一个空格,删去多余的空格
while(a[i + 1] == ' ') i++; //检测下一个是不是空格,是的话就跳过(i++)
}
else cout << a[i];
}
}
在传输信息的过程中,为了保证信息的安全,我们需要对原信息进行加密处理,形成加密信息,从而使得信息内容不会被监听者窃取。
现在给定一个字符串,对其进行加密处理。
加密的规则如下:
字符串中的小写字母,a 加密为 b,b 加密为 c,…,y 加密为 z,z 加密为 a。
字符串中的大写字母,A 加密为 B,B 加密为 C,…,Y 加密为 Z,Z 加密为 A。
字符串中的其他字符,不作处理。
请你输出加密后的字符串。
输入格式
共一行,包含一个字符串。注意字符串中可能包含空格。
输出格式
输出加密后的字符串。
数据范围
输入字符串的长度不超过 100。
输入样例:
Hello! How are you!
输出样例:
Ifmmp! Ipx bsf zpv!
mycode2022
#include
using namespace std;
int main()
{
string str;
getline(cin, str);
for(int i = 0; i < str.size(); i++)
if(isalpha(str[i])) //先判断是不是字符
{
if(str[i] >= 'a' && str[i] <= 'z') str[i] = (str[i] - 'a' + 1) % 26 + 'a';
else str[i] = (str[i] - 'A' + 1) % 26 + 'A';
}
cout << str;
return 0;
}
string类型 for(autu c: str)
char类型转int的ASCLL码偏移量: c - ‘a’
(映射 - 减去区间第一个值【偏移值】
: 相当于平移到起点, 如0)
#include
using namespace std;
int main()
{
string str;
getline(cin, str);
for (auto &c : str) //加&修改
if(c >= 'a' && c <= 'z') c = (c - 'a' + 1) % 26 + 'a'; //小写字符偏移加密
else if (c >= 'A' && c <= 'Z') c = (c - 'A' + 1) % 26 + 'A'; //大写字符偏移加密
cout << str;
return 0;
}
单个字符修改版
scanf不会过滤空格哦!
#include
using namespace std;
int main()
{
char ch;
while(scanf("%c",&ch) == 1) //有读入即使是空格,回车也会继续循环
{
if(ch >= 'a' && ch <= 'z') ch = (ch-'a' + 1 ) % 26 + 'a';
else if(ch >= 'A' && ch <= 'Z') ch = (ch - 'A' + 1) % 26 + 'A';
cout << ch;
}
return 0;
}
不动脑子法
#include
using namespace std;
int main(){
string a;
getline(cin,a);
for(int i=0;i<a.size();i++){
if(a[i]>='a'&&a[i]<'z'||a[i]>='A'&&a[i]<'Z')
a[i]++;
else if(a[i]=='z'||a[i]=='Z')a[i]-=25;
}
cout<<a<<endl;
return 0;
}
枚举更菜法,但s[i]
#include
using namespace std;
int main()
{
string st;
int i;
getline(cin,st);
for (i=0;i<st.size();i++){
if (st[i]>='a'&&st[i]<='y'||st[i]>='A'&&st[i]<='Y'){
cout<<char(st[i]+1);
continue;
}//普通形式
if (st[i]=='z'){ //可改if else就不用continue
cout<<'a';
continue;
}//特殊
if (st[i]=='Z'){
cout<<'A';
continue;
}//特殊
cout<<st[i];//输出这一项就ok了
}
return 0;
}
一般我们用 strcmp 可比较两个字符串的大小,比较方法为对两个字符串从前往后逐个字符相比较(按 ASCII 码值大小比较),直到出现不同的字符或遇到 \0 为止。
如果全部字符都相同,则认为相同;如果出现不相同的字符,则以第一个不相同的字符的比较结果为准。
但在有些时候,我们比较字符串的大小时,希望忽略字母的大小,例如 Hello 和 hello 在忽略字母大小写时是相等的。
请写一个程序,实现对两个字符串进行忽略字母大小写的大小比较。
输入格式
输入为两行,每行一个字符串,共两个字符串。注意字符串中可能包含空格。
数据保证每个字符串的长度都不超过 80。
输出格式
如果第一个字符串比第二个字符串小,输出一个字符 <。
如果第一个字符串比第二个字符串大,输出一个字符 >。
如果两个字符串相等,输出一个字符 =。
输入样例:
Hello
hello
输出样例:
=
tolower(char c)函数
先全部转小写再比较大小
注意字符串中可能包含空格,此题不能cin(空格会结束!!!), 需用getline(cin,a);
#include
using namespace std;
int main()
{
string a, b;
getline(cin, a);
getline(cin, b);
for(auto &c: a) c = tolower(c); //不加&只能使用字符串的值,加了&就可以使用和修改值
for(auto &c: b) c = tolower(c); //tolower转换成小写的函数
if(a == b) puts("=");
else if(a > b) puts(">");
else if(a < b) puts("<");
}
比较strcmp(a.c_str(),b.c_str())返回值 + string.c_str()转char[]
#include
#include
#include
using namespace std;
int main()
{
string a, b;
getline(cin, a);//注意字符串中可能包含空格,此题不能cin 空格会结束 !! ,用getline(cin,a);
getline(cin, b);
for (int i = 0; i < a.size(); i ++) //a97, A65
if (a[i] >= 'A' && a[i] <= 'Z') a[i] += 32;
for (int i = 0; i < b.size(); i ++)
if (b[i] >= 'A' && b[i] <= 'Z') b[i] += 32;
int c;
c = strcmp(a.c_str(),b.c_str()); //char[]型比较函数 : 先a.c_str() : string转char[]
if(c < 0) cout << "<";
if(c == 0) cout << "=";
if(c > 0) cout << ">";
return 0;
}
// if(s1.size()>s2.size()) //提前剪枝
// {
// puts(">");return 0;
// }
// if(s1.size()
// {
// puts("<");return 0;
// }
给定一个由大小写字母构成的字符串。
把该字符串中特定的字符全部用字符 # 替换。
请你输出替换后的字符串。
输入格式
输入共两行。
第一行包含一个长度不超过 30 的字符串。
第二行包含一个字符,表示要替换掉的特定字符。
输出格式
输出共一行,为替换后的字符串。
输入样例:
hello
l
输出样例:
he##o
#include
using namespace std;
int main()
{
string str;
char ch;
cin >> str >> ch;
for(auto &c: str)
if(c == ch) c = '#';
cout << str;
return 0;
}
输入一个字符串,以回车结束(字符串长度不超过 100)。
该字符串由若干个单词组成,单词之间用一个空格隔开,所有单词区分大小写。
现需要将其中的某个单词替换成另一个单词,并输出替换之后的字符串。
输入格式
输入共 3 行。
第 1 行是包含多个单词的字符串 s;
第 2 行是待替换的单词 a(长度不超过 100);
第 3 行是 a 将被替换的单词 b(长度不超过 100)。
输出格式
共一行,输出将 s 中所有单词 a 替换成 b 之后的字符串。
输入样例:
You want someone to help you
You
I
输出样例:
I want someone to help you
全部存入string word[N]
#include
#include
using namespace std;
const int N = 105;
string word[N];
int cnt;
int main()
{
while(cin >> word[cnt]) cnt ++; //按word单词全部读入【 不要写成 cin >> word[cnt ++] 可能优先级或者结合错误,总之下标混乱】
string a = word[cnt - 2], b = word[cnt - 1];
for(int i = 0; i < cnt - 2; i++)
if(word[i] == a) cout<< b <<" ";
else cout << word[i] <<" ";
return 0;
}
string函数复合使用
#include
using namespace std;
int main()
{
string s, a, b;
getline(cin, s);
cin >> a >> b;//替换目标,替换值
s = ' ' + s + ' '; // 把单词和语句前后都加上空格 便于查找!!!
a = ' ' + a + ' ';
b = ' ' + b + ' ';
while(s.find(a) != string::npos) // 一直查找 找到就替换 没找到返回值string::npos (npos模板参数名称)
s.replace(s.find(a), a.size(), b); //找到a, 把a替换为b
// 把加进去的首尾空格都删除
s.erase(s.begin()); //等效s1.erase(0, 1); 【配合使用迭代器删除首元素】
s.erase(s.end() - 1); //或者s1.erase(s1.size() - 1);
cout << s;
return 0;
}
用正则表达式替换单词
#include
#include
#include //正则表达式
using namespace std;
int main()
{
string s, a, b;
getline(cin, s);
cin >> a >> b;
cout << regex_replace(s,regex("\\b" + a + "\\b"),b) << endl;
return 0;
}
string类型
s1.find(s2); // 在 s1 中查找字符串 s2,找到返回 s2 首字母在字符串中的下标,找不到返回 -1
s1.replace(pos, len, s2); // 把 s1 中从下标 pos 开始的长度为 len 的子串替换为 s2
s1.erase(it); // 把 s1 字符串中迭代器 it 处的字符删除
s1.erase(pos, len); // 把 s1 中从下标 pos 开始的长度为 len 的子串删除
详细总结跳转查看
求一个字符串中最长的连续出现的字符,输出该字符及其出现次数,字符串中无空白字符(空格、回车和 tab),如果这样的字符不止一个,则输出第一个。
输入格式
第一行输入整数 N,表示测试数据的组数。
每组数据占一行,包含一个不含空白字符的字符串,字符串长度不超过 200。
输出格式
共一行,输出最长的连续出现的字符及其出现次数,中间用空格隔开。
输入样例:
2
aaaaabbbbbcccccccdddddddddd
abcdefghigk
输出样例:
d 10
a 1
mycode2022
题目:字符串中无空白字符(空格、回车和 tab)—> 用cin读入
#include
#include
using namespace std;
int main()
{
int n;
cin >> n;
string str;
while (n -- )
{
cin >> str;
char ch = str[0];
int cnt = 1, max_cnt = 0;
for(int i = 0; i < str.size(); i++)
{
while(i + 1 < str.size() && str[i] == str[i + 1]) cnt ++, i++;
if(max_cnt < cnt) max_cnt = cnt, ch = str[i];
cnt = 1;
}
cout << ch << " " << max_cnt << endl;
}
return 0;
}
y总双指针
题目:字符串中无空白字符(空格、回车和 tab)
算法
(双指针) O(n×T)
使用双指针扫描每一个test case,并记录下最大长度与该长度下的字符即可。
时间复杂度
每个test case的字符串会被扫描一次,总共T个test case,所以总复杂度为O(n×T)。
#include
using namespace std;
int main()
{
int T;
cin >> T;
while(T --)
{
int maxn = -1;//maxn记录最大长度
string str, maxs;//maxs记录最大长度时的字符
cin >> str;
for(int i = 0; i < str.size(); i ++)
{
int j = i;
int cnt = 0;
while(str[j] == str[i] && j < str.size())//当指针j没有越界且与指针i的内容相同时移动
j ++, cnt ++;
if(cnt > maxn)//更新最大值
maxn = cnt, maxs = str[i];
i = j - 1;//判断下一个位置
}
cout << maxs << " " << maxn << endl;
}
}
给你一个只包含小写字母的字符串。
请你判断是否存在只在字符串中出现过一次的字符。
如果存在,则输出满足条件的字符中位置最靠前的那个。
如果没有,输出 no。
输入格式
共一行,包含一个由小写字母构成的字符串。
数据保证字符串的长度不超过 100000。
输出格式
输出满足条件的第一个字符。
如果没有,则输出 no。
输入样例:
abceabcd
输出样例:
e
从前往后查找与从后向前查找同一个字符,如果位置一样那该字符就直出现一次,因为是从第一个开始查找所以找出的是第一个只出现一次的字符
s.find()和s.rfind()
夹逼
#include
#include
#include
using namespace std;
int main()
{
string s;
bool flag = false;
while(getline(cin, s)) //可能包含空格
{
for(int i = 0;i < s.size(); i++)
{
if(s.find(s[i]) == s.rfind(s[i])) //正序查找第一个下标 == 逆序查找第一个下标 :等效有且仅有一个元素
{
cout << s[i] << endl;
flag = true;
break;
}
}
}
if(!flag) puts("no");
return 0;
}
去掉标记版
#include
#include
#include
using namespace std;
int main()
{
string s;
while(getline(cin, s)) //可能包含空格
{
for(int i = 0;i < s.size(); i++)
{
if(s.find(s[i]) == s.rfind(s[i])) //正序 == 逆序 等效有且仅有一个元素
{
cout << s[i] << endl;
return 0; //省标记【但多轮输入只能break】
}
}
}
puts("no");
return 0;
}
cnt统计1
#include
using namespace std;
const int N = 150; //97开始 +
int cnt[N];
int main()
{
string s;
cin >> s;
//虽然不改s, 但是有时会没加&而调试
for(auto &i: s)//遍历字符串 ,对应字符ASCLL位置++ : 优化映射26,数组开26即可 【i-'a'】 【yxc:写代码不要难为自己!!!】
{
cnt[i] ++; //(int)(i-'a') [好像自动转整数]
printf("%d ", i);
}
for(auto &i: s) //遍历字符串
if(cnt[i] == 1) //i值即 s[i]
{
cout << i << endl;
return 0; //省一个标记
}
cout << "no" << endl;
return 0;
}
cnt统计2
#include
#include
using namespace std;
const int N = 30; //映射0-25
int cnt[N];
int main()
{
string s;
cin >> s;
for(int i = 0; i < s.size(); i++) cnt[s[i] - 'a']++; //s.size()返回值类型。。
for(int i = 0; i < s.size(); i++)
if(cnt[s[i] - 'a'] == 1)
{
cout << s[i] << endl;
return 0;
}
puts("no");
return 0;
}
有两个不包含空白字符的字符串 str 和 substr,str 的字符个数不超过 10,substr 的字符个数为 3。(字符个数不包括字符串结尾处的 \0。)
将 substr 插入到 str 中 ASCII 码最大的那个字符后面,若有多个最大则只考虑第一个。
输入格式
输入包括若干行,每一行为一组测试数据,格式为
str substr
输出格式
对于每一组测试数据,输出插入之后的字符串。
输入样例:
abcab eee
12343 555
输出样例:
abceeeab
12345553
str.insert(下标位置从0开始, 插入sub)
#include
#include
using namespace std;
int main()
{
string str, sub;
while(cin >> str >> sub)
{
int pos = 0;
for(int i=0; i < str.size(); i++)
if(str[i] > str[pos]) //找对应ASCLL码max的字符下标位置,多个相同选取第一个
{
pos = i;
}
str.insert(pos + 1, sub); //插入str中ASCII 码最大的那个字符的后面
cout << str << endl;
}
}
一个以 . 结尾的简单英文句子,单词之间用空格分隔,没有缩写形式和其它特殊形式,求句子中的最长单词。
输入格式
输入这个简单英文句子,长度不超过 500。
输出格式
该句子中最长的单词。如果多于一个,则输出第一个。
输入样例:
I am a student of Peking University.
输出样例:
University
mycode2022
#include
#include
using namespace std;
int main()
{
string str, res;
int max_len = 0;
while(cin >> str)
{
if(str.back() == '.') str.pop_back(); //或者str.erase(str.size() - 1, 1);
if(str.size() > max_len)
{
max_len = str.size();
res = str;
}
}
cout << res << endl;
return 0;
}
双指针-截取每个单词(比函数快)
#include
using namespace std;
int cnt, maxn;
string s, res, tmp;
int main()
{
getline(cin,s);
for (int i = 0; i < s.size(); i++)
{
for (int j = i; s[j] != '.' && s[j] != ' '; j ++)
{
tmp = tmp + s[j];
cnt++;
}
if (maxn < cnt)
{
maxn = cnt;
res = tmp;
}
i += cnt; //判断下一个单词:移到下个单词开头
cnt= 0;//每轮遍历状态初始化!!
tmp = "";
}
cout << res << endl;//空字符串就打印为空
return 0;
}
others :
str.erase()
#include
#include
using namespace std;
string l,lmax;
int len,maxx,f;
int main(){
while(cin>>l){
len=l.size();
if(l[len-1]=='.'){ //最后一个
len--;
l.erase(len,1);
f=1;
}
if(len>maxx){
lmax=l;
maxx=len;
}
if(f) break; //空字符串,没有'.'结尾
}
cout<<lmax ;
return 0;
}
编写程序,读入一行英文(只包含字母和空格,单词间以单个空格分隔),将所有单词的顺序倒排并输出,依然以单个空格分隔。
输入格式
输入为一个字符串(字符串长度至多为 100)。
输出格式
输出为按要求排序后的字符串。
输入样例:
I am a student
输出样例:
student a am I
字符串倒序拼接-多个拼接
#include // string
using namespace std;
int main()
{
string str, res;
while(cin >> str)
{
res = str + " " + res; //可以多个加号连用!!!
}
cout << res << endl;
return 0;
}
cnt统计
#include
using namespace std;
const int N = 105;
int cnt;
string s[N];
int main()
{
while(cin >> s[cnt]) cnt ++;
while(cnt -- ) cout << s[cnt] <<" ";
return 0;
}
对于一个字符串来说,定义一次循环移位操作为:将字符串的第一个字符移动到末尾形成新的字符串。
给定两个字符串 s1 和 s2,要求判定其中一个字符串是否是另一字符串通过若干次循环移位后的新字符串的子串。
例如 CDAA 是由 AABCD 两次移位后产生的新串 BCDAA 的子串,而 ABCD 与 ACBD 则不能通过多次移位来得到其中一个字符串是新串的子串。
输入格式
共一行,包含两个字符串,中间由单个空格隔开。
字符串只包含字母和数字,长度不超过 30。
输出格式
如果一个字符串是另一字符串通过若干次循环移位产生的新串的子串,则输出 true,否则输出 false。
输入样例:
AABCD CDAA
输出样例:
true
string::find()与string::npos
自身拼接且遍历 :可以得到所有循环子串
#include
#include
#include
using namespace std;
int main()
{
string a, b, s;
cin >> a >> b;
if(a.size() < b.size()) swap(a, b); //题目其中一个, 选取长为主串, 另一个为模式串
s = a + a;
if(s.find(b) != string::npos) puts("true"); //find函数寻找子串
else puts("false");
return 0;
}
string.find()若没有找到,相当于不为空 :string字符串就会返回一个特殊的标记npos (类似n长度的pos位置)
string::npos 等于 -1 或者 4294967295
size_t类型: 所能达到的最大长度,它是无符号整数 //这种整数用来记录一个大小(size)。size_t的全称应该是size type,就是说“一种用来记录大小的数据类型”
完整知识点链接
string::find()函数:是一个字符或字符串查找函数, 该函数有唯一的返回类型, 即string::size_type
string::size_type, 定义无符号整形类型,可能是整数也可能是长整数。
如果查找成功,返回按照查找规则找到的第一个字符或者子串的位置
如果查找失败,返回string::npos,即-1(但结果不是-1,而是一个很大的数值,那是因为它是无符号的)
string::npos是这样定义的:static const size_type npos = -1;
因为string::size_type描述的是size,故需为无符号整数型类别。
因为缺省配置为size_t作为size_type,于是-1被转换为无符号整数类型,npos也就成为了该类别的最大无符号值。
不过实际值还是取决于size_type的实际定义类型,即无符号整型(unsigned int)的-1与无符号长整型(unsigned long)的-1是不同的。
#include
#include
using namespace std;
bool check(string a, string b)
{
int len = a.size();
a += a; //复制字符串并连接
//if (a.find(b) >= 0 && a.find(b) < len) return true; //判断是否包含
if (a.find(b) != string::npos) return true; //判断a中是否包含b
return false;
}
int main()
{
string a, b;
cin >> a >> b;
if (check(a, b) || check(b, a)) cout << "true";
else cout << "false";
return 0;
}
给定两个字符串 a 和 b,我们定义 a×b 为他们的连接。
例如,如果 a=abc 而 b=def, 则 a×b=abcdef。
如果我们将连接考虑成乘法,一个非负整数的乘方将用一种通常的方式定义:a0=``(空字符串),a(n+1)=a×(an)。
输入格式
输入包含多组测试样例,每组测试样例占一行。
每组样例包含一个字符串 s,s 的长度不超过 100。
最后的测试样例后面将是一个点号作为一行。
输出格式
对于每一个 s,你需要输出最大的 n,使得存在一个字符串 a,让 s=an。
输入样例:
abcd
aaaa
ababab
.
输出样例:
1
4
3
枚举子串看能否拼接成字符串
#include
#include
using namespace std;
int main()
{
string str;
while(cin >> str && str != ".")
{
//if(str == ".") return 0;
for(int i = 1; i <= str.size(); i++)
{
string sub = str.substr(0, i); //string sub = string(str, 0, i);
if(str.size() % sub.size() != 0) continue; //剪枝:不可能是周期
string sub_concat = sub;
while(sub_concat.size() < str.size()) sub_concat += sub; //能否由子串组成字符串str
if(sub_concat == str)
{
cout<< (str.size() / sub.size()) <<endl; // n子串个数 = 字符串总长 / 子串长
break;
}
}
}
return 0;
}
cnt统计
#include
#include
using namespace std;
int main()
{
string str;
while(cin >> str)
{
if(str == ".") return 0;
for(int i = 1; i <= str.size(); i++)
{
int cnt = 1;//统计子串个数
string sub = str.substr(0, i);
if(str.size() % sub.size() != 0) continue; //剪枝:不可能是周期
string concat_sub = sub;
while(concat_sub.size() < str.size()) concat_sub += sub, cnt++; //子串拼接次数cnt++
if(concat_sub == str)
{
cout<< cnt << endl; // n子串个数 = 字符串总长 / 子串长
break;
}
}
}
return 0;
}
y总朴素版:双指针
#include
using namespace std;
int main()
{
string str;
while (cin >> str, str != ".")
{
int k = 1;
while (k <= str.size()) //枚举周期长度(子串长度)为k
{
if (str.size() % k == 0)
{
bool flag = true;
for (int i = k; i < str.size(); i += k) //希尔排序
for (int j = 0; j < k; j ++ )
if (str[j] != str[i + j]) //比较是否相等
flag = false;
if (flag) break; //找到周期子串
}
k ++ ;
}
cout << str.size() / k << endl; //n乘方 = 子串个数
}
return 0;
}
有三个字符串 S,S1,S2,其中,S 长度不超过 300,S1 和 S2 的长度不超过 10。
现在,我们想要检测 S1 和 S2 是否同时在 S 中出现,且 S1 位于 S2 的左边,并在 S 中互不交叉(即,S1 的右边界点在 S2 的左边界点的左侧)。
计算满足上述条件的最大跨距(即,最大间隔距离:最右边的 S2 的起始点与最左边的 S1 的终止点之间的字符数目)。
如果没有满足条件的 S1,S2 存在,则输出 −1。
例如,S= abcd123ab888efghij45ef67kl, S1= ab, S2= ef,其中,S1 在 S 中出现了 2 次,S2 也在 S 中出现了 2 次,最大跨距为:18。
输入格式
输入共一行,包含三个字符串 S,S1,S2,字符串之间用逗号隔开。
数据保证三个字符串中不含空格和逗号。
输出格式
输出一个整数,表示最大跨距。
如果没有满足条件的 S1 和 S2 存在,则输出 −1。
输入样例:
abcd123ab888efghij45ef67kl,ab,ef
输出样例:
18
思路: 找第一个s1和最后一个s2[有点贪心最优解]
string成员函数
s.find(字符串); 从前往后返回第一个位置下标, 找不到返回 -1
s.rfind(字符串); 从后往前返回第一个位置下标, 找不到返回 -1
mycode2022
#include
#include
#include
using namespace std;
int main()
{
string s, s1, s2;
char ch;
int i = 0;
while(cin >> ch)
{
if(ch != ',')
{
if(i == 0) s += ch;
else if(i == 1) s1 += ch;
else s2 += ch;
}
else i ++; //第一次遇到',' i == 1, 第二次遇到',' i == 2
}
//为了保证s1在s2的左边且互不交叉, s2逆序寻找[找最优解]
int l = s.find(s1), r = s.rfind(s2); //l:s1的位置 , r: s2的位置
if(l != -1 && r != -1 && l + s1.size() - 1 < r) printf("%d", r - l - s1.size() ); //输出下标差值距离
else puts("-1"); //不存在输出-1
return 0;
}
y总
#include
using namespace std;
int main()
{
string line;
cin >> line; //读入一行-无空格
string a, b, c; //【字符串截取a, b, c】
int k = 0;
while (line[k] != ',') a += line[k ++ ];
k ++ ;
while (line[k] != ',') b += line[k ++ ];
k ++ ;
while (k < line.size()) c += line[k ++ ];
int l = 0; //模拟find:从左到右找
while (l < a.size())
{
if (a.substr(l, b.size()) == b) break;
l ++ ;
}
int r = a.size() - 1;
while (r >= 0) //模拟rfind:从右到左找
{
if (a.substr(r, c.size()) == c) break;
r -- ;
}
if (l + b.size() <= r) cout << r - l - b.size() << endl; //找不到子串则走到边界,也是不满足此条件输出-1
else cout << -1 << endl;
return 0;
}
三月的风链接
#include
using namespace std;
int main()
{
string s, s1, s2, a;
getline(cin, a);
int f1, f2; // 两个','的位置 读入处理【先寻找','位置下标,再用substr函数】
f1 = a.find(',');
f2 = a.rfind(',');
s = a.substr(0, f1);
s1 = a.substr(f1 + 1, f2 - f1 - 1);
s2 = a.substr(f2 + 1);
int l, r;
l = s.find(s1); // 在字符串s上从左往右找s1
r = s.rfind(s2); // 在字符串s上从右往左找s2 [找最优解]
if (l == -1 || r == -1) // s1 或 s2 不在 s 上
{
cout << "-1";
return 0;
}
l = s.find(s1) + s1.size() - 1; // l为s1最右面的下标
if ( l >= r ) // s1 s2 交叉
cout << "-1";
else cout << r - l - 1;
return 0;
}
给出若干个字符串,输出这些字符串的最长公共后缀。
输入格式
由若干组输入组成。
每组输入的第一行是一个整数 N。
N 为 0 时表示输入结束,否则后面会继续有 N 行输入,每行是一个字符串(字符串内不含空白符)。
每个字符串的长度不超过 200。
输出格式
共一行,为 N 个字符串的最长公共后缀(可能为空)。
数据范围
1≤N≤200
输入样例:
3
baba
aba
cba
2
aa
cc
2
aa
a
0
输出样例:
ba
a
相邻两个求相同后缀:比较取min求出公共后缀
#include
#include
#include
using namespace std;
int main()
{
int n;
while(cin >> n, n)
{
string s, a;
int len = 0x3f3f3f3f; //用min初始INF
cin >> s;
for(int i = 1; i < n; i ++)
{
int res = 0;
cin >> a;
for(int j = 0; j < a.size() && j < s.size(); j ++) //等效j < min(a.size(), s.size())
if(a[a.size() - 1 - j] == s[s.size() - 1 - j]) res ++; //此轮公共后缀长度res
else break;
len = min(len, res); //取比较两两相邻后缀: 取最小值为公共后缀
}
if(len) cout << s.substr(s.size() - len) << endl;
else cout << endl; //输出回车
}
return 0;
}
y总
#include
using namespace std;
int main()
{
string words[200];
int n;
while (cin >> n, n)
{
for (int i = 0; i < n; i ++ ) cin >> words[i];
int k = 1; //最长公共后缀长度:从1开始判断
while (true)
{
bool flag = true; //false则否定此轮判断
for (int i = 0; i < n; i ++ )
{
if (words[i].size() < k) //size < k : 公共后缀长度不可能大于k
{
flag = false;
break;
} //遍历所有与第一个判断: substr(起始下标从0开始, 截取长度)
else if (words[i].substr(words[i].size() - k, k) != words[0].substr(words[0].size() - k, k))
{
flag = false;
break;
}
}
if (!flag) break;
k ++ ;
}
k -- ; //上一轮k++ 但是判断不行后break, 则合理的最长公共后缀长度为k - 1
cout << words[0].substr(words[0].size() - k, k) << endl;
}
return 0;
}
思想:把所有字符串首尾翻转,求公共后缀就变为求公共前缀
依次从前往后比较所有字符串每一位的字符是否相同,取所有字符串中最短的长度作为len
从第 0 位依次比较到第 len - 1 位,即可确定前几位是公共前缀。
#include
#include
using namespace std;
const int N = 210;
string s[N];
int main()
{
int m; // m个字符串
while (cin >> m && m)
{
int len = 200;
for (int i = 0; i < m; i ++)
{
cin >> s[i];
reverse( s[i].begin(), s[i].end() ); // 把每个字符串首尾翻转 变后缀为前缀
if ( s[i].size() < len) len = s[i].size(); // 取所有字符串中最短长度作为len
}
string a; // 从前往后存放相同的字符 最后一位不一定属于公共前缀
int l = -1;
for(int i = 0; i < len; i ++) // 从字符串第一位依次比较
{
a += s[0][i]; // 把第一个字符串的第i个字符存到a[i]中用于与后面的比较
for (int j = 1; j < m; j ++)
if (a[i] != s[j][i])
{
l = i; // 把出现不同的位数存到l中
break;
}
if (l != -1) break; // 已经知道前缀的位数 后面的没必要判断
}
if (l == -1) // 说明没出现过a[i] != s[j][i] a中的字符都是公共前缀
for (int i = len - 1; i >= 0; i --) // 因为翻转过 倒序输出
cout << a[i];
else
for (int i = l - 1; i >= 0; i --) // a的第l位字符不是公共前缀 l之前的字符都是
cout << a[i];
cout << endl;
}
return 0;
}
实参是形参的初始值,形参也可以设置默认值。
当传入的实参个数少于形参个数时,最后没有被传入值的形参会使用默认值。
当初始化一个非引用类型的变量时,初始值被拷贝给变量。此时,对变量的改动不会影响初始值。
当函数的形参为引用类型时,对形参的修改会影响实参的值。使用引用的作用:避免拷贝、让函数返回额外信息。
一维数组形参的写法: ( int *a / int a[] / int a[10]) (三种等价)
void函数如果想在它的中间位置提前退出,可以使用return语句。return的这种用法有点类似于我们用break语句退出循环。
函数递归:在一个函数内部,也可以调用函数本身。
输入一个整数 n,请你编写一个函数,int fact(int n),计算并输出 n 的阶乘。
输入格式
共一行,包含一个整数 n。
输出格式
共一行,包含一个整数表示 n 的阶乘的值。
数据范围
1≤n≤10
输入样例:
3
输出样例:
6
递归
#include
using namespace std;
int n;
int fact(int n)
{
if(n == 1)return 1;
return n * fact(n-1);
}
int main()
{
cin >> n;
cout << fact(n);
return 0;
}
递推
#include
#include
using namespace std;
int main()
{
int n;
scanf("%d", &n);
int res = 1;
for(int i = 1; i <= n; i++)
res = res * i;
printf("%d", res); //最多10!不会爆int
return 0;
}
用不上但是%orz%
//主函数递归犇
#include
int main(int n,bool f=true)
{
if(f)
{
scanf("%d",&n);
printf("%d",main(n,false));
return 0;
}
if(n)return main(n-1,false)*n;
return 1;
}
输入两个整数 x 和 y,请你编写一个函数,int max(int x, int y),计算并输出 x 和 y 的最大值。
输入格式
共一行,包含两个整数 x 和 y。
输出格式
共一行,包含一个整数,表示两个数中较大的那个数。
数据范围
−100≤x,y≤100
输入样例:
3 6
输出样例:
6
#include
using namespace std;
void max(int x, int y)
{
// if(x > y) cout << x;
// else cout << y ;
printf("%d", x > y ? x : y);
}
int main()
{
int x, y;
cin >> x >> y;
max(x, y);
return 0;
}
输入两个浮点数 x 和 y,请你编写一个函数,double add(double x, double y),计算并输出 x 与 y 的和。
输入格式
共一行,包含两个浮点数 x 和 y。
输出格式
共一行,包含一个浮点数,表示两个数的和,结果保留 2 位小数。
数据范围
−1000≤x,y≤1000
输入样例:
1.11 2.22
输出样例:
3.33
#include
using namespace std;
double a, b;
double add(double x, double y)
{
printf("%.2lf", x + y);
}
int main()
{
scanf("%lf%lf", &a, &b);
add(a, b);
return 0;
}
输入两个整数 l 和 r,请你编写一个函数,int sum(int l, int r),计算并输出区间 [l,r] 内所有整数的和。
输入格式
共一行,包含两个整数 l 和 r。
输出格式
共一行,包含一个整数,表示所求的和。
数据范围
1≤l≤r≤1000
输入样例:
3 5
输出样例:
12
#include
using namespace std;
void sum(int l, int r)
{
int s = 0;
for(int i = l; i <= r; i++)
s += i;
printf("%d", s);
}
int main()
{
int a, b;
scanf("%d%d",&a, &b);
sum(a, b);
return 0;
}
old_code
#include
using namespace std;
int a,b,res;
int sum(int l, int r)
{
for(int i = l;i <= r;i++) res += i;
return res; //不要返回局部变量,会释放
}
int main()
{
scanf("%d%d",&a,&b);
printf("%d",sum(a,b));
return 0;
}
输入两个整数 a 和 b,请你编写一个函数,int gcd(int a, int b), 计算并输出 a 和 b 的最大公约数。
输入格式
共一行,包含两个整数 a 和 b。
输出格式
共一行,包含一个整数,表示 a 和 b 的最大公约数。
数据范围
1≤a,b≤1000
输入样例:
12 16
输出样例:
4
#include
#include
using namespace std;
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
int main()
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d", gcd(a, b));
// printf("%d", __gcd(a, b));
return 0;
}
输入两个整数 a 和 b,请你编写一个函数,int lcm(int a, int b),计算并输出 a 和 b 的最小公倍数。
输入格式
共一行,包含两个整数 a 和 b。
输出格式
共一行,包含一个整数,表示 a 和 b 的最小公倍数。
数据范围
1≤a,b≤1000
输入样例:
6 8
输出样例:
24
#include
#include //__gcd(a,b) 两条下杆
using namespace std;
int a,b;
int lcm(int a, int b)
{
return a / __gcd(a,b) * b; //防溢出 [但有时计算错误]
}
int main()
{
scanf("%d%d",&a,&b);
printf("%d",lcm(a,b));
return 0;
}
other
#include
using namespace std;
int lcm(int &a,int &b)
{
int res=a*b;
for(int i=a; i<=res; i++)
{
if((i%a==0)&&(i%b==0)) res=i;
}
return res;
}
int main()
{
int a,b;
cin>>a>>b;
cout<<lcm(a,b)<<endl;
return 0;
}
输入一个整数 x,请你编写一个函数,int abs(int x),输出 x 的绝对值。
输入格式
共一行,包含一个整数 x。
输出格式
共一行,包含 x 的绝对值。
数据范围
−100≤x≤100
输入样例:
-3
输出样例:
3
#include
using namespace std;
int abs(int x)
{
if(x < 0) return -x;
else return x;
}
int main()
{
int x;
scanf("%d", &x);
printf("%d", abs(x));
return 0;
}
输入两个整数 x 和 y,请你编写一个函数,void swap(int &x, int &y), 交换两个整数的数值并输出交换后的 x 和 y。
输入格式
共一行,包含两个整数 x 和 y。
输出格式
共一行,包含交换后的 x 和 y。
数据范围
1≤x,y≤100
输入样例:
3 5
输出样例:
5 3
#include
using namespace std;
void swap(int &x, int &y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int x, y;
scanf("%d%d", &x, &y);
swap(x,y);
printf("%d %d", x, y); //Presentation Error 格式错误:即将AC
return 0;
}
输入一个长度为 n 的数组 a 和一个整数 size,请你编写一个函数, void print(int a[], int size), 打印数组 a 中的前 size 个数。
输入格式
第一行包含两个整数 n 和 size。
第二行包含 n 个整数 a[i],表示整个数组。
输出格式
共一行,包含 size 个整数,表示数组的前 size 个数。
数据范围
1≤n≤1000,
1≤size≤n,
输入样例:
5 3
1 2 3 4 5
输出样例:
1 2 3
#include
using namespace std;
const int N = 1010;
int a[N];
int Size; //size与库函数重名
int n;
void print(int a[], int Size)
{
if(n < Size) printf("error"); //健壮性
for(int i = 0; i < Size; i++)
printf("%d ", a[i]);
}
int main()
{
scanf("%d%d", &n, &Size);
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
print(a, Size);
return 0;
}
给定一个 row×col 的二维数组 a,请你编写一个函数,void print2D(int a[][N], int row, int col),打印数组构成的 row 行,col 列的矩阵。
注意,每打印完一整行需要输出一个回车。
输入格式
第一行包含两个整数 row,col。
接下来 row 行,每行包含 col 个整数,表示完整二维数组 a。
输出格式
共 row 行,每行 col 个整数,表示打印出的矩阵。
数据范围
1≤row≤100,
1≤col≤100
输入样例:
3 4
1 3 4 5
2 6 9 4
1 4 7 5
输出样例:
1 3 4 5
2 6 9 4
1 4 7 5
mycode2022
#include
using namespace std;
const int N = 110;
int a[N][N];
void print2D(int a[][N], int row, int col)
{
for(int i = 0; i < row; i++)
{
for(int j = 0; j < col; j++)
{
printf("%d ", a[i][j]);
}
puts("");
}
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
scanf("%d", &a[i][j]);
print2D(a , n, m);
return 0;
}
以前好玩【难为自己hh】
#include
using namespace std;
const int N = 1e2+10;
int r,c;
int a[N][N];
void print2D(int a[][N], int row, int col)
{
for(int i = 0;i < r*c;i++)
{
printf("%d ",a[i/c][i%c]);
if((i+1)%c==0)puts("");
}
}
int main()
{
scanf("%d%d",&r,&c);
for(int i = 0;i < r*c;i++)scanf("%d",&a[i/c][i%c]); //行 i/c , 目前列 == i%c [并没有减少循环次数hh]
print2D(a,r,c);
return 0;
}
给定两个数组 a 和 b 以及一个整数 size,请你编写一个函数,void copy(int a[], int b[], int size),将 a 数组中的前 size 个数字,复制到 b 数组中。
复制完成后,输出 b 数组。
输入格式
第一行包含整数 n,m,size,分别表示 a 数组的长度,b 数组的长度以及整数 size。
第二行包含 n 个整数,表示数组 a。
第三行包含 m 个整数,表示数组 b。
输出格式
共一行,包含 m 个整数,表示复制完成后的数组 b。
数据范围
1≤n≤m≤100,
1≤size≤n
输入样例:
3 5 2
1 2 3
4 5 6 7 8
输出样例:
1 2 6 7 8
#include
using namespace std;
const int N = 105;
int n,m,Size;
int a[N],b[N];
void copy(int a[], int b[], int size)
{
for(int i = 0;i < size;i++) b[i] = a[i];
}
int main()
{
scanf("%d%d%d",&n,&m,&Size);
for(int i = 0;i < n;i++)scanf("%d ",&a[i]);
for(int i = 0;i < m;i++)scanf("%d ",&b[i]);
copy(a,b,Size);
for(int i = 0;i < m;i++)printf("%d ",b[i]);
return 0;
}
给定一个字符串,请你编写一个函数,void print(char str[]),将这个字符串打印出来。
输入格式
共一行,包含一个字符串。
输出格式
共一行,表示打印出的字符串。
数据范围
1≤字符串长度≤100
输入样例:
I love AcWing.
输出样例:
I love AcWing.
//char a[] -- cin.getline(a,size + 1)
#include
#include
using namespace std;
const int N = 105;
char a[N];
void print(char a[])
{
for (int i = 0; a[i]; i ++ ) //妙
cout << a[i];
}
int main()
{
cin.getline(a, 120); // 注意这里的数字,如果你只写了100,那么最多只会读99个字符!
print(a);
return 0;
}
//string --- getline(cin,string)
mycode2022
不符合题目要求构造void print(char str[]) 嘎嘎
#include
using namespace std;
int main()
{
string str;
getline(cin, str);
cout << str;
return 0;
}
给定一个长度为 n 的数组 a 和一个整数 size,请你编写一个函数,void reverse(int a[], int size),实现将数组 a 中的前 size 个数翻转。
输出翻转后的数组 a。
输入格式
第一行包含两个整数 n 和 size。
第二行包含 n 个整数,表示数组 a。
输出格式
共一行,包含 n 个整数,表示翻转后的数组 a。
数据范围
1≤size≤n≤1000,
1≤a[i]≤1000
输入样例:
5 3
1 2 3 4 5
输出样例:
3 2 1 4 5
#include
#include
using namespace std;
const int N = 1010;
int n,Size;
int a[N];
void reverse(int a[], int size)
{
for(int i = 0;i < size / 2; i++)
swap(a[i], a[size -1 - i]); //1 ---- size-1 ,2 ---- size-2 , 翻转size/2 次
}
int main()
{
scanf("%d%d", &n, &Size);
for(int i = 0;i < n;i++) scanf("%d", &a[i]);
reverse(a, Size);
for(int i = 0;i < n;i++) printf("%d ", a[i]);
return 0;
}
给定一个长度为 n 的数组 a,请你编写一个函数:
int get_unique_count(int a[], int n); // 返回数组前n个数中的不同数的个数
输入格式
第一行包含一个整数 n。
第二行包含 n 个整数,表示数组 a。
输出格式
共一行,包含一个整数表示数组中不同数的个数。
数据范围
1≤n≤1000,
1≤ai≤1000。
输入样例:
5
1 1 2 4 5
输出样例:
4
unique():去除相邻的重复元素(只保留一个),所以使用前需要对数组进行排序
去重后数列长度:set_len =unique(a, a + n) - a
unique(a, a + n)返回的是a去重后,不重复部分的最后一个元素的尾地址, 减去首地址a得到不重复数列的长度
注意:unique函数没有将重复的元素删除,而是把不重复的元素移到前面来【即重复的元素放在尾部】
unique
#include
#include
using namespace std;
const int N = 1010;
int n;
int a[N];
int main()
{
scanf("%d", &n);
for(int i=0;i<n;i++) scanf("%d", &a[i]);
sort(a,a + n);
int ans = unique(a, a + n) - a;
printf("%d", ans);
return 0;
}
sort
#include
#include
using namespace std;
const int N = 1010;
int a[N];
int cnt;
int main()
{
int n;
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
sort(a, a + n);
for(int i = 0; i < n; i++)
if(a[i] != a[i + 1]) cnt ++;
printf("%d", cnt);
return 0;
}
cnt统计
#include
using namespace std;
const int N = 1e3 + 10;
int get_unique_count(int a[], int n) //可以边标记边计数【这样重复的数只有第一个会cnt++】
{
int res = 0;
int cnt[N] = {};//value范围
for(int i = 0; i < n; i++)
{
cnt[a[i]] ++;
if(cnt[a[i]] == 1) res++;
}
return res;
}
int main()
{
int n;
scanf("%d", &n);
int a[n + 1]; //最省空间法:其实也不好:一般解题不这样写
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
printf("%d", get_unique_count(a, n));
return 0;
}
给定一个长度为 n 的数组 a 以及两个整数 l 和 r,请你编写一个函数,void sort(int a[], int l, int r),将 a[l]∼a[r] 从小到大排序。
输出排好序的数组 a。
输入格式
第一行包含三个整数 n,l,r。
第二行包含 n 个整数,表示数组 a。
输出格式
共一行,包含 n 个整数,表示排序完成后的数组 a。
数据范围
0≤l≤r
5 2 4
4 5 1 3 2
输出样例:
4 5 1 2 3
quick 、merge
#include
#include
using namespace std;
const int N = 1010;
int n, l, r;
int a[N];
void sort(int q[], int l, int r)
{
if(l >= r) return;
int x = q[l + r >> 1], i = l - 1, j = r + 1;
while(i < j)
{
do i++ ; while(q[i] < x);
do j-- ; while(q[j] > x);
if(i < j)swap(q[i],q[j]);
}
sort(q, l, j), sort(q, j + 1, r);
}
int tmp[N];
void merge(int q[], int l, int r)
{
if(l >= r) return;
int mid = l + r >> 1;
merge(q, l, mid), merge(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while(i <= mid && j <= r)
if(q[i] <= q[j]) tmp[k++] = q[i++];
else tmp[k++] = q[j++];
while(i <= mid) tmp[k++] = q[i++];
while(j <= r) tmp[k++] = q[j++];
for(int i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j];
}
int main()
{
scanf("%d%d%d",&n,&l,&r);
for(int i = 0;i <n;i++) scanf("%d",&a[i]);
sort(a, l, r);
// merge(a, l, r);
for(int i = 0;i <n;i++)printf("%d ",a[i]);
return 0;
}
输入格式
共一行,包含一个整数 n。
输出格式
共一行,包含一个整数,表示 n 的阶乘的值。
数据范围
1≤n≤10
输入样例:
3
输出样例:
6
mycode2022
#include
using namespace std;
int fact(int n)
{
if(n == 1) return 1;
return fact(n - 1) * n;
}
int main()
{
int n;
scanf("%d", &n);
printf("%d", fact(n));
return 0;
}
#include
using namespace std;
int main()
{
int n;
scanf("%d",&n);
int ans = 1;
while(n) ans *= n --;
printf("%d", ans);
return 0;
}
请使用递归的方式求斐波那契数列的第 n 项。
斐波那契数列:1,1,2,3,5…,这个数列从第 3 项开始,每一项都等于前两项之和
输入格式
共一行,包含整数 n。
输出格式
共一行,包含一个整数,表示斐波那契数列的第 n 项。
数据范围
1≤n≤30
输入样例:
4
输出样例:
3
#include
using namespace std;
int n;
int Fib(int n)
{
if(n == 1 || n == 2)return 1;
return Fib(n-1) + Fib(n-2);
}
int main()
{
scanf("%d", &n);
printf("%d", Fib(n)); //下标从1开始
}
递推
#include
using namespace std;
int main()
{
int n;
scanf("%d", &n);
int a = 1,b = 1, c; // a初始化代表第一项
if(n <= 2) puts("1");
else
{
for(int i = 2; i <= n; i++) //a已经初始为第一项:每轮向后移一项
{
c = a + b;
a = b;
b = c;
}
printf("%d", a);
}
return 0;
}
一个楼梯共有 n 级台阶,每次可以走一级或者两级,问从第 0 级台阶走到第 n 级台阶一共有多少种方案。
输入格式
共一行,包含一个整数 n。
输出格式
共一行,包含一个整数,表示方案数。
数据范围
1≤n≤15
输入样例:
5
输出样例:
8
#include
using namespace std;
int Fib(int n)
{
if(n==1 || n == 2) return 1;
else return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n;
scanf("%d",&n);
printf("%d", Fib(n + 1)); // 47项之后开long
return 0;
}
给定一个 n×m 的方格阵,沿着方格的边线走,从左上角 (0,0) 开始,每次只能往右或者往下走一个单位距离,问走到右下角 (n,m) 一共有多少种不同的走法。
输入格式
共一行,包含两个整数 n 和 m。
输出格式
共一行,包含一个整数,表示走法数量。
数据范围
1≤n,m≤10
输入样例:
2 3
输出样例:
10
dp
从右或者上走过来 : f[n][m] = f[n - 1][m] + f[n][m - 1]
注意:边界:f[i][0] = f[0][j]= 1
#include
using namespace std;
const int N = 12;
int f[N][N];
int main()
{
int n, m;
scanf("%d%d", &n, &m);
f[0][0] = 1;
for(int i = 0; i <= n; i++)
for(int j = 0; j <= m; j++)
if (!i || !j) f[i][j] = 1; //仅一行或者一列 【边界:f[i][0] = f[0][j]= 1】
else f[i][j] = f[i - 1][j] + f[i][j - 1];
printf("%d", f[n][m]);
return 0;
}
一维优化
#include
const int N = 12;
int f[N][N];
int main()
{
int n, m;
scanf("%d %d", &n, &m);
for (int i = 0; i <= m; i ++ ) f[i] = 1; //边界:仅一条直路可走
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
f[j] += f[j - 1];
printf("%d\n", f[m]);
return 0;
}
dfs
#include
using namespace std;
int res;
int n, m; //dfs需全局:或者麻烦自己多写两个参数hh
void dfs(int x, int y)
{
if (x == n && y == m) // 如果走到(n, m), 那么 res ++ 并返回
{
res ++;
return;
}
if (x < n) dfs(x + 1, y); // 往右走
if (y < m) dfs(x, y + 1); // 往下走
}
int main()
{
scanf("%d %d", &n, &m);
dfs(0, 0); // 从点 (0, 0) 开始爆搜
printf("%d\n", res);
return 0;
}
垫底抽风-组合数思想O(n+m)
首先将dp的数组打印出来,找下规律。
1 1 1 1 1 1 1 1 1
1 2 3 4 5 6 7 8 9
1 3 6 10 15 21 28 36 45
1 4 10 20 35 56 84 120 165
1 5 15 35 70 126 210 330 495
1 6 21 56 126 252 462 792 1287
1 7 28 84 210 462 924 1716 3003
1 8 36 120 330 792 1716 3432 6435
1 9 45 165 495 1287 3003 6435 12870
理解①如果你从左上往右下斜着看,不难发现这就是一个旋转后的杨辉三角
正常杨辉三角中的第n行,第m个数是 C n m = n ! m ! ( n − m ) ! C_{n}^{m} = \frac{n!}{m!(n−m)!} Cnm=m!(n−m)!n!
矩阵排列杨辉三角的第n行,第m个数 = C n + m m = ( n + m ) ! m ! ( n + m − m ) ! = ( n + m ) ! m ! n ! C_{n+m}^{m}=\frac{(n+m)!}{m!(n+m-m)!}=\frac{(n+m)!}{m!n!} Cn+mm=m!(n+m−m)!(n+m)!=m!n!(n+m)!
理解②(0, 0)走到点 (n,m)最少n + m步,n步往下走,m步往右走:等效从 n + m步中选出n步往下走 r e s = C n + m m res = C_{n+m}^{m} res=Cn+mm
#include
using namespace std;
const int N = 21; // n + m <= 20
int c[N][N]; //数值大就开long long
void init()//打表
{
for(int i = 0; i <= N; i++)
for(int j = 0; j <= i; j++) //注意定义域限制C[n][m] : n >= m
if(!j) c[i][j] = 1;
else c[i][j] = c[i - 1][j - 1] + c[i - 1][j]; //选或不选
}
int main()
{
init();
int n, m;
scanf("%d%d", &n, &m);
printf("%d", c[n + m][n]);
return 0;
}
不打表直接计算
#include
typedef long long LL;
using namespace std;
int n, m;
LL res = 1;
int main()
{
scanf("%d %d", &n, &m);
for (int i = m + 1, j = 2; i <= n + m; i ++) // i 枚举 (n + m)! , j 枚举 n!
{
res *= i;
while( j <= n && res % j == 0)
res /= j ++; // 边乘边除防止溢出
}
printf("%lld\n", res);
return 0;
}
给定一个整数 n,将数字 1∼n 排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数 n。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1≤n≤9
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
dfs
#include
using namespace std;
const int N = 10;
int n;
int path[N];
bool st[N];
void dfs(int u)
{
if(u == n)
{
for(int i = 0 ; i < n; i++)
printf("%d ", path[i]);
puts("");
}
for(int i = 1; i <= n; i++)
{
if(!st[i])
{
path[u] = i;
st[i] = true;
dfs(u + 1);
st[i] = false;
}
}
}
int main()
{
scanf("%d", &n);
dfs(0);
return 0;
}
next_permutation(q + 1, q + n + 1); 从1开始
当存在比当前字典序更大的排列时,next_permutation()返回真,直到没有更大的排列的时候才退出循环
#include
#include
using namespace std;
const int N = 10;
int q[N];
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) q[i] = i;
do
{
for(int i = 1; i <= n; i++)
printf("%d ", q[i]);
puts("");
}
while(next_permutation(q + 1, q + n + 1));
return 0;
}
leetcode有题目 {目前用不上:但有益于理解字典序}
other手写next_permutation()
#include
#include
using namespace std;
int a[12];
bool turn(int a[] ,int n)
{
int k = n - 1;
while(a[k-1] > a[k]) k--;//找到位置k
//当k的位置是0的时候,说明整个排列时递减的,这个排列的字典序最大
if(k == 0) return false;//因此返回false
k = k - 1;
int t = n - 1;
while(a[k] > a[t]) t--;//从后往前找到第一个大于a[k]的数字的位置
swap(a[k] , a[t]);//交换
reverse(a + k + 1, a + n);//翻转
return true;//返回true
}
int main()
{
int n;
cin>>n;
for(int i = 0 ; i < n ; i++) a[i] = i + 1;//输入
for(int i = 0 ; i < n ; i++) cout<<a[i]<<" ";//输出
cout<<endl;
while(turn(a, n))
{
//输出进行一个排序后的排列
for(int i = 0 ; i<n ; i++) cout<<a[i]<<" ";
cout<<endl;
}
return 0;
}
输入一个整数 n ,求斐波那契数列的第 n 项。
假定从 0 开始,第 0 项为 0。
数据范围
0≤n≤39
样例
输入整数 n=5
返回 5
class Solution {
public:
int Fibonacci(int n) {
int a = 0, b = 1, c;
while (n -- ){
c = a + b;
a = b, b = c;
}
return a;
}
};
// if(n == 0) return 0;
// if(n == 1) return 1;
// else return Fibonacci(n-1) + Fibonacci(n-2);
请实现一个函数,把字符串中的每个空格替换成"%20"。
数据范围
0≤ 输入字符串的长度 ≤1000。
注意输出字符串的长度可能大于 1000。
样例
输入:“We are happy.”
输出:“We%20are%20happy.”
STL
class Solution {
public:
string replaceSpaces(string &str) {
int t;//若找到' '返回第一个出现的下标
while((t = str.find(' ')) != string::npos){ //npos记:参数下标n的position
str.replace(t, 1, "%20");//(替换起始位置, 替换原串长度, 替换的字符串)
}
return str;
}
};
y总链接
(线性扫描) O(n)
这个题在C++里比较好做,我们可以从前往后枚举原字符串:
如果遇到空格,则在string类型的答案中添加 “%20”;
如果遇到其他字符,则直接将它添加在答案中;
但在C语言中,我们没有string这种好用的模板,需要自己malloc出char数组来存储答案。
此时我们就需要分成三步来做:
①遍历一遍原字符串,计算出答案的最终长度;
②malloc出该长度的char数组;
③再遍历一遍原字符串,计算出最终的答案数组;
时间复杂度分析
原字符串只会被遍历常数次,所以总时间复杂度是 O(n)。
class Solution {
public:
string replaceSpaces(string &str) {
string res;
for (auto x : str)
if (x == ' ')
res += "%20";
else
res += x;
return res;
}
};
(双指针扫描) O(n)
在部分编程语言中,我们可以动态地将原数组长度扩大,此时我们就可以使用双指针算法,来降低空间的使用:
首先遍历一遍原数组,求出最终答案的长度length;
将原数组resize成length大小;
使用两个指针,指针i指向原字符串的末尾,指针j指向length的位置;
两个指针分别从后往前遍历,如果str[i] == ’ ‘,则指针j的位置上依次填充’0’, ‘2’, ‘%’,这样倒着看就是"%20";如果str[i] != ’ ',则指针j的位置上填充该字符即可。
由于i之前的字符串,在变换之后,长度一定不小于原字符串,所以遍历过程中一定有i <= j,这样可以保证str[j]不会覆盖还未遍历过的str[i],从而答案是正确的。
(逆序添加02% --> %20)
class Solution {
public:
string replaceSpaces(string &str) {
int len = 0; //计算修改后的长度
for (auto c : str)
if (c == ' ')
len += 3; //%20占三位
else
len ++ ;
int i = str.size() - 1, j = len - 1;
str.resize(len);
while (i >= 0) //逆序添加02% --> %20
{
if (str[i] == ' ')
{
str[j -- ] = '0';
str[j -- ] = '2';
str[j -- ] = '%';
}
else str[j -- ] = str[i];
i -- ;
}
return str;
}
};
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。
请定义一个函数实现字符串左旋转操作的功能。
比如输入字符串"abcdefg"和数字 2,该函数将返回左旋转 2 位得到的结果"cdefgab"。
注意:
数据保证 n 小于等于输入字符串的长度。
数据范围
输入字符串长度 [0,1000]。
样例
输入:“abcdefg” , n=2
输出:“cdefgab”
string构造函数截取子串
string 有个构造函数:传入起点和结束点迭代器,可以构造出迭代器之间的字符串(
左闭右开
)。
分别构造出 s1 为 s[n] ~ 末尾。s2 为 s[0] ~ s[n-1]
返回 s1 + s2 即可.
class Solution {
public:
string leftRotateString(string str, int n) {
string s1(str.begin() + n, str.end());
string s2 (str.begin(), str.begin() + n);
return s1 + s2;
}
};
substr函数
substr 指定长度复制
形式 :
s.substr(pos, len) //不指定len时 ,默认s.size() - pos (从pos读到结束)
返回值:
string,包含s中从pos开始的len个字符的拷贝
(pos的默认值是0,len的默认值是s.size() - pos,即不加参数会默认拷贝整个s)
异常 :
若pos的值超过了string的大小,则substr函数会抛出一个out_of_range异常;
若pos+n的值超过了string的大小,则substr会调整n的值,只拷贝到string的末尾
class Solution {
public:
string leftRotateString(string str, int n) {
return str.substr(n) + str.substr(0,n);
}
};
请你写一个函数 StrToInt,实现把字符串转换成整数这个功能。
当然,不能使用 atoi 或者其他类似的库函数。
数据范围
输入字符串长度 [0,20]。
样例
输入:“123”
输出:123
注意:
你的函数应满足下列条件:
忽略所有行首空格,找到第一个非空格字符,可以是 ‘+/−’ 表示是正数或者负数,紧随其后找到最长的一串连续数字,将其解析成一个整数;
整数后可能有任意非数字字符,请将其忽略;
如果整数长度为 0,则返回 0;
如果整数大于 INT_MAX(231−1),请返回 INT_MAX;如果整数小于INT_MIN(−231) ,请返回 INT_MIN;
stringstream字符串输入输出流【string-->int】
class Solution {
public:
int strToInt(string str) {
stringstream s;
int res = 0;
if(str=="") return 0;
s << str;
s >> res;
return res;
}
};
sstream : stringstream
stringstream s; //
int res;
s << 114;
s << 514; //没有清空不会覆盖只会拼接 : ss == "114514"
s >> res; //res = 114514
s.clear(); //好习惯:判断下一个数据前, 清空s
特殊判断 + 符号 + ASCLL
class Solution {
public:
int strToInt(string str) {
int k = 0;
while (k < str.size() && str[k] == ' ') k ++ ; //去除前导空字符
long long res = 0;
bool is_minus = false;//标记正负
if (k < str.size())
{
if (str[k] == '-') is_minus = true, k ++ ;
else if (str[k] == '+') k ++ ;
}
while (k < str.size() && str[k] >= '0' && str[k] <= '9')
{
res = res * 10 + str[k] - '0'; //秦九昭
if (res > 1e11) break; //1ell隐式转long long
k ++ ;
}
if(is_minus) res = -res; //确定符号
if (res > INT_MAX) res = INT_MAX;
if (res < INT_MIN) res = INT_MIN;
return res;
}
};
y总代码
class Solution {
public:
int strToInt(string str) {
int k = 0;
while (k < str.size() && str[k] == ' ') k ++ ;
long long res = 0;
int minus = 1;//标记正负
if (k < str.size())
{
if (str[k] == '-') minus = -1, k ++ ;
else if (str[k] == '+') k ++ ;
}
while (k < str.size() && str[k] >= '0' && str[k] <= '9')
{
res = res * 10 + str[k] - '0'; //str[k] - '0' ,【 进位 + ASCLL转换】
if (res > 1e11) break;
k ++ ;
}
res *= minus; //确定符号
if (res > INT_MAX) res = INT_MAX;
if (res < INT_MIN) res = INT_MIN;
return res;
}
};
不用乘除法
a分配n*(n+1)个
char型(1字节)
, sizeof(a) = n * (n + 1) 字节,
a二进制数右移1位 >> 1 等效 十进制数a / 2
class Solution {
public:
int getSum(int n) {
char a[n][n+1];
return sizeof(a) >> 1;
}
};
高斯NB
注意
:多个乘除中:先除可以防溢出,但是可能计算错误【比如此题】
int型可能,但浮点数如double型可以先除再乘不会错
class Solution {
public:
int getSum(int n) {
return (long)(n + 1) * n / 2 ; //先除2可以防溢出,但是(int)1/2 == 0 ; 达到2.5e11 开long
}
};
递归
class Solution {
public:
int getSum(int n) {
int res = n;
(n > 0) && (res += getSum(n - 1));//利用逻辑运算符短路性质终止递归!![语法:其实这样写难为自己, 但NB]
return res;
}
};
给定单向链表的一个节点指针,定义一个函数在O(1)时间删除该结点。
假设链表一定存在,并且该节点一定不是尾节点。
数据范围
链表长度 [1,500]。
样例
输入:链表 1->4->6->8
删掉节点:第2个节点即6(头节点为第0个节点)
输出:新链表 1->4->8
将下一个节点的值复制到当前节点,然后将下一个节点删除即可。
时间复杂度
只有常数次操作,所以时间复杂度是 O(1)。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
auto p = node->next;
//node->val = p->val;
//node->next = p->next;
// 这两步的作用就是将 *(node->next) 赋值给 *node,所以可以合并成一条语句:
*node = *(node->next);
delete p; //好习惯,释放野指针
}
};
输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。
数据范围
链表长度 [0,500]。
样例
输入:1->3->5 , 2->4->5
输出:1->2->3->4->5->5
每次加入未选择的最小值-头插法[二路归并]
class Solution {
public:
ListNode* merge(ListNode* l1, ListNode* l2) { //前提:两个有序链表
auto dummy = new ListNode(-1), tail = dummy;
while (l1 && l2 ) { //其中一个遍历完,停止
if (l1 -> val < l2 -> val) { //放入值较小的, tail更新指向其位置
tail = tail -> next = l1;
l1 = l1 -> next;
}
else {
tail = tail -> next = l2; //等效tail->next = l2, tail = tail->next;
l2 = l2 -> next;
}
}
if(l1) tail->next = l1; //剩下的
if(l2) tail->next = l2;
return dummy->next;
}
};
二路归并 O(n)
新建头部的保护结点dummy(虚拟),设置cur指针指向dummy。
若当前l1指针指向的结点的值val比l2指针指向的结点的值val小,则令cur的next指针指向l1,且l1后移;否则指向l2,且l2后移。
然后cur指针按照上一部设置好的位置后移。
循环以上步骤直到l1或l2为空。
将剩余的l1或l2接到cur指针后边。
时间复杂度
两个链表各遍历一次,所以时间复杂度为O(n)
// 与cur与tail相同-换一种写法风格
class Solution {
public:
ListNode* merge(ListNode* l1, ListNode* l2) {
ListNode *dummy = new ListNode(0);
ListNode *cur = dummy;
while (l1 != NULL && l2 != NULL) {
if (l1 -> val < l2 -> val) {
cur -> next = l1;
l1 = l1 -> next;
}
else {
cur -> next = l2;
l2 = l2 -> next;
}
cur = cur -> next;
}
cur -> next = (l1 != NULL ? l1 : l2);
return dummy -> next;
}
};
定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。
思考题:
请同时实现迭代版本和递归版本。
数据范围
链表长度 [0,30]。
样例
输入:1->2->3->4->5->NULL
输出:5->4->3->2->1->NULL
mycode2022
class Solution {
public:
ListNode* reverseList(ListNode* head) {
auto tail = head;
ListNode* prev = nullptr; //不能auto 【左边是nullptr, 不被auto识别为ListNode*】
while(tail)
{
auto next = tail->next;
tail->next = prev;
prev = tail, tail = next; //tail = next等效tail = tail->next;
}
return prev;
}
};
Y总链接
链表操作-迭代 O(n)
翻转即将所有节点的next指针指向前驱节点。
由于是单链表,我们在迭代时不能直接找到前驱节点,所以我们需要一个额外的指针保存前驱节点。
同时在改变当前节点的next指针前,不要忘记保存它的后继节点。
空间复杂度分析:遍历时只有3个额外变量,所以额外的空间复杂度是 O(1)。
时间复杂度分析:只遍历一次链表,时间复杂度是 O(n)。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *prev = nullptr; //
ListNode *cur = head;
while (cur)
{
ListNode *next = cur->next; //next存cur->next
cur->next = prev;//head->next逆转后指向空,之后 1 <-- 2, 2 <-- 3 ......
prev = cur, cur = next;
}
return prev;
}
};
链表操作-递归 O(n)
首先我们先考虑 reverseList 函数能做什么,它可以翻转一个链表,并返回新链表的头节点,也就是原链表的尾节点。
所以我们可以先递归处理 reverseList(head->next),这样我们可以将以head->next为头节点的链表翻转,
并得到原链表的尾节点tail,此时head->next是新链表的尾节点,
我们令它的next指针指向head,并将head->next指向空即可将整个链表翻转,且新链表的头节点是tail。
空间复杂度分析:总共递归 n 层,系统栈的空间复杂度是 O(n),所以总共需要额外 O(n)的空间。
时间复杂度分析:链表中每个节点只被遍历一次,所以时间复杂度是 O(n)。
[过程简单:两个相邻点的next逆转方向, 边界修改需理解]
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) return head;//为空停止
ListNode *tail = reverseList(head->next);
head->next->next = head;//新链表尾结点head->next的next指向head
head->next = nullptr;//指向空
return tail;
}
};
bolg补充
C和C++中的NULL不等价:
NULL表示指针不指向任何对象,但是问题在于,NULL不是关键字,而只是一个宏定义(macro)。
在C++中,NULL却被明确定义为整常数0:
输入两个链表,找出它们的第一个公共结点。
当不存在公共节点时,返回空节点。
数据范围
链表长度 [1,2000]。
样例
给出两个链表如下所示:
A: a1 → a2
↘
c1 → c2 → c3
↗
B: b1 → b2 → b3
输出第一个公共节点c1
简写
【注释为数据结构定义,考研要写】
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *findFirstCommonNode(ListNode *headA, ListNode *headB) {
auto p = headA, q = headB;
while (p != q) { //找到p == q停止
p = p ? p->next : headB; //p非空往下走,否则(走到末尾)再往B走
q = q ? q->next : headA;
}
return p;
}
};
方法1:不同部分为a和b,公共部分为c :a + c + b = b + c + a;
让两个一起走, a走到头就转向b, b走到头转向a,则走a+b+c步后必在公共部分相遇。
class Solution {
public:
ListNode *findFirstCommonNode(ListNode *headA, ListNode *headB) {
auto p = headA, q = headB;
while(p != q) {
if(p) p = p->next;
else p = headB;
if (q) q = q->next;
else q = headA;
}
return p;
}
};
方法2:先计算出两个链表的长度,可以让比较长的先走两个链表长度之差的步数,两个再一起走就同时到公共节点。
class Solution {
public:
ListNode *findFirstCommonNode(ListNode *headA, ListNode *headB) {
auto p = headA, q = headB;
int la = 0, lb = 0;
for (auto t = headA; t; t = t->next) la ++;
for (auto t = headB; t; t = t->next) lb ++;
int k = la - lb;
if (la < lb) {
p = headB, q = headA;
k = lb - la;
}
while(k --) {
p = p->next;
}
while(p) {
if (p == q) return p;
p = p->next;
q = q->next;
}
return nullptr;
}
};
在一个排序的链表中,存在重复的节点,请删除该链表中重复的节点,重复的节点不保留。
数据范围
链表中节点 val 值取值范围 [0,100]。
链表长度 [0,100]。
样例1
输入:1->2->3->3->4->4->5
输出:1->2->5
样例2
输入:1->1->1->2->3
输出:2->3
线性扫描 O(n)
指针修改my理解:指针会直接改变指向地址单元的next指针域存放的指向地址
为了方便处理边界情况,我们定义一个虚拟元素 dummy 指向链表头节点。
然后从前往后扫描整个链表,每次扫描元素相同的一段,如果这段中的元素个数多于1个,则将整段元素直接删除。
整个链表只扫描一遍, 所以时间复杂度是 O(n)。
class Solution {
public:
ListNode* deleteDuplication(ListNode* head) {
auto dummy = new ListNode(-1); //虚拟头结点dummy, 可以保证头结点不被删去
dummy->next = head;
auto p = dummy;
while (p->next) { //从第一个元素[首结点]开始判断
auto q = p->next; //q一直走判断与当前位p->next->val的关系
while (q && p->next->val == q->val) q = q->next; //若有重复元素, p->next->val != q->val的位置(跳过重复段)
if (p->next->next == q) p = p->next; // p与q之间只隔一个点保留 p指向此点
else p->next = q; //删除重复元素段:直接p->next指向q代表的不重复位置
}
return dummy->next; //返回头结点地址,即整段链表
}
};
统计一个数字在排序数组中出现的次数。
例如输入排序数组 [1,2,3,3,3,3,4,5] 和数字 3,由于 3 在这个数组中出现了 4 次,因此输出 4。
数据范围
数组长度 [0,1000]。
样例
输入:[1, 2, 3, 3, 3, 3, 4, 5] , 3
输出:4
cnt统计
class Solution {
public:
int getNumberOfK(vector<int>& nums , int k) {
int cnt = 0;
for(auto i: nums) if(i == k) cnt++;
return cnt;
}
};
有序多重集合multiset
class Solution {
public:
int getNumberOfK(vector<int>& nums , int k) {
multiset<int> s;
for(int x : nums) s.insert(x);
return s.count(k); //计数k个数
}
};
lower_bound和upper_bound
class Solution {
public:
int getNumberOfK(vector<int>& nums , int k) {
auto l = lower_bound(nums.begin(), nums.end(), k); //正序查找第一个k所在下标
auto r = upper_bound(nums.begin(), nums.end(), k); //右往左(逆序)
return r - l;//个数 = (最后一个 - 第一个)
}
};
一个长度为 n−1 的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围 0 到 n−1 之内。
在范围 0 到 n−1 的 n 个数字中有且只有一个数字不在该数组中,请找出这个数字。
数据范围
1≤n≤1000
样例
输入:[0,1,2,4]
输出:3
简版
class Solution {
public:
int getMissingNumber(vector<int>& nums) {
for (int i = 0; i < nums.size(); i++)
if (nums[i] != i) return i; //题目说明只缺失一个:对号入座
return nums.size();特殊情况:当所有数都满足nums[i] == i时, 表示缺失的是n = nums.size()
}
};
y总
二分 O(logn)
这道题目给定的是递增数组,假设数组中第一个缺失的数是 xx,那么数组中的数如下所示;
从中可以看出,数组左边蓝色部分都满足nums[i] == i,数组右边橙色部分都不满足nums[i] == i,因此我们可以二分出分界点 x 的值。
另外要注意特殊情况:当所有数都满足nums[i] == i时,表示缺失的是 n
时间复杂度分析
二分中的迭代只会执行 O(logn) 次,因此时间复杂度是 O(logn)。
class Solution {
public:
int getMissingNumber(vector<int>& nums) {
if (nums.empty()) return 0;
int l = 0, r = nums.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (nums[mid] != mid) r = mid; //递增且仅有一个缺失 <==> 对不上号:缺失!
else l = mid + 1;
}
if (nums[r] == r) r ++ ; //特殊情况:当所有数都满足nums[i] == i时, 表示缺失的是n(最后一位)
return r;
}
};
数学差值法 O(n)
nums数组中的数的和sum = n * (n + 1) / 2 - 缺失值
求sum : 缺失值 = 连续递增求和[高斯] - sum
class Solution {
public:
int getMissingNumber(vector<int>& nums) {
int n=nums.size(),sum = 0;
for(auto &x:nums) sum += x;
return n * (n+1) / 2 - sum; //注意数值大小
}
};
输入一个整数数组,实现一个函数来调整该数组中数字的顺序。
使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分。
数据范围
数组长度 [0,100]。
样例
输入:[1,2,3,4,5]
输出: [1,3,5,2,4]
朴素:先放奇数,后放偶数
class Solution {
public:
vector<int> res;
void reOrderArray(vector<int> &array) {
for(int x:array)
if(x % 2) res.push_back(x);
//插奇数。
for(int x:array)
if(x % 2 == 0) res.push_back(x);
//插偶数。
array = res; //void没有返回值:修改原数组指向res数组
}
};
快排式双指针扫描 O(n)
用两个指针分别从首尾开始,往中间扫描。扫描时保证第一个指针前面的数都是奇数,第二个指针后面的数都是偶数。
每次迭代时需要进行的操作:
第一个指针一直往后走,直到遇到第一个偶数为止;
第二个指针一直往前走,直到遇到第一个奇数为止;
交换两个指针指向的位置上的数,再进入下一层迭代,直到两个指针相遇为止;
时间复杂度:
当两个指针相遇时,走过的总路程长度是 n,所以时间复杂度是 O(n)。
class Solution { //题目说把偶数全放后面,奇数全放前面,但没有说需要有序 , 采用快排交换法
public:
void reOrderArray(vector<int> &array) {
int l = 0, r = array.size() - 1;
while (l < r) {
while (l < r && array[l] % 2 == 1) l ++ ; //类似快排
while (l < r && array[r] % 2 == 0) r -- ;
if (l < r) swap(array[l], array[r]);
}
}
};
输入一个链表的头结点,按照 从尾到头 的顺序返回节点的值。
返回的结果用数组存储。
数据范围
0≤ 链表长度 ≤1000。
样例
输入:[2, 3, 5]
返回:[5, 3, 2]
vector
单链表只能从前往后遍历,不能从后往前遍历。
因此我们先从前往后遍历一遍输入的链表,将结果记录在答案数组中。
最后再将得到的数组逆序即可。
时间复杂度分析
链表和答案数组仅被遍历了常数次,所以总时间复杂度是 O(n)。
法一:reverse 答案数组. 时间:O(n);空间:O(n). 4ms; 8.5MB
class Solution {
public:
vector<int> printListReversingly(ListNode* head) {
vector<int> res;
while (head) {
res.push_back(head->val);
head = head->next;
}
return vector<int>(res.rbegin(), res.rend()); //返回res的翻转数组
}
};
递归
法二:递归. 时间:O(n);空间:栈空间O(n). 4ms; 10.8MB
class Solution {
public:
vector<int> printListReversingly(ListNode* head) {
if (!head) return {};//特判
auto res = printListReversingly(head->next);
res.push_back(head->val); //最后一个结点第一个返回:等效翻转
return res;
}
};
辅助【栈】法
法三:辅助【栈】法. 时间:O(n);空间:O(n). 4ms; 8.5MB
class Solution {
public:
vector<int> printListReversingly(ListNode* head) {
stack<int> s;
while (head) {
s.push(head->val); // 入栈
head = head->next;
}
int n = s.size();
vector<int> res(n);
for (int i = 0; i < n; i ++ ) {
res[i] = s.top(); //取栈顶元素
s.pop();
}
return res;
}
};
请用栈实现一个队列,支持如下四种操作:
push(x) – 将元素x插到队尾;
pop() – 将队首的元素弹出,并返回该元素;
peek() – 返回队首元素;
empty() – 返回队列是否为空;
注意:
你只能使用栈的标准操作:push to top,peek/pop from top, size 和 is empty;
如果你选择的编程语言没有栈的标准库,你可以使用list或者deque等模拟栈的操作;
输入数据保证合法,例如,在队列为空时,不会进行pop或者peek等操作;
数据范围
每组数据操作命令数量 [0,100]。
样例
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // returns 1
queue.pop(); // returns 1
queue.empty(); // returns false
【用两个栈实现队列】暴力:
stk全部弹出放入辅助栈cache,删除辅助栈cache的栈顶元素【等效队头元素】再全部放回stk
算法
(栈,队列) O(n)
这是一道基础题,只要把功能实现对就可以,不需要考虑运行效率。
我们用两个栈来做,一个主栈,用来存储数据;一个辅助栈,用来当缓存。
push(x),我们直接将x插入主栈中即可。
pop(),此时我们需要弹出最先进入栈的元素,也就是栈底元素。我们可以先将所有元素从主栈中弹出,压入辅助栈中。则辅助栈的栈顶元素就是我们要弹出的元素,将其弹出即可。然后再将辅助栈中的元素全部弹出,压入主栈中。
peek(),可以用和pop()操作类似的方式,得到最先压入栈的元素。
empty(),直接判断主栈是否为空即可。
时间复杂度分析
push():O(1)O(1);
pop(): 每次需要将主栈元素全部弹出,再压入,所以需要 O(n) 的时间;
peek():类似于pop(),需要 O(n) 的时间;
empty():O(1);
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* bool param_4 = obj.empty();
*/
class MyQueue {
public:
/** Initialize your data structure here. */
stack<int> stk, cache;
MyQueue() { //构造函数
}
/** Push element x to the back of queue. */
void push(int x) { //队尾入队等效入栈
stk.push(x);
}
void copy(stack<int> &a, stack<int> &b) { //辅助函数 【把stk栈所有元素转移到辅助缓存区cache(顺序刚好相反:如原栈顶变栈底)】
while (a.size()) { //满足了健壮性检查
b.push(a.top());
a.pop();
}
}
/** Removes the element from in front of queue and returns that element. */
int pop() {
copy(stk, cache); //【把stk栈所有元素转移到辅助缓存区cache】
int res = cache.top(); //栈顶为最后一个元素
cache.pop(); //第一个进栈元素出栈 : 等效出队
copy(cache, stk); //【出队后剩下元素放回stk等效实现队列队头出队】
return res;
}
/** Get the front element. */
int peek() { //q.first()
copy(stk, cache);
int res = cache.top(); //不用出队直接获取队头元素
copy(cache, stk);
return res;
}
/** Returns whether the queue is empty. */
bool empty() {
return stk.empty(); //判断是否为空
}
};
输入 n 个整数,找出其中最小的 k 个数。
注意:
输出数组内元素请按从小到大顺序排序;
数据范围
1≤k≤n≤1000
样例
输入:[1,2,3,4,5,6,7,8] , k=4
输出:[1,2,3,4]
堆排序 O(nlogk)
大根堆:【
STL堆特性
:元素放入堆会自动排序(父>孩子):大根堆的堆顶最大
,小根堆相反】
priority_queue
大根堆 :priority_queue , greater > heap; 小根堆
维护数量为k的大根堆 :堆顶为最大值
【res数组存答案】res不断取堆顶(同时堆顶出堆)存入最大值,为从大到小,若要得到从小到大前k大的元素序列还需翻转一次res
未达k个数直接放入堆,大于k个数, 放入堆自动排序只需删除堆顶,即不断删除当前共k+1个元素中的最大元素(第k+1大), 维护k个最小元素
y总:
(堆排序) O(nlogk)
维护一个大小为k的大根堆,将数组元素都push进堆,当堆中的数大于k时弹出堆顶元素。
注意弹出堆顶的顺序是从大到小的k个数,要进行逆序操作
时间复杂度分析:建堆的时间复杂度是O(logk),要进行n次建堆的操作。
class Solution {
public:
vector<int> getLeastNumbers_Solution(vector<int> input, int k) { //input:传入数组, k:即求数组中前k小的元素
vector<int> res;
priority_queue<int> heap; // 大根堆
for(auto x : input)
{
heap.push(x);
if(heap.size() > k) heap.pop(); //元素个数到达k个后开始筛选,即加入元素后排序, 堆顶为k+1大的元素, 不断删除k+1大的元素
}
while(heap.size()) //不断出队取堆顶元素等效取第k大, 第k - 1大 ... 第1大的元素
{
res.push_back(heap.top()); //答案放入res数组:此时才能从大到小排序到数组中
heap.pop();
}
reverse(res.begin(),res.end()); //大根堆从大到小存放 ,从小到大输出需要逆序翻转!!!
return res;
}
};
快速选择 O(klogn)
运用快速排序的思想,每次快速选择会将一个数放置到正确的位置(即满足左边的数都比它小,右边的数都比它大),因此我们可以对原数组做k次快速选择。
时间复杂度分析:一次快速选择的时间复杂度是O(logn),进行k次,时间复杂度为O(klogn)
class Solution {
public:
vector<int> getLeastNumbers_Solution(vector<int> input, int k) {
vector<int> res;
for(int i = 1;i <= k; i++)
res.push_back(quick_sort(input, 0, input.size() - 1, i));
return res;
}
int quick_sort(vector<int>& q,int l,int r,int k)
{
if(l >= r) return q[l];
int x = q[l + r >> 1], i = l - 1, j = r + 1;
while(i < j)
{
do i++; while(x > q[i]);
do j--; while(x < q[j]);
if(i < j) swap(q[i], q[j]);
}
if(k <= j - l + 1) return quick_sort(q, l, j, k); //分两段: 前一段长度 j - L + 1 若 > k :说明k在前一段
else return quick_sort(q, j + 1, r, k - (j - l + 1)); //否则在后一段的找剩下除去前一段的还需添加的个数
}
};
mycode2022
class Solution {
public:
vector<int> getLeastNumbers_Solution(vector<int> input, int k) {
vector<int> res;
for(int i = 1; i <= k; i++)
{
int x = quick_sort(input, 0, input.size() - 1, i);
res.push_back(x);
}
return res;
}
int quick_sort(vector<int> &q, int l, int r, int k)
{
if(l >= r) return q[l];
int x = q[l + r >> 1] , i = l - 1, j = r + 1;
while(i < j)
{
do i++; while(x > q[i]);
do j--; while(x < q[j]);
if(i < j) swap(q[i] , q[j]);
}
if(k <= j - l + 1) quick_sort(q, l, j, k);
else quick_sort(q, j + 1, r, k - (j - l + 1));
}
};
输入一个数组和一个数字 s,在数组中查找两个数,使得它们的和正好是 s。
如果有多对数字的和等于 s,输出任意一对即可。
你可以认为每组输入中都至少含有一组满足条件的输出。
数据范围
数组长度 [1,1002]。
样例
输入:[1,2,3,4] , sum=7
输出:[3,4]
思想: 遍历一遍:当前准备放入的元素 与 已放入的元素 判断是否存在满足 + hash表优化为O(n)
class Solution {
public:
vector<int> findNumbersWithSum(vector<int>& nums, int target)
{
unordered_map<int, int> hash;//创建哈希表[无序二元组-散列]
for (int i = 0; i < nums.size(); i ++)
{
int r = target - nums[i]; //判断之前放入的元素有没有满足可以组成目标值S的元素
if(hash[r]) return {r, nums[i]}; // vector({r, nums[i]}) :c++语法可以省略"()"
hash[nums[i]] ++;//不断放入【标记值存在】
}
return vector<int>(); //更标准写法:防止返回值报错警告
}
};
unordered_set
(使用count时间复杂度会慢一些)
class Solution {
public:
vector<int> findNumbersWithSum(vector<int>& nums, int target) {
unordered_set<int> hash;
for (auto x: nums)
if (hash.count(target - x)) return {x, target - x};
else hash.insert(x);
return {};
}
};
暴力:O( n 2 n^2 n2)
class Solution {
public:
vector<int> findNumbersWithSum(vector<int>& nums, int target)
{
for(int i = 0; i < nums.size(); i++)
for(int j = 0; j < i; j++)
{
if(nums[i] + nums[j] == target) return {nums[j] ,nums[i]};
}
return {};
}
};
哈希表:对于数x,目的是找到target - x
暴力法的时间复杂度较高的原因是寻找 target - x 的时间复杂度过高。需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,[x, target - x] 即为答案。
使用哈希表,可以将寻找 target - x 的时间复杂度降低到从O(N) 降低到 O(1)。
创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在target - x,如果有返回[x, target - x]。如果没有,将 x 插入到哈希表中。
在i从0到nums.size() - 1过程总,j只遍历了一次,所以时间复杂度O(n);开辟了数组大小的哈希表,所以空间复杂度是O(n)
class Solution {
public:
vector<int> findNumbersWithSum(vector<int>& nums, int target)
{
unordered_map<int, int> hash;//创建哈希表[无序二元组-散列]
for (int i = 0; i < nums.size(); ++i)
{
if(hash[target - nums[i]]) return {target - nums[i],nums[i]}; //找到逆序输出数组 等效reverse()
hash[nums[i]]++;//nums[i]出现次数加1
}
return vector<int>();
}
};
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
数据范围
矩阵中元素数量 [0,400]。
样例
输入:
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12]
]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
蛇形矩阵
(模拟) O(n^2)
我们顺时针定义四个方向:上右下左。
从左上角开始遍历,先往右走,走到不能走为止,然后更改到下个方向,再走到不能走为止,依次类推,遍历 n2 个格子后停止。
时间复杂度
矩阵中每个格子遍历一次,所以总时间复杂度是 O(n^2)。
class Solution {
public:
vector<int> printMatrix(vector<vector<int> > matrix) {
vector<int> res;
if (matrix.empty()) return res;
int n = matrix.size(), m = matrix[0].size(); // n行m列【注意如何vector二维得到n, m】
vector<vector<bool>> st(n, vector<bool>(m, false)); //BFS需要st辅助标记走过 创建n行:每行为一个初始化vector(m, false)
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int x = 0, y = 0, d = 1;
for (int i = 0; i < n * m; i++)
{
res.push_back(matrix[x][y]); //把原数组按蛇形矩阵遍历放入res
st[x][y] = true;
int a = x + dx[d], b = y + dy[d];
if (a < 0 || a >= n || b < 0 || b >= m || st[a][b])
{
d = (d + 1) % 4;
a = x + dx[d], b = y + dy[d];
}
x = a, y = b;
}
return res;
}
};
输入一组数字(可能包含重复数字),输出其所有的排列方式。
数据范围
输入数组长度 [0,6]。
样例
输入:[1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
返回字典序的下一个排列next_permutation(起始下标, 结束下标)
class Solution {
public:
vector<vector<int>> permutation(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<vector<int>> res;
do {
res.push_back(nums);
} while (next_permutation(nums.begin(), nums.end()));
return res;
}
};
y总的写法:my_think这种不是很推荐hh
class Solution {
public:
vector<vector<int>> permutation(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<vector<int>> res;
do res.push_back(nums); while (next_permutation(nums.begin(), nums.end()));
return res;
}
};
dfs+二进制表示
算法1
(回溯) O(n!)
由于有重复元素的存在,这道题的枚举顺序和 Permutations 不同。
先将所有数从小到大排序,这样相同的数会排在一起;
从左到右依次枚举每个数,每次将它放在一个空位上;
对于相同数,我们人为定序,就可以避免重复计算:我们在dfs时记录一个额外的状态,记录上一个相同数存放的位置 start,我们在枚举当前数时,只枚举 start+1,start+2,…,n 这些位置。
不要忘记递归前和回溯时,对状态进行更新。
时间复杂度分析:搜索树中最后一层共 n! 个节点,前面所有层加一块的节点数量相比于最后一层节点数是无穷小量,可以忽略。且最后一层节点记录方案的计算量是O(n),所以总时间复杂度是 O(n×n!)。
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
vector<vector<int>> permutation(vector<int>& nums) {
sort(nums.begin(), nums.end());
path.resize(nums.size());
dfs(nums, 0, 0, 0);
return res;
}
void dfs(vector<int>& nums, int u, int start, int state){ //state二进制数
if (u == nums.size()){
res.push_back(path);
return;
}
//第一个数 或者 和上一个数不同 :可以从0开始 【可能有重复数如 1 1 2 2 3 4】
if (!u || nums[u] != nums[u - 1]) start = 0;
for (int i = start; i < nums.size(); i++){ //二进制表示:1被用过
if (!(state >> i & 1)){ //第i位没有被用过:i从0开始 i = 0时判断最低位......
path[i] = nums[u];
dfs(nums, u + 1, i + 1, state + (1 << i)); //state + (1 << i)标记已用过的位置
}
}
}
};
朴素dfs写法
class Solution {
public:
vector<bool> st;
vector<int> path;
vector<vector<int>> res;
vector<vector<int>> permutation(vector<int>& nums) {
sort(nums.begin(), nums.end());
st = vector<bool>(nums.size(), false);
path = vector<int>(nums.size());
dfs(nums, 0, 0);
return ans;
}
void dfs(vector<int>& nums, int u, int start)
{
if (u == nums.size())
{
res.push_back(path);
return;
}
for (int i = start; i < nums.size(); i ++ )
if (!st[i])
{
st[i] = true;
path[i] = nums[u];
if (u + 1 < nums.size() && nums[u + 1] != nums[u])
dfs(nums, u + 1, 0); //可能包含重复数字
else
dfs(nums, u + 1, i + 1);
st[i] = false;
}
}
};
给定一个长度为 n 的序列 A,A 中的数各不相同。
对于 A 中的每一个数 Ai,求:
min1≤j 以及令上式取到最小值的 j(记为 Pi)。若最小值点不唯一,则选择使 Aj 较小的那个。
输入格式
第一行输入整数 n,代表序列长度。
第二行输入 n 个整数A1…An,代表序列的具体数值,数值之间用空格隔开。
输出格式
输出共 n−1 行,每行输出两个整数,数值之间用空格隔开。
分别表示当 i 取 2∼n 时,对应的 min1≤j
数据范围
n≤105,|Ai|≤109
输入样例:
3
1 5 3
输出样例:
4 1
2 1
从前往后逐个加入set集合(底层平衡树实现:加入后自动排序)
【边加入变判断】比较左右两边选取与自己差值较小的[比k小的第一个数, k, 比k大的第一个数]
理解:当前一个数加入set, 首先在结尾, 然后set排序, 若有比它大的数则排在它的右边,
若没有则会超过边界, 所以先设置两个边界值[-INF, INF]防止下标越界
lower_bound( begin,end,num):
从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,
找到返回该数字的地址,不存在则返回end。
通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num):
从数组的begin位置到end-1位置二分查找第一个大于num的数字,
找到返回该数字的地址不存在则返回end。
通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
详细用法三参数与四参数
lower_bound(起始下标, 终点下标, 目标元素)
用于在已排好序的数组中找出>= 目标元素
的下标最小
的元素的地址
upper_bound(起始下标, 终点下标, 目标元素)
用于在已排好序的数组中找出< 目标元素
的下标最小
的元素的地址
yxc
#include
#include
#include
using namespace std;
int n;
int a[100010];
int main()
{
set<pair<int, int>> a;
a.insert({2000000000, -1}); //哨兵
a.insert({-2000000000, -1});
cin >> n;
for (int i = 1; i <= n; i ++ ) //set插入时内部自动排序
{
int x;
cin >> x;
if (i > 1)
{
auto right = a.lower_bound({x, -1}); // 整不会了{pair}
auto left = a.upper_bound({x, -1});
left -- ;
//计算差值
long long r = (long long)right->first - x;
long long l = (long long)x - left->first;
if (l <= r) //比较差值-输出较小的差值
cout << l << ' ' << left->second << endl;
else
cout << r << ' ' << right->second << endl;
}
a.insert({x, i});
}
return 0;
}
因为出在链表这一章节, 所以y总用链表做, 实际写难为自己了hh(中等)
#include
#include
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef pair<LL, int> PII;
const int N = 1e5 + 10;
int n;
int l[N], r[N]; //模拟双向链表
int p[N]; //存最小值下标
PII a[N], ans[N]; //{值, 下标}
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ )
{
cin >> a[i].x;
a[i].y = i; //每个点在双链表中的位置
}
sort(a + 1, a + 1 + n); //从小到大
a[0].x = -3e9, a[n + 1].x = 3e9; //简化边界判断, 用左右哨兵防止使用边界更新
for (int i = 1; i <= n; i ++ )
{
l[i] = i - 1, r[i] = i + 1; //存左右指针指向的下标
p[a[i].y] = i; //每个点在双链表中的位置
}
for (int i = n; i > 1; i -- )
{ //找到当前元素在双链表中的位置 且取左右指针指向的位置【前一个小和后一个大的数比较哪个差值更小】
int j = p[i], left = l[j], right = r[j];
LL left_value = abs(a[left].x - a[j].x);
LL right_value = abs(a[right].x - a[j].x);
if (left_value <= right_value) ans[i] = {left_value, a[left].y};
else ans[i] = {right_value, a[right].y};
l[right] = left, r[left] = right;
}
for (int i = 2; i <= n; i ++ ) //第一个数最小(不存在更小的)
printf("%d %d\n",ans[i].x, ans[i].y);
return 0;
}
/*
#include
#include
#include
#include
#define x first
#define y second
using namespace std;
typedef pair PII;
int n;
set s;
int main()
{
int x;
cin >> n >> x;
s.insert({x, 1});
for (int i = 2; i <= n; i ++ )
{
int x;
cin >> x;
s.insert({x, i});
set::iterator it = s.find({x, i});
pair res = {-1, -1}; //边界
bool flag = false;
if (it != s.begin()){
flag = true;
-- it;
res = {x - (*it).x, (*it).y};
}
if (flag) it++;
it++;
if ((res.y == -1) || (it != s.end() && (*it).x - x < res.x))
res = {(*it).x - x, (*it).y};
cout << res.x << ' ' << res.y << ' ' << endl;
}
return 0;
}
*/
输入一个 32 位整数,输出该数二进制表示中 1 的个数。
注意:
负数在计算机中用其绝对值的补码来表示。
数据范围
−100≤ 输入整数 ≤100
样例1
输入:9
输出:2
解释:9的二进制表示是1001,一共有2个1。
样例2
输入:-2
输出:31
解释:-2在计算机里会被表示成11111111111111111111111111111110,
一共有31个1。
位运算 O(logn)
(位运算) O(logn)
迭代进行如下两步,直到 n 变成0为止:
如果 n 在二进制表示下末尾是1,则在答案中加1;
将 n 右移一位,也就是将 n 在二进制表示下的最后一位删掉;
这里有个难点是如何处理负数。
在C++中如果我们右移一个负整数,系统会自动在最高位补1,这样会导致 n 永远不为0,就死循环了。
解决办法是把 nn 强制转化成无符号整型,这样 nn 的二进制表示不会发生改变,但在右移时系统会自动在最高位补0。
时间复杂度
每次会将 n 除以2,最多会除 lognlogn 次,所以时间复杂度是 O(logn)。
class Solution {
public:
int NumberOf1(int n) {
int res = 0;
unsigned int un = n;
while (un) res += un & 1, un >>= 1;
return res;
}
};
lowbit(int x)
class Solution {
public:
int lowbit(int x) {
return x & -x;
}
int NumberOf1(int n) {
int cnt = 0;
while(n)
{
n -= lowbit(n);
cnt ++;
}
return cnt;
}
};
n >> k & 1
判断n的二进制的第k位
是否为1[从最低位为第0位
算起] (第k位为1时, 逐位相与结果为1)
n & 1:n最低位是否为1【n是否为奇数】
遍历占用的空间 : int占用4B = 32bit (二进制32位)
class Solution {
public:
int NumberOf1(int n) {
int res = 0;
for (int i = 0; i < 32; i ++ )
res += n >> i & 1;
return res;
}
};
先说一下lowbit是什么东西:
lowbit(x)表示x的最后一位1,比如10的二进制表示是1010,那lowbit(x)就应该是(10)2也就2。是因为C++中的负数使用补码表示的:而补码等于反码(就是把x的二进制中的每一位都取反)+ 1
所以x & (-x) = x & (~x+1)。
等效的n & n - 1
n & (n - 1)的结果:n最右边的1变成0,比如n为6
110 & 101 —> 100
循环直到n为0为止
class Solution {
public int NumberOf1(int n){
int cnt = 0;
while(n != 0){
n=n & (n - 1);
count++;
}
return cnt;
}
}
c++自带函数
该函数是C++自带的库函数,内部实现是用查表实现的。
作用:统计数字在二进制下“1”的个数。
class Solution {
public:
int NumberOf1(int n) {
return __builtin_popcount(n);
}
};
给定 N 个三元组 (x,y,z),其中 x 是整数,y 是浮点数,z 是字符串。
请你按照 x 从小到大的顺序将这些三元组打印出来。
数据保证不同三元组的 x 值互不相同。
输入格式
第一行包含整数 N。
接下来 N 行,每行包含一个整数 x,一个浮点数 y,一个字符串 z,表示一个三元组,三者之间用空格隔开。
输出格式
共 N 行,按照 x 从小到大的顺序,每行输出一个三元组。
注意,所有输入和输出的浮点数 y 均保留两位小数。
数据范围
1≤N≤10000,
1≤x,y≤105,
字符串总长度不超过 105。
输入样例:
5
32 1.36 nsyiupnnhc
18 4.53 fmofzwrah
33 4.86 wzuymbm
1 3.93 gtnrwcebt
31 4.53 gcllxioc
输出样例:
1 3.93 gtnrwcebt
18 4.53 fmofzwrah
31 4.53 gcllxioc
32 1.36 nsyiupnnhc
33 4.86 wzuymbm
补充:快捷键:双击选中块
y总struct结构体
*printf函数输出字符串是针对char 的,即printf只能输出c语言的内置数据类型,而string不是c语言的内置数据类型。
如需输出string对象中的字符串,可以使用string的成员函数c_str(),该函数返回字符串的首字符的地址。
#include
#include
#include
using namespace std;
const int N = 10010;
struct Data
{
int x;
double y;
string z;
bool operator< (const Data &t) const //按题目规则重载小于号
{
return x < t.x;
}
}a[N];
int main()
{
int n;
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> a[i].x >> a[i].y >> a[i].z; //有string
sort(a, a + n); //sort内部使用到重载后的小于号排序
for (int i = 0; i < n; i ++ )
printf("%d %.2lf %s\n", a[i].x, a[i].y, a[i].z.c_str()); //用C语言输出string 转char类型 str.c_str()
return 0;
}
pair多层嵌套
#include
#include
#include
#include
#define x first
#define y second
using namespace std;
const int N = 10010;
typedef pair<int, pair<double, string> > PIDS;
vector<PIDS> ans;
int n, a;
double b;
string s;
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ )
{
cin >> a >> b >> s;
ans.push_back({a, {b, s}});
}
sort(ans.begin(), ans.end()); //vector等容器使用sort
for (auto i: ans)
printf("%d %.2lf %s\n",i.x, i.y.x, i.y.y.c_str()); //string转char 才能 %s输入输出
return 0;
}
map + pair
map.insert() 以及 set.insert() 有序无序unordered都有insert
map超完整补充
map哈希 (key, value)
count() 返回指定元素出现的次数, (帮助评论区理解: 因为key值不会重复,所以只能是1 or 0)
#include
#include
#include
#include
#define x first
#define y second
using namespace std;
const int N = 10010;
typedef pair<double, string> PDS;
map<int, PDS> res;
int n;
int main()
{
cin >> n;
int a;
double b;
string c;
for (int i = 0; i < n; i ++ )
{
cin >> a >> b >> c;
res.insert({a, {b, c}}); //对应pair多层嵌套
}
for (auto i : res)
printf("%d %.2f %s\n", i.x, i.y.x, i.y.y.c_str());
// 迭代器写法 [类似指针]
// map::iterator it;
// for (it = res.begin(); it != res.end(); it ++ )
// printf("%d %.2f %s\n", it->x, it->y.x, it->y.y.c_str());
return 0;
}