hw笔试知识学习记录
笔试题目
机考的经验和练习题网站
常考知识点:
- 基本操作:输入输出处理(重点),字符串操作与ASCii码(重点)
- 数据结构:一维数组,栈,队列
- 编程思想:暴力法(重点),递归
- 算法:排列组合(重点),快速排序(重点),二分查找,位运算,滑动窗口,深度优先搜索(进阶),广度优先搜索(进阶),搜索回溯(进阶)
登录牛客网华为机试专栏练习并熟悉机试环境:https://www.nowcoder.com/ta/huawei
机考辅导
- 需要开摄像头,不能访问其他网页或查找资源,可以在本地IDE调试好后拷贝到牛客网上再调试
- 多刷一下各种类型算法题,难度中等及以上,在牛客有或力扣上刷都可以
- 熟悉牛客网考试环境,和本地IDE,力扣有差异
- ACM模式,需要解决输入输出
- 一定不要死磕某一题,三题得分加起来达到及格分即可
逐个击破
牛客网环境check
输入输出处理
- 核心代码模式处理
不需要处理任何输入输出,直接返回值即可。 - ACM 模式
你的代码需要处理输入输出,请使用如下样例代码读取输入和打印输出:
#include <iostream>
using namespace std;
int main() {
int a, b;
while (cin >> a >> b) { // 注意 while 处理多个 case 。 这个while以后用起来
cout << a + b << endl;
}
}
// 64 位输出请用 printf("%lld")
python解法过目:https://pycoder.blog.csdn.net/article/details/124648380
刷题
总结
数据结构
- set和map的key是有序的 unorder_set和unorder_map无序,需要不同的头文件,另外,multimap和multiset的key允许重复, key有序
- string常用方法
这个整型变量会参与到地址运算吗?(对于整型变量这非常常见,例如用作数组以及stl的索引),如果是,那么不要用int了。
在索引访问、指针地址相关的运算上,使用ptrdiff_t和unsigned_int,应当是规范的C++的统一标准。否则混用的话,必然会出现大片的编译器警告,并且降低程序效率。
- 优先队列 大小堆 priority_queue
参考大佬博客:https://www.cnblogs.com/huashanqingzhu/p/11040390.html
基本用法:
#include <iostream>
#include <algorithm>
#include<time.h>
#include<vector>
#include<queue>
#include"assert.h"
using namespace std;
//重写运算符>
//方法1
class tmp1 //运算符重载<
{
public:
int x;
tmp1(int a) { x = a; }
bool operator<(const tmp1& a) const //默认左边是this指针
{
return x < a.x; //大顶堆
}
};
struct cmp2 {
bool operator() (tmp1 lhs, tmp1 rhs) {
return lhs.x > rhs.x;
}
};
//重写比较函数
struct cmp {
bool operator() (int lhs, int rhs) {
return lhs > rhs;
}
};
//priority_queue
int main() {
int n, k, t;
while (cin >> n >> k) {
vector<int> nums;
priority_queue<int, vector<int>, cmp> min_queue;
priority_queue<tmp1, vector<tmp1>, cmp2> tmp1_min_deque;
for (int i = 0; i < n; i++) {
cin >> t;
nums.push_back(t);
min_queue.push(t);
tmp1_min_deque.push(t);
}
cout << "nums中的前k个值: ";
int t = k;
for (int i = 0; i < k; i++) {
if (i < n) cout << nums[i] << " ";
}
cout << endl;
cout << "min_queue中的前k个值: ";
while (t-- && t <= min_queue.size()) {
cout << min_queue.top() << " ";
min_queue.pop();
}
cout << endl;
cout << "tmp1_min_deque中的前k个值: ";
t = k;
while (t-- && t <= tmp1_min_deque.size()) {
cout << tmp1_min_deque.top().x << " ";
tmp1_min_deque.pop();
}
cout << endl;
}
//对于基础类型 默认是大顶堆 STL里面默认用的是vector
priority_queue<int> a;
priority_queue<int,vector<int>,greater<int> > b; //小顶堆这样写
//升序队列,小顶堆
priority_queue <int, vector<int>, greater<int> > q; //注意> >括号中间的空格,没有的话编译器可能会认为是右移运算符
//降序队列,大顶堆 默认
priority_queue <int, vector<int>, less<int> > p;
//其中,greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了
//自定义比较函数如上面例子
//重写比较函数
priority_queue<int, vector<int>, cmp> min_queue;
//对类的比较
priority_queue<tmp1, vector<tmp1>, cmp2> tmp1_min_deque;
return 0;
}
一些注意的点
- int类型转换来做浮点数的四舍五入
int main() {
float a;
while (cin >> a) {
if (a > 0) {
cout << int(a + 0.5) << endl;
}
}
return 0;
}
输入输出处理
输入原理
程序的输入都有一个缓冲区,即输入缓冲区。一次输入过程是这样的,当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据。正因为cin对象是直接从缓冲区取数据的,所以有时候当缓冲区中有残留数据时,cin对象会直接取得这些残留数据而不会请求键盘输入
cin的说明
参考 https://blog.csdn.net/selina8921/article/details/79067941
- 该操作符是根据后面变量的类型读取数据。
- 输入结束条件 :遇到Enter、Space、Tab键。
- 当cin>>从缓冲区中读取数据时,若缓冲区中第一个字符是空格、tab或换行这些分隔符时,cin>>会将其忽略并清除,继续读取下一个字符,若缓冲区为空,则继续等待。但是如果读取成功,字符后面的分隔符是残留在缓冲区的,cin>>不做处理。
cout的说明
输入一个字符串不带空格 和一个变量
- cout.width(8);
控制 下一次 cout输出宽度至少大于等于8
- cout.fill('0');
下一次 cout输出不够就填充0
- cout << left << "asdasdas"<< endl;
本次左对齐
技巧
- 若要读取单个字符,直接cin.get(char ch)或ch=cin.get()即可
cin.get()的返回值是int类型,成功:读取字符的ASCII码值,遇到文件结束符时,返回EOF,即-1,Windows下标准输入输入文件结束符为Ctrl+z,Linux为Ctrl+d。
-
cin.get(str,size);读取一行时,只能将字符串读入C风格的字符串中,即char*,但是C++的getline函数还可以将字符串读入C++风格的字符串中,即string类型。
-
while(cin>>s);
注意 while 处理多个 case。退出方法:回车后,在新行Ctrl+z并回车。若输入数据后Ctrl+z再回车无效。
这是因为:https://blog.csdn.net/qq_41543888/article/details/102766294
- ch = toupper(getchar())
tolower()/toupper() c++内建函数
int tolower(int c)
{
if ((c >= 'A') && (c <= 'Z'))
return c + ('a' - 'A');
return c;
}
int toupper(int c)
{
if ((c >= 'a') && (c <= 'z'))
return c + ('A' - 'a');
return c;
}
- 读取一行用cin.get(a, 5);
cin.get(数组名,长度,[结束符]) 结束符可选,默认回车Enter
对结束符处理:不丢弃缓冲区中的Enter(自定义结束符时同样不丢弃缓冲区中的结束符)
会在读取的字符串后面自动加上'\0'
#include<iostream>
using namespace std;
int main(void){
char ch='a',a[20];
cin.get(a,5);
cin.get(ch);
cout<<a<<"--"<<(int)ch<<endl; Enter 的ascii为10
return 0;
}
输入:
1 23回车
输出:
1 23--10
- 读取一行用getline
cin.getline(数组名,长度,[结束符]) 结束符可选,默认回车Enter, i.e., '\n'
getline()的原型是 istream& getline ( istream &is , string &str , char delim );
cin就是一个输入流
会在读取的字符串后面自动加上'\0'
- 鉴于getline较cin.get()的优点,建议使用getline进行行的读取。区别:
- cin.get()当输入的字符串超过规定长度时,不会引起cin函数的错误,后面的cin操作会继续执行,只是直接从缓冲区中取数据。
但是cin.getline()当输入超过规定长度时,会引起cin函数的错误,后面的cin操作将不再执行。
当输入的字符数大于count时,则get函数只读取count-1个字符,而其余的字符仍然保存在缓冲区中,还可再对其进行读取;
但是函数getline则不然,getline()会设置失效位(faibit),并且关闭后面的输入,这个时候再 用ch=cin.get()是读取不到留在输入队列中的字符的。
可以用下面的命 令来恢复输入:
cin.clear(); //因为clear()会重置失效位,打开输入。这个时候ch=cin.get();就可以读取留在输入队列中的字符。
- cin.get读取一行时,遇到换行符(自定义结束符)时结束读取,但是不对换行符(自定义结束符)进行处理,换行符(自定义结束符)仍然残留在输入缓冲区。
getline读取一行字符时,默认遇到’\n’(自定义结束符)时终止,并且将’\n’(自定义结束符)直接从输入缓冲区中删除掉,不会影响下面的输入处理。
两者都会在读取的字符串后面自动加上'\0'
- cin.get(str,size);读取一行时,只能将字符串读入C风格的字符串中,即char*,但是C++的getline函数还可以将字符串读入C++风格的字符串中,即string类型。(string test; getline(cin,test);)
-
include
- 使用sstream::stringstream输入输出流进制转换
stringstream ss;
string tmp;
int a = 10;
ss << hex << a;
ss >> tmp;
cout << tmp<< endl;//输出 a
字符串操作与ascii码
- 字符串中的某一个字串全替换为另一个串
string& replace_all(string& src, const string& old_value, const string& new_value) {
// 每次重新定位起始位置,防止上轮替换后的字符串形成新的old_value
for (string::size_type pos(0); pos != string::npos; pos += new_value.length()) {
if ((pos = src.find(old_value, pos)) != string::npos) {
src.replace(pos, old_value.length(), new_value);
}
else break;
}
return src;
}
- 即使str字符串长度不足8,str.substr(0, 8)也能正常获取剩余的字符,不会报错。
int main()
{
string str;
cin >> str;
cout << str.substr(0, 8) << endl;
return 0;
}
- 字符串转数值,可以用内建函数如 stoll stoi stof stod
int stoi (const string& str, size_t* idx = 0, int base = 10); base 默认值为10即以十进制解析, idx默认为 nullptr即可,表示从后面碰到数值的第一个字符开始解析
int main() {
string str;
while (cin >> str) {
cout << stoi(str,nullptr,0) << endl;
}
return 0;
}
-
自己按照逻辑实现进制转换
- 十六进制转10进制 输入 '0xAAA' \to int32
使用pow函数要加头文件< cmath > 不然过不了编译
#include<iostream> #include<string> #include<cmath> using namespace std; int main() { string str; while (cin >> str) { int pow_up_num = 0; int res = 0; for (int i = str.size() - 1; i > 1; i --) { if(str[i] >= '0' && str[i] <= '9') { res += (str[i] - '0') * pow(16, pow_up_num); pow_up_num++; } else if(str[i] >= 'A' && str[i] <= 'F'){ res += (str[i] - 'A'+10) * pow(16, pow_up_num); pow_up_num++; } else if (str[i] >= 'a' && str[i] <= 'f') { res += (str[i] - 'a'+10) * pow(16, pow_up_num); pow_up_num++; } else { cout << "error input!" << endl; exit(-1); } } cout << res << endl; } return 0; }
-
字符串反转
- 内建函数reverse(s,s.begin(),s.end());记得加头文件#include< algorithm.h >
- 自己实现reverse函数
void reverse_string(string& s, int start, int end) {
while (start < end) {
swap(s[start++], s[end--]);
}
}
-
使用排序算法sort(s.begin(), s.end());时候记得加头文件#include< algorithm.h >
-
单词字典序
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
int N;
while (cin >> N) {
//vector<string> res;
vector<string> s;
for (int i = 0; i < N; i++) {
string str;
cin >> str;
s.push_back(str);
}
sort(s.begin(), s.end()); // str1 > str2 可以这样对比字符串单词字典序
for (auto a : s) {
cout << a << endl;
}
}
return 0;
}
- 正则表达式+stringstream+getline(ss,t,";")分割字符串
- getline()的第三个参数默认为空格Enter 可以自行更改
- 使用regex_match要包含头文件
#include<regex>
- 使用stringstream分割字符串需要头文件
#include<sstream>
https://www.cnblogs.com/gamesky/archive/2013/01/09/2852356.html
#include<iostream>
#include<string>
#include<regex>
#include <vector>
#include<sstream>
using namespace std;
int main() {
//ios::sync_with_stdio(false);
//cin.tie(0), cout.tie(0);
string s, t;
while (getline(cin, s)) {
stringstream ss(s);
pair<int, int> p(0, 0);
while (getline(ss, t, ';')) {
if (t.empty())
continue;
string _ = t.substr(1);
if (regex_match(_, regex("[0-9]*"))) {
switch (t[0]) {
case 'A': p.first -= stoi(_); break; //左移
case 'D': p.first += stoi(_); break; //右移
case 'W': p.second += stoi(_); break; //上移
case 'S': p.second -= stoi(_); break; //下移
default: break; //无效
}
}
}
cout << p.first << "," << p.second << endl;
}
return 0;
}
-
ip地址和掩码的判断,地址的分类
主要是几个点要做好判断- 如何输入,ip用什么保存
- 因为ip是以键盘输入如:127.26.135.1~255.255.255.0读入,所以使用
getline(cin,s,'~')
分别读取前后ip和掩码,
- 因为ip是以键盘输入如:127.26.135.1~255.255.255.0读入,所以使用
- 如何确定是忽略的ip地址,不统计
- ip地址的第一个字段转int后为 127 或者 0
- 如何识别是错误的ip
- 不是合法的ip
- ip段为空
- ip段大转int后大于255
- 不为四个IP段,或少或多
- 不是合法的ip
- 如何识别是错误的掩码
- 掩码由若干个1接上若干个0组成,问题是怎么把一个字符串做识别,可以每次读一个段,然后左移8位,最后存到一个无符号的int类型中,然后用以下代码判断是不是 111..10..01; if条件是指掩码不为全0,掩码不为全1,掩码由若干个1接若干个0
bool isLegalMask(string ip) { istringstream iss(ip); string sub; unsigned int b = 0; //vector<int> v; while (getline(iss, sub, '.')) b = (b << 8) + stoi(sub); if (b == 0 || b == 0xFFFFFFFF || (((b ^ 0xFFFFFFFF) + 1) | b) != b) { return false; } return true; }
- 如何获取ip地址的某一个段然后进行ip地址的分类判断
- ip是以string读ru
- 使用
#include<sstream>
的istringstream类型作为输入流,然后用while的case功能依次读入每一个段,存到一个vector中即可
istringstream iss(ip); //用stringstream也可以 string sub; vector<int> sub_vec; while (getline(iss, sub, '.')) sub_vec.push_back(stoi(sub));
- 如何输入,ip用什么保存
-
返回字符串中长度大于2的包含公共元素的最长重复子串
-
遍历所有的子串,统计出现次数,暴力搜
#include<iostream> #include<string> #include<map> using namespace std; //暴力破解找最长相同子串 string findMaxSubStr(string s) { string ret = ""; int size = s.size(); map<string, int> m; for (int len = 3; len < size; len++) { //每次check的字符串长度 for (int pos = 0; pos < size; pos++) {//从最后一个字符串开始算起 if (pos < size && pos + len < size) { string sub = s.substr(pos,len); m[sub]++; } } } for (auto i : m) { if (i.second >= 2) ret = i.first; } return ret; } //统计字符类型 int statisticStrType(string s) { int A = 0, B = 0, C = 0, D = 0; for (auto ch : s) { if (ch >= 'a' && ch <= 'z') A=1; else if (ch >= 'A' && ch <= 'Z') B=1; else if (ch >= '0' && ch <= '9') C=1; else D=1; } return (A+B+C+D); } int main() { string psd; while (cin >> psd) { if (psd.size() < 9) { cout << "NG" << endl; } else if (findMaxSubStr(psd).size() > 2) { cout << "NG" << endl; } else if (statisticStrType(psd) < 3) { cout << "NG" << endl; } else { cout << "OK" << endl; } } return 0; }
-
只需要遍历所有长度为3的字符串,如果在字符串的其它地方找到了这个串就表示不通过
但是简单的拼接
string ss = s.substr(0, pos) + s.substr(pos + 3)
会出问题,形如021Aaabcbc$as
这样的密码会被认为NG所以还是遍历一遍所有的三个子字符串做统计,以此来实现优化
#include <iostream> #include <map> #include <string> using namespace std; int main() { string s; while (cin >> s) { //密码长度需要大于8 if (s.size() <= 8) { cout << "NG" << endl; continue; } //密码至少有四种类型字符的三种 int A = 0, B = 0, C = 0, D = 0; bool flag = false; for (auto ch : s) { if (ch >= 'a' && ch <= 'z') A = 1; else if (ch >= 'A' && ch <= 'Z') B = 1; else if (ch >= '0' && ch <= '9') C = 1; else D = 1; } if (A + B + C + D >= 3) { flag = true; } else { cout << "NG" << endl; continue; } int size = s.size(); map<string, int> m; //密码不能有大于2包含公共元素的子字符串 //只需要遍历所有长度为3的子字符串就好 for (int pos = 0; pos < size; pos++) {//从第一个字符串开始算起 if (pos < size && pos + 3 < size) { string sub = s.substr(pos, 3); m[sub]++; } } for (auto i : m) { if (i.second >= 2) { flag = false; break; } } //021Aaabcbc$ if (flag) { cout << "OK" << endl; } else { cout << "NG" << endl; } } return 0; }
-
string::npos用法,参考:https://blog.csdn.net/jiejinquanil/article/details/51789682
npos可以表示string的结束位子,是string::type_size 类型的,也就是find()返回的类型。find函数在找不到指定值得情况下会返回string::npos
-
string or char 的一些方法
- islower(ch) 'a'~'z'
- isupper(ch) 'A'~'Z'
- isalpha(ch) 'a''z'+'A''Z'
- isalnum(ch) 'a''z'+'A''Z'+ '0'~'9'
- C++中string类下的begin,end,rbegin,rend的用法,返回迭代器,分别指向字符串的第一个位置,最后一个位置的下一个位置,最后一个位置,第一个位置的前一个位置
-
回文字符串
- 验证一个字符串是不是有效的回文,只考虑字母和数字字符,忽略字母的大小写,这里将空字符串也定义为有效的回文
- 忽略字符串大小写可以用tolower(ch)或者是toupper(ch)
- 仅仅考虑数字+字母,可以用 isalnum(ch)
- 解法1:去除杂糅后,用字符串翻转api,然后看看翻转前后是否相等
- 解法2:双指针,去除杂糅后,从两端开始分别判断是否相等,相等移动指针,否则不是回文,当两个指针相遇后,那么就是回文
- 解法2优化,在原字符串上判断:就是要用
while(left < right && !isalnum(ch))
来忽略无关的字符,从而匹配到两个需要比较的位置
- 字符串中有效的回文子串数量,假设输入为已经处理过的字符串
- 中心扩散,可以遍历每一个可能是回文中心的位置,然后向外扩散,直到两边字符不相等就停止扩散
- 但是要注意子串长度,可能是奇数也可能是偶数,需要分别考虑
- 最长回文子序列,给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度;这里子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
dp[i][j]
表示字符串下标为[i,j]
内的最长子字符串- 初始化:
0<=i<=j<n
时,dp[i][j]
才有值,否则为0;任何单个字符都是回文,所以0<=i<n
时,dp[i][i]=1
- 当
i<j
时,考虑s[i]
是否和s[j]
相等的两种情况s[i]==s[j]
:在得到[i+1,j-1]
之间的条件下,又在两边各加了一个字符,所以长度+2
dp[i][j]=dp[i+1][j−1]+2
s[i]!=s[j]
😒[i]和s[j]不可能同时作为一个回文字符串的首尾,所以:
dp[i][j] = max(dp[i+1][j], dp[i][j-1])
- 状态转移都是从长度较短转移到较长,即中心扩散,所以注意循环顺序
i从n-1遍历到0
j从i+1,遍历到n-1 - dp[0][n-1]即为最长的回文子字符串
- 验证一个字符串是不是有效的回文,只考虑字母和数字字符,忽略字母的大小写,这里将空字符串也定义为有效的回文
#include<iostream>
#include<string>
#include<vector>
using namespace std;
#if 0
//有效的回文
bool isPalindrome(string s) {
string sgood;
for (char ch : s) {
if (isalnum(ch)) {
sgood += tolower(ch);
}
}
string sgood_rev(sgood.rbegin(), sgood.rend());
return sgood == sgood_rev;
}
int main() {
string s = "asdffdsa";
cout << isPalindrome(s) << endl;
return 0;
}
#endif
#if 0
//字符串中有效的回文子串数量 输入为已经处理过的字符串
//遍历每一个可能是回文中心的位置,然后向外扩散,直到两边字符不相等就停止扩散
int countSubstrings(string s) {
int n = s.size(), ans = 0;
//奇偶位置一次性处理
/*
for (int i = 0; i < 2 * n - 1; ++i) {
int l = i / 2, r = i / 2 + i % 2;
while (l >= 0 && r < n && s[l] == s[r]) {
--l;
++r;
++ans;
}
}
*/
//奇偶位置处理
//子串奇数长 中心为s[i]
//子串偶数数长 中心为s[i],s[i+1]
for (int i = 0; i < n; i++) {
int l = i, r = i;
while (l >= 0 && r < n && s[l] == s[r]) {
--l;
++r;
++ans;
}
}
for (int i = 0; i < n; i++) {
int l = i, r = i+1;
while (l >= 0 && r < n && s[l] == s[r]) {
--l;
++r;
++ans;
}
}
return ans;
}
int main() {
cout << countSubstrings("asajfdsfigghhjhhgg") << endl;
}
#endif
#if 1
int longestPalindromeSubseq(string s) {
int n = s.length();
vector<vector<int>> dp(n, vector<int>(n));
for (int i = n - 1; i >= 0; i--) {
dp[i][i] = 1;
char c1 = s[i];
for (int j = i + 1; j < n; j++) {
char c2 = s[j];
if (c1 == c2) {
dp[i][j] = dp[i + 1][j - 1] + 2;
}
else {
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
}
}
}
return dp[0][n - 1];
}
int main() {
cout << longestPalindromeSubseq("asajfdsfigghhjhhgg") << endl;
return 0;
}
#endif
- 小写单词字母异位词,数组hash表做
//字母异位词 hash表 假设只有小写字母
bool is_bro(string a, string b) {
if (a == b) return false;
if (a.size() != b.size()) return false;
int count[26] = { 0 };
for (int i = 0; i < a.size(); i++) {
count[a[i] - 'a']++;
count[b[i] - 'a']--;
}
for (int i = 0; i < 26; i++) {
if (count[i] != 0) return false;
}
return true;
}
-
strcpy的实现
- src和dest的内存不重叠
char *strcpy(char * dest,const char * src){ assert(dest!=NULL && src!= NULL); char * ret = dest; while((*dest++=*src++)!='\0'); return ret; }
- src和dest的内存重叠,即 src<=dest<=src+strlen(src) 意思是如果还从低地址开始复制,会将未处理得地址给覆盖掉,出错,所以出现内存重叠需要从高地址开始复制
- memcpy自带内存重叠判断
char* my_memcpy(char * dest,const char*src,int len){ assert(dest!=NULL && src!= NULL); char * ret = dest; if(dest>=src&&dest<=src+len-1){ //确定src 和 dest的最高地址 src = src + len -1;//src指向'\0'位置 dest = dest + len -1; while(len--){ *dest--=*src--; } }else{ while(len--){ *dest++=*src++; } } return ret; } char *strcpy(char * dest,char * src){ assert(dest!=NULL && src!= NULL); char * ret = dest; my_memcpy(dest,src,strlen(src)+1);//+1意义在于'\0' return ret; }
-
myString类的声明与实现
class myString { public: myString(const char* str = NULL); myString(const myString& other); ~myString(void); myString& operator =(const myString& other); //myString返回的类型 必须有返回值 private: char* m_data; }; myString::myString(const char* str) { if (str == NULL) { m_data = new char[1]; m_data = '\0'; } else { int len = strlen(str); m_data = new char[len+1];//+1意义在于'\0' strcpy(m_data,str); } } myString::myString(const myString& other) { int len = strlen(other.m_data); m_data = new char[len + 1]; strcpy(m_data, other.m_data); } myString::~myString(void) { delete []m_data; } //赋值函数 myString& myString::operator=(const myString& other) {//&引用 //考虑自赋值情况 if (this == &other) {//&取地址符号 return *this;//返回当前对象本身 } //既然是赋值,原先可能有值或者为赋初值,不管怎么说,都要先清再建 delete[]m_data; int len = strlen(other.m_data); m_data = new char[len + 1]; strcpy(m_data, other.m_data); //不要忘记return return *this; }
-
bitset 与 string的转换
- bitset转string
//bit_to_string 就得这样写
bitset<4> b_val(val);
string s_b_val =
b_val.template to_string<char, char_traits<char>,
allocator<char> >();
bitset<6> b("10101011");//参数一定要是01组成的字符串否则报错,不足6位左边补0 超过6位只取高6位101010
- 字符串也有count函数,在algorithm头文件里,用法:
count(str.begin(),str.end(),str[i])
但是自己用数组做hash表的统计也要会:
int Count(char &string,char ch){
int table[128] = { 0 };
for (int i = 0; i < s.size(); i++) {
table[s[i]]++;
}
return table[ch];
}
int main() {
string s;
while (cin >> s) {
//用数组做hash表,下标对应ascii字符,值对应出现次数
char ch = 'b';
cout<<Count(s,ch)<<endl;
}
return 0;
}
-
T是否子字符串S? 使用KMP算法,-构建next数组,不匹配则回溯。(或者说是前缀表解法)
参考:https://www.cnblogs.com/PiaYie/p/15884418.html -
构建前缀表
- 前缀表存的是啥? 位置0-位置i的字符串的最长相等前后缀,初始化为-1,作用是为了当不匹配时找到正确的位置继续匹配
- 对一个串生成前缀表数组
-
遍历字符串
-
如果等于cnt==S.size()-1就true
vector
-
vector中pair的使用
-
vector::emplace_back() 和 vector::push_back()
emplace_back() 和 push_back() 的区别,就在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
栈和队列
-
使用队列先进先出的性质,按顺序记录流中的项
题目,HJ19 简单错误记录
- 暴力解法
- 如何记录输入
D:\zwtymj\xccb\ljj\cqzlyaszjvlsjmkwoqijggmybr 645
和这个错误出现的次数首先要裁剪文件名,最多不超过16个字符,在用一个字符串存文件名,一个int存行号,一个int存出现的次数 所以用vector + pair做记录
vector<pair<pair<string, int>, int> > record_data;//用来记录出现错误的截断内容,行号,出现的次数
- 如何维持最新的8个错误
输出记录的最后8条内容
- 如何记录输入
#include<iostream> #include<string> #include<map> #include<deque> #include<vector> #include<sstream> using namespace std; string getFile(string path) { istringstream iss(path); string sub; while (getline(iss, sub, '\\')) continue; int size = sub.size(); if (size > 16) { sub = sub.substr(size - 16, size); } return sub; } void recordFalt(vector<pair<pair<string, int>, int> >& record_data,string file_name,int line_num) { vector<pair<pair<string, int>, int> >::iterator it; for (it = record_data.begin(); it < record_data.end(); it++) { if (it->first.first == file_name && it->first.second == line_num) { it->second += 1; return ; } } record_data.push_back(make_pair(make_pair(file_name,line_num),1)); return; } int main() { string path_name; int line_num; //unordered_map<string, pair<int, int>> map; vector<pair<pair<string, int>, int> > record_data;//用来记录出现错误的截断内容,行号,出现的次数 //vector<pair<string, int>> res;//记录输出截断文件名+行号 while (cin >> path_name >> line_num) { string file_name = getFile(path_name); recordFalt(record_data, file_name, line_num); } vector<pair<pair<string, int>, int> >::iterator it1; if (record_data.size() < 8) { for (it1 = record_data.begin(); it1 != record_data.end(); it1++) { cout << it1->first.first << " " << it1->first.second << " " << it1->second << endl; } } else { for (it1 = record_data.begin()+(record_data.size()-8); it1 != record_data.end(); it1++) { cout << it1->first.first << " " << it1->first.second << " " << it1->second << endl; } } return 0; }
- 使用双端队列deque+map求解, 代码+注释
- 如何存输入
使用map,一个string存file_name+line来做key,value为这个错误出现的次数
- 使用双端队列来维持最新的8个错误,如果deque长度大于8,只要方便地
d.pop_front()
即可
- 如何存输入
#include<iostream> #include<string> #include<map> #include<deque> #include<vector> #include<sstream> using namespace std; //用双端队列deque+map来做 int main() { //整个行输入做字符串,即file_name+line_num string name_line; //使用hash映射来存储出现的次数 map<string, int> m; //使用队列维持最新的8个错误 deque<string> res; while (getline(cin, name_line)) { //裁取截断文件名+行号 name_line = name_line.substr(name_line.find_last_of('\\') + 1); int pos = name_line.find_last_of(" "); if (pos > 16) { name_line = name_line.substr(pos - 16); } //记录错误出现的次数 if (m.find(name_line) == m.end()) { res.push_back(name_line); } ++m[name_line]; //维持8个在案错误 if (res.size() > 8) { res.pop_front(); } } for (auto it : res) { cout << it << " " << m[it] << endl; } return 0; }
- 暴力解法
-
表达式求值-栈-字符串识别-运算符优先级-双栈法
题目
- 给定一个字符串描述的算术表达式,计算出结果值。
- 输入字符串长度不超过 100 ,合法的字符包括 ”+, -, *, /, (, )” , ”0-9” 。
- 数据范围:运算过程中和最终结果均满足 $|val| \le 2^{31}-1$ ,即只进行整型运算,确保输入的表达式合法
#include<iostream>
#include<string>
#include<stack>
#include"assert.h"
using namespace std;
void caculate_num_op_num(stack<int>& num_st, stack<char> &op_st) {
if (num_st.size() < 2 || op_st.empty()) return;
int rhs = num_st.top();
num_st.pop();
int lhs = num_st.top();
num_st.pop();
char op = op_st.top();
op_st.pop();
if (op == '+') num_st.push(lhs + rhs);
else if (op == '-') num_st.push(lhs - rhs);
else if (op == '*') num_st.push(lhs * rhs);
else if (op == '/' && rhs != 0) num_st.push(lhs / rhs);
}
bool priority(char in_stack, char into_stack) {
if (in_stack == '(') return false;
else if ((in_stack == '+' || in_stack == '-') && (into_stack == '*' || into_stack == '/')) return false;
return true;
}
int caculateVal(string s) {
stack<int> num_st;
stack<char> op_st;
int res = 0;
bool flag = false;
op_st.push('(');
s += ')';
for (int i = 0; i < s.size(); i++) {
if (s[i] == '(') {
op_st.push(s[i]);
}
else if (s[i] == ')') {
while (op_st.top() != '(') caculate_num_op_num(num_st, op_st);
op_st.pop();
}
else if(flag) {
//操作符
while(!op_st.empty()&&priority(op_st.top(),s[i])) caculate_num_op_num(num_st, op_st); //算完 这里的while很重要,不能是if
if (s[i] == '+') op_st.push('+');
else if (s[i] == '-') op_st.push('-');
else if (s[i] == '*') op_st.push('*');
else if (s[i] == '/') op_st.push('/');
flag = false;
}
else {
//操作数
int num = stoi(s.substr(i));
num_st.push(num);
int len = to_string(num).size();
i += len - 1;
flag = true;
}
}
return num_st.top();
}
int main() {
string s;
while (cin >> s) {
cout << caculateVal(s) << endl;
}
return 0;
}
hash表
- 多对一用法:简单密码
#include<iostream> #include<string> #include<unordered_map> using namespace std; #if 0 int main() { string pwd; string res = ""; while (cin >> pwd) { for (auto ch : pwd) { if (ch == '1') res+=ch; else if (ch == '1') continue; else if (ch >= 'a' && ch <= 'c') res += '2'; else if (ch >= 'd' && ch <= 'f') res += '3'; else if (ch >= 'g' && ch <= 'i') res += '4'; else if (ch >= 'j' && ch <= 'l') res += '5'; else if (ch >= 'm' && ch <= 'o') res += '6'; else if (ch >= 'p' && ch <= 's') res += '7'; else if (ch >= 't' && ch <= 'v') res += '8'; else if (ch >= 'w' && ch <= 'z') res += '9'; else if (ch >= 'A' && ch <= 'Z') { ch = tolower(ch) + 1; if (ch == '{') ch = 'a'; res += ch; } else res += ch; } cout << pwd << endl; } return 0; } #endif #if 1 //使用map方法只需要判断大小写 int main() { string s; unordered_map<char, char> m; m['a'] = m['b'] = m['c'] = '2'; m['d'] = m['e'] = m['f'] = '3'; m['g'] = m['h'] = m['i'] = '4'; m['j'] = m['k'] = m['l'] = '5'; m['m'] = m['n'] = m['o'] = '6'; m['p'] = m['q'] = m['r'] = m['s'] = '7'; m['t'] = m['u'] = m['v'] = '8'; m['w'] = m['x'] = m['y'] = m['z'] = '9'; while (cin >> s) { //原地修改节省空间 for (int i = 0; i < s.size(); i++) { if (islower(s[i])) s[i] = m[s[i]]; else if (isupper(s[i]) && s[i]!='Z') { s[i] = s[i] - 'A' + 'a' + 1; } if (s[i] == 'Z') s[i] = 'a'; } cout << s << endl; } return 0; } #endif
暴力破解
- 质数分解定理
- 判定质数和分解质因数
- 边界条件可选
- i <= sqrt(N)
- i <= N/i
#include<iostream>
#include<vector>
using namespace std;
//质数分解定理 一个大于1的数其质数分解形式是唯一的。
int main() {
int N;
//vector<int> res;
while (cin >> N) {
if (N == 1) {
cout << N << " " << N << endl;
}
for (int i = 2; i <= N / i; i++) {
//M*能够整除i
if (N % i == 0) {
int cnt = 0;
while (N % i == 0) {
cnt++;
//s.push_back(i);
N /= i;
//if (cnt) cout << i << " " << cnt << endl;
if (cnt) cout << i << " ";
}
}
}
//if (N > 1) cout << N << " " << 1<< endl;
if (N > 1) cout << N << endl;
}
return 0;
}
快速排序
经典排序算法总结:https://www.cnblogs.com/fnlingnzb-learner/p/9374732.html
经典排序算法总结(动图):https://www.cnblogs.com/onepixel/articles/7674659.html
- 冒泡排序法 $O(n^2)$
//冒泡排序
void BubbleSort(int* h, size_t len)
{
if (h == NULL) return;
if (len <= 1) return;
//i是次数,j是具体下标
for (int i = 0; i < len - 1; ++i)
for (int j = 0; j < len - 1 - i; ++j)
if (h[j] > h[j + 1])
Swap(h[j], h[j + 1]);
return;
}
- 快速排序 $O(n\log_2 n)$
- 内建函数 sort(s.begin(), s.end()); 是algorithm中的函数需要头文件
- 自己实现
//快速排序,随机选取哨兵放前面
void QuickSort(int* h, int left, int right)
{
if (h == NULL) return;
if (left >= right) return;
//防止有序队列导致快速排序效率降低
srand((unsigned)time(NULL)); // include"time.h"
int len = right - left;
int kindex = rand() % (len + 1) + left;
Swap(h[left], h[kindex]);
//第一个元素作为哨兵,把比哨兵大的元素全移到哨兵的右边,这时候可以不管哨兵左边和右边的序列是否有序甚至是否局部有序
//有序的原因是这个步骤+递归的作用
int key = h[left], i = left, j = right;
while (i < j)
{
while (h[j] >= key && i < j) --j;
if (i < j) h[i] = h[j];//从j往左把遇到的第一个小于哨兵的数往前扔,哨兵被覆盖,但是备份到key了 被扔的这个位置就空出来了
while (h[i] < key && i < j) ++i;
if (i < j) h[j] = h[i];//从i往右把遇到的第一个大于等于哨兵的数往刚刚空出的位置加塞,这时候的这个位置s[i]其实是需要放一个元素进来的(如果有比哨兵小的当然好,实在找不到了,把备份的哨兵放进来)
}
h[i] = key;
//最后递归,实现有序
QuickSort(h, left, i - 1);
QuickSort(h, j + 1, right);
}
- 输入n个整数,输出最小的k个
思路:快速排序, 或者用优先队列priority_queue
#include <iostream>
#include <algorithm>
#include<time.h>
#include<vector>
#include<queue>
#include"assert.h"
using namespace std;
#if 0
//快排
void Qsort(vector<int>& nums,int left,int right) {
int len = right - left;
if (nums.empty()) return;
if (left >= right) return;
//随机生成一个k
srand((unsigned int)time(NULL));
int k = left + rand() % (len + 1);
swap(nums[left], nums[k]);
//执行快排
int i = left, j = right;
int key = nums[i];
while (i < j) { //
while (i<j && nums[j]>key) --j;
if (i < j) nums[i] = nums[j];
while (i < j && nums[i] < key) ++i;
if (i < j) nums[j] = nums[i];
}
//i=j
nums[i] = key;
//递归
Qsort(nums, left, i - 1);
Qsort(nums, j+1, right);
}
//快速排序
int main() {
int n, k,t;
while (cin >> n >> k) {
vector<int> nums;
for (int i = 0; i < n; i++) {
cin >> t;
nums.push_back(t);
}
Qsort(nums,0,nums.size()-1);
assert(k <= n);
for (int i = 0; i < k; i++) {
cout << nums[i] << " ";
}
cout << endl;
}
}
#endif
//大小堆
#if 1
//重写比较函数
struct cmp {
bool operator() (int lhs, int rhs) {
return lhs > rhs;
}
};
//小顶堆
int main() {
int n, k, t;
while (cin >> n >> k) {
vector<int> nums;
priority_queue<int,vector<int>,cmp> min_queue;
for (int i = 0; i < n; i++) {
cin >> t;
nums.push_back(t);
min_queue.push(t);
}
while (k--&&k<=min_queue.size()) {
cout << min_queue.top() << " ";
min_queue.pop();
}
}
}
#endif
排列组合
二分查找
递归
- 素数伴侣 - 匈牙利算法 - 二分图(按特定的规则连线最多)连线最多算法
给定偶数个正整数, 问最多能找到多少组数对,使得他们的和为素数
- 首先要给数分为两组,奇数和偶数,如果其中有一个数组为空,则不可能构成“素数伴侣”
- 后就相当于是左边一些奇数元素的点,要连到右边偶数元素上面,这就是二分图连线最多的问题,我们可以用匈牙利算法。
- 首先我们遍历左边奇数数组,对每一个元素都查找能否在偶数数组中找到配对的数,查找时我们遍历偶数数组,如果该偶数能和这个奇数匹配,且在这一轮这个偶数没被用过,我们再检查这个match数组(表示现阶段偶数匹配的对象),如果match数组中这个偶数没有匹配对象,或者递归查找这个匹配对象可以有其他的偶数匹配,那我们修改该匹配对象为这个奇数,代表能找到匹配。
- 创建一个素数表,用空间换时间
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
bool isPrime(int m) {
if (m <= 1) return false;
for (int i = 2; i < sqrt(m); i++) {
if (m % i == 0) return false;
}
return true;
}
//建立N长质数check表 欧拉线性质数筛
void buildPrimeTable(int N, bool* isprime_t, int *prime_t,int &count) {
int cnt=0;
isprime_t[0] = isprime_t[1] = false;
for (int i = 2; i < N; i++) {
if (isprime_t[i]) prime_t[cnt++] = i;//如果是质数就加入到质数表中,用最小的质数的i倍提前标记了后面的质数
for (int j = 0; j< N; j++) {
if (i * prime_t[j] >= N) break;
isprime_t[i * prime_t[j]] = false;//每一个素数都将其当前的i倍数标记为非素数
if (i % prime_t[j] == 0) break;//最关键只标记一次,不会重复标j记一个数是不是素数的倍数
}
}
count = cnt;
}
void buildisPrimeTable(int N, bool* isprime_t) {
int cnt = 0;
int* prime_t = new int[N];
isprime_t[0] = isprime_t[1] = false;
for (int i = 2; i < N; i++) {
if (isprime_t[i]) prime_t[cnt++] = i;//如果是质数就加入到质数表中,用最小的质数的i倍提前标记了后面的质数
for (int j = 0; j < N; j++) {
if (i * prime_t[j] >= N) break;
isprime_t[i * prime_t[j]] = false;//每一个素数都将其当前的i倍数标记为非素数
if (i % prime_t[j] == 0) break;//最关键只标记一次,不会重复标j记一个数是不是素数的倍数
}
}
delete []prime_t;
}
bool odd_find_enev_matchbest(int odd, vector<int> &evens, vector<bool> &even_used, vector<int> &even_match, bool *isprime_t) {
//遍历每一个偶数与奇数匹配
for (int i = 0; i < evens.size(); i++) {
if (!even_used[i] && isprime_t[odd + evens[i]]) {
even_used[i] = true;//这个偶数被使用
//如果第i个偶数还未配对,或者跟它配对的奇数有别的选择
if (even_match[i] == 0 || odd_find_enev_matchbest(even_match[i], evens, even_used, even_match, isprime_t)) {
even_match[i] = odd;
return true;
}
}
}
return false;
}
int main() {
const int len = 60000;
bool isprime_t[len];
memset(isprime_t, true, sizeof(isprime_t));
buildisPrimeTable(len, isprime_t);
int N,tmp;
while (cin >> N) {
vector<int> nums(N);
vector<int> odds;//奇数组
vector<int> evens;//偶数组
for (int i = 0; i < N; i++) {
cin >> tmp;
nums[i] = tmp;
if (tmp % 2 == 0) evens.push_back(tmp);
else odds.push_back(tmp);
}
//素数一定是由一个奇数和一个偶数的和构成
int count = 0;
if (odds.size() == 0 || evens.size() == 0) {
cout << count << endl;
continue;
}
vector<int> even_match(evens.size(), 0);//记录每一个偶数[i]匹配的是哪一个奇数even_match[i]
for (int i = 0; i < odds.size(); i++) {//遍历每一个奇数
vector<bool> even_used(evens.size(), false);//这一轮偶数没有被使用则标记值为0
//对每一个奇数,能找到最优的与之配对的偶数,那么匹配+1
if (odd_find_enev_matchbest(odds[i],evens,even_used,even_match,isprime_t)) {
count++;
}
}
cout << count << endl;
}
return 0;
}
当然这题除了用递归还可以用动态规划做,下面介绍递归思路:
通配符串char *p
字符串char *s
- 递递归函数怎么写:bool match( char *s, char *p) {} s,p用string存即可,默认在末尾+'\0'
- 递归结束条件:当两个字符串都到尾巴的时候,返回true, 当有个字符串提前结束时候,返回false
- 转移方程:
- 如果 p 是'?' ,那么就只能匹配一个字符(数字或者字母),继续向下递归match(p+1,s+1);但是,如果 ! isalnum(s)那么肯定不匹配,直接返回false;
- 如果 p 是'',(:匹配0个或以上的英文字母或数字0到9)三种情况,匹配一个字符,匹配一个字符,匹配多个字符; 所以返回 match(s,p+1) || match(s+1,p+1)||match(s+1,p)
注意,连续遇到多个号时,只看成一个 * - 如果 p 和s 均为字符,且相等 match(p+1,s+1)
- 如果上面三种情况都不行,返回false
- 注意sting转char* 时候如果非要用 string::c_str()转,要转成 const char * 而不是 char *
#include<iostream>
#include<string>
#include<vector>
using namespace std;
//递归
#if 0
bool match(const char* s, const char* p) {
if (*s == '\0' && *p == '\0') return true;//完全匹配
if ((*p == '\0' || *s == '\0')) return false;//不匹配
if (*p == '?') {
//不匹配
if (!isalnum(*s)) return false;
return match(s + 1, p + 1);
}
else if (*p == '*') {
//多个*看成一个
while (*p == '*') p++;
p--;
return match(s, p + 1) || match(s + 1, p + 1) || match(s + 1, p);//下一步匹配
}
else if (tolower(*s) == tolower(*p)) {
return match(s + 1, p + 1);//下一步匹配
}
//其它三种情况都不满足 如 'a'不等于 '.'
return false;//不匹配
}
int main() {
string p, s;
while (cin >> p >> s) {
bool res = match(s.c_str(), p.c_str());
if (res) cout << "true" << endl;
else cout << "false" << endl;
}
return 0;
}
#endif
//dp方法
#if 1
bool ismatch(string s, string p) {
int len_s = s.size(); //字串
int len_p = p.size(); //通配符串
vector<vector<bool>> dp(len_p + 1, vector<bool>(len_s + 1, false));
dp[0][0] = 1;
for (int i = 1; i <= len_p; i++) {
char ch_p = p[i - 1];
//如果s为空,p只有是全'*'才有可能匹配(即全*匹配0个字符)
dp[i][0] = dp[i - 1][0] && (ch_p == '*');
for (int j = 1; j <= len_s; j++) {
char ch_s = s[j - 1];
if (ch_p == '*' && isalnum(ch_s)) {
dp[i][j] = dp[i - 1][j] || dp[i][j - 1]; //*匹配0个||多个
}
else if (tolower(ch_s) == tolower(ch_p)) { //出现其它字符,只可能是 '.'== '.'
dp[i][j] = dp[i - 1][j - 1];
}
else if (ch_p == '?') {
//只能匹配字符或者数字,一个
if (isalnum(ch_s)) dp[i][j] = dp[i - 1][j - 1];
else dp[i][j] = false;
}
else if (ch_p == '*') {// 多个*,则只有一个*起作用,即把这个*看作匹配0个
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[len_p][len_s];
}
int main() {
string p, s;
while (cin >> p >> s) {
bool res = ismatch(s, p);
if (res) cout << "true" << endl;
else cout << "false" << endl;
}
return 0;
}
#endif
位运算
- 正整数的汉明重量 - 巧用 num = num & (num - 1)
int hmweight_caculate(unsigned int num) {
// n = n & (n-1)
int res = 0;
while (num) {
res += 1;
num = num & (num - 1);
}
return res;
}
滑动窗口
- DNA序列,固定长度的子序列的权重最大的那一个子序列
#include<iostream>
#include<string>
using namespace std;
//双指针
int main() {
string s;
int k;
while (cin >> s >> k) {
if (s.size() <= k || k<=0) {
cout << s << endl;
continue;
}
double maxratio = 0.0;
int pos = 0;
int count = 0;
for (int i = 0; i < s.size(); i++) {
//入窗口 s[i]
if (s[i] == 'C' || s[i] == 'G') count++;
//出窗口 s[i - k + 1]
if (i >= k - 1) {
if (1.0 * count / k > maxratio) {
pos = i-k+1;
maxratio = 1.0 * count / k;
}
if (s[i - k + 1] == 'C' || s[i - k + 1] == 'G') {
count--;
}
}
}
cout << s.substr(pos, k) << endl;
}
return 0;
}
直接模拟,或者滑动窗口: (入魔了😡)
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main(){
int n;
string commands;
while(cin >> n >> commands){
int num = 1;//选中的歌曲
int win_b = 1;//页面的起始
int win_e = min(4,n);//页面的末位置
for(int i = 0; i < commands.size(); i++){
if(commands[i] == 'U') {//向上移动一格
num = (num-1-1+n)%n + 1;
}else if(commands[i] == 'D') {//向下移动一格
num = num % n + 1;
}
if(num < win_b){//如果当前歌曲在窗口前,则将窗口往前移动
win_b = num;
win_e = win_b + 3;
}else if(num > win_e){//如果当前歌曲在窗口后,则将窗口往后移动
win_e = num;
win_b = win_e - 3;
}
}
for(int i = win_b; i <= win_e; i++){//输出当前页面
cout << i << ' ';
}
cout << endl;
cout << num << endl;//输出选中的歌曲
}
return 0;
}
深度优先算法DFS
#include<iostream>
#include<vector>
#include"assert.h"
#include<sstream>
using namespace std;
double nums[4];
bool flag = false;
bool used[4] = { 0 };
void dfs(int step,double sum) {
//递归结束条件
//每个数都要被选取计算
if (step == 4) {
if (sum == 24) {
flag = true;
return;
}
}
else {
step++;
//遍历四个数字
for (int j = 0; j < 4; j++) {
if (used[j] == false) {
used[j] = true;
dfs(step, sum + nums[j]);
dfs(step, sum - nums[j]);
dfs(step, sum * nums[j]);
dfs(step, sum / nums[j]);
//回溯
used[j] = false;
}
}
}
//隐藏的return 每个数都被用了但是得不到24
}
int main() {
for (int i = 0; i < 4; i++) {
cin >> nums[i];
}
dfs(0,0);
if (flag == true) cout << "true" << endl;
else cout << "false" << endl;
return 0;
}
-
- DFS按固定顺序走到黑,这里每一个位置对应4个周边元素,上下左右
- DFS回溯重要的问题一是怎么个递归法,事关函数参数的选取;二是怎么确定递归结束条件;在适当的时候做减枝。
#include<iostream>
#include<vector>
using namespace std;
vector<pair<int, int>> paths;
void dfs(vector<vector<int>>& matrix, int i, int j, int n, int m, vector<pair<int, int>>& res) {
res.push_back(make_pair(i, j));//插入路径
matrix[i][j] = 1;//走过了就不能再走了
//递归终止条件
if (i == n - 1 && j == m - 1) {
paths = res;
return;
}
//四个方向 上[i-1][j]下[i+1][j]左[i][j-1]右[i][j+1]
if (i - 1 >= 0 && matrix[i - 1][j] == 0)
dfs(matrix, i - 1, j, n, m, res);
if (i + 1 < n && matrix[i + 1][j] == 0)
dfs(matrix, i + 1, j, n, m, res);
if (j - 1 >= 0 && matrix[i][j-1] == 0)
dfs(matrix, i, j-1, n, m, res);
if (j+1 < m && matrix[i][j+1] == 0)
dfs(matrix, i , j+1, n, m, res);
//回溯
res.pop_back();
matrix[i][j] = 0;
}
int main() {
int n, m;
while (cin >> n >> m) {
vector<vector<int>> matrix(n, vector<int>(m,0));
vector<pair<int, int>> res;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> matrix[i][j];
}
}
dfs(matrix, 0, 0, n, m, res);
for (auto i : paths) {
cout << "(" << i.first << "," << i.second << ")" << endl;
}
}
}
广度优先算法BFS
搜索回溯
动态规划(可移步动态规划.md)
其它
-
#include<algorithm>
中取容器的最大值和最小值方法- max_element(first,end,cmp)
- min_element(first,end,cmp)
返回容器中最小值和最大值的指针。其中cmp为可选择参数, cmp可以是自定义的函数!
#include<iostream> #include<map> #include <algorithm> using namespace std; //定义cmp函数 bool cmp_value(const pair<int, int> left, const pair<int, int> right) { return left.second < right.second; } int main() { map<int, int> test; //初始化 test.emplace(10, 5);//插入 test.emplace(3, 17); test.emplace(19, 20); test.emplace(20, 20); //输出按序排列的key值 map原因 for (auto it : test) cout << it.first << " "; cout << endl; //i是迭代器 返回值为19-20 但只能返回一个迭代器 auto i = max_element(test.begin(), test.end(), cmp_value); cout << i->first <<" " << i->second << endl; return 0; } output: 19 20
-
欧拉质数筛
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//素数判定 只需要到sqrt(m)即可
bool isPrime(int m) {
if (m <= 1) return false;
for (int i = 2; i < sqrt(m); i++) {
if (m % i == 0) return false;
}
return true;
}
//建立N长质数check表 欧拉线性质数筛
void buildPrimeTable(int N, bool* isprime_t, int *prime_t,int &count) {
int cnt=0;
isprime_t[0] = isprime_t[1] = false;
for (int i = 2; i < N; i++) {
if (isprime_t[i]) prime_t[cnt++] = i;//如果是质数就加入到质数表中,用最小的质数的i倍提前标记了后面的质数
for (int j = 0; j< N; j++) {
if (i * prime_t[j] >= N) break;
isprime_t[i * prime_t[j]] = false;//每一个素数都将其当前的i倍数标记为非素数
if (i % prime_t[j] == 0) break;//最关键只标记一次,不会重复标j记一个数是不是素数的倍数
}
}
count = cnt;
}
int main() {
const int len = 100;
bool isprime_t[len]; //如果值为0,就是质数 01不是质数直接标记
memset(isprime_t, true, sizeof(isprime_t));
int prime_t[len];
int count;
buildPrimeTable(len, isprime_t, prime_t, count);
for (int i = 0; i < count; i++) {
cout << prime_t[i] << " ";
}
cout <<endl<< count << endl;
for (int i = 0; i < len; i++) {
cout << isprime_t[i] << " ";
}
return 0;
}
- 闰年判定方法 - 四年一闰,百年不闰,400年再闰,所以是
int year = 5441;
if((year%4==0) && (year % 100!=0)||(year%400==0)) return true;
else false;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)