每日两题cpp
- 组队竞赛(贪心)
- 删除公共字符(哈希)
- 排序子序列(数组)
- 倒置字符串(字符串)
- 数组中出现次数超过一半的数字(数组)
- 进制转换(数学)
- 连续最大和(贪心,动态规划)
- 不要二(贪心,数学)
- 把字符串转换成整数(库函数atoi的实现)(类型转换,边界处理)
- 另类加法(数学,位运算)
- 走方格的方案数(动态规划,路径)
- 参数解析(模拟,字符串)
- 跳石板(贪心,动态规划)
- 幸运的袋子(dfs深度搜索+剪枝)
- 扑克牌大小(字符串,模拟)
- 杨辉三角的变形
- 统计每个月兔子的总数(模拟,递推)
- 字符串通配符(字符串,模拟,DFS)
- 最长公共子序列(动态规划)
- 最长公共子串(动态规划)
- 最长公共字串计算(动态规划)
- 洗牌(数组,递推,模拟)
- MP3光标位置(数组,滑动窗口,模拟)
- 编辑距离(字符串,动态规划)
- 年终奖(二维数组,动态规划)
- 迷宫问题
- 星际密码(递推规律)
- 数根(字符串,高精度,整型,递归)
- 跳台阶扩展问题(变态版青蛙跳台阶,递归,数学)
- 高精度加法
- 高精度减法
- 高精度乘法
- 不用加减乘除做加法
- 判断三角形
组队竞赛(贪心)
解题思路
本题的主要思路是贪心算法,贪心算法其实很简单,就是每次选值时都选当前能看到的局部最解忧,所
以这里的贪心就是保证每组的第二个 是次大的 ;
先排好序,每次使用最左边的和最右边的两个进行组队,可以保证每队中间位置局部最大,对其加和;
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main()
{
vector<int>arr;
int n,tmp;
while(cin>>n)
{
while(cin>>tmp)
{
arr.push_back(tmp);
}
//录入完成;
sort(arr.begin(),arr.end());
long long ret = 0;
for(int i = n;i<arr.size();i+=2)
{
ret+=arr[i];
}
cout<<ret<<endl;
}
return 0;
}
删除公共字符(哈希)
解题思路
搞个哈希表,存入要删的字符,遍历原字符串,用哈希表过滤的不需要的即可;
#include<iostream>
#include<string.h>
#include<vector>
#include<algorithm>
#include<string>
#include<unordered_set>
using namespace std;
int main()
{
string s1,s2;
unordered_set<char>st;
// 注意这里不能使用cin接收,因为cin遇到空格就结束了。
// oj中IO输入字符串最好使用getline。
getline(cin,s1);
getline(cin,s2);
for(int i = 0;i<s2.size();i++)
{
st.insert(s2[i]);
}
for(int i = 0;i<s1.size();i++)
{
if(st.find(s1[i])==st.end()) cout<<s1[i];
}
cout<<endl;
return 0;
}
排序子序列(数组)
解题思路
flag作为标志转换器1为递增序列,-1为递减序列,每次变换完恢复0(待设置状态),循环统计转折次数;
#include<iostream>
using namespace std;
int main()
{
int n;
cin >> n;
int arr[n];
//当第一次出现子序列转折时,其实已经"一刀两断",划分成两个子序列了
int cnt = 1;
int flag = 0;
if (n <= 2) return 1;
for (int i = 0; i < n; i++)
{
cin >> arr[i];
}
for (int i = 0; i < n; i++)
{
if (i + 1 < n && arr[i] < arr[i + 1])
{
//flag待设置,根据递增还是递减设置flag;
if (flag == 0) flag = 1;
//flag-1递减状态,序列出现递增,划分,cnt++flag恢复初始待设置状态
if (flag == -1)
{
flag = 0;
cnt++;
}
}
else if (i + 1 < n && arr[i] > arr[i + 1])
{
if (flag == 0) flag = -1;
if (flag == 1)
{
cnt++;
flag = 0;
}
}
//else 这里就是arr[i] > arr[i + 1],不处理只需要让i++ for里面处理了;
}
cout << cnt << endl;
return 0;
}
倒置字符串(字符串)
解题思路
1.先反转每个单词;
2.再反转整体字符串则可以达到目的;(善于观察)
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int main()
{
string str;
vector<int>arr;
getline(cin,str);
int len = str.size();
//先反转每个单词
int l = 0;
int r = 1;
while(l<len)
{
while(r<len&&str[r]!=' ') r++;//找出空格' 'or字符串结尾位置;(最后一个单词也要反转)
reverse(str.begin()+l,str.begin()+r);
l = r+1;
r = l;
}
//再反转整体
reverse(str.begin(),str.end());
cout<<str<<endl;
return 0;
}
数组中出现次数超过一半的数字(数组)
解题思路
思路1:超过一半肯定mid位置是这个数,sort+返回;
时间O(nlogn)
思路2:出现次数抵消原理,遍历一遍最终一定剩下出现次数最多得数字,直接返回;
时间O(n),空间O(1)最优;
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
int ret = numbers[0];//记录出现次数多得数字;
int times = 0;//记录出现次数;
for(auto &e:numbers)
{
if(e!=ret) times--;
else times++;
if(times == 0)
{
ret = e;
times++;
}
}
return ret;
}
};
进制转换(数学)
解题思路
进制转换:辗转相除法;
不仅限于转二进制,有可能转16进制,因此用一个字符数组映射10-_>‘a’,11–>b’等;
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main()
{
string ret;
char arr[]= {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
int x,n;
cin>>x>>n;
//特殊情况,不然会打印空字符串;
if(x == 0)
{
cout<<0<<endl;
return 0;
}
//负数在开头先输出'-'代表其为负数,再将其按整数运行,因为arr映射的时候下标都是正的;
if(x<1)
{
cout<<'-';
x*=-1;
}
//辗转相除
while(x)
{
ret+=arr[x%n];
x/=n;
}
//每次添加入ret的结果是低位到高位的,最后需要翻转输出
reverse(ret.begin(),ret.end());
cout<<ret<<endl;
return 0;
}
连续最大和(贪心,动态规划)
解题思路
1.贪心,依次累加,将出现过的最大值存入max,若sum变<0,则舍弃 置sum=新的arr[i];
2.dp; dp[i] = max(nums[i],nums[i]+dp[i-1]);//状态转移方程;
贪心
class Solution {
public:
//贪心;
int maxSubArray(vector<int>& nums) {
int ans = INT_MIN;
int sum = 0;
for(int i = 0;i<nums.size();i++)
{
//如果上次的sum为<0 则置换为nums[i];
if(sum<=0)
{
sum = nums[i];
}
else
{
sum+=nums[i];
}
//每次更新cur;
if(sum>ans)
{
ans = sum;
}
}
return ans;
}
};
DP
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
//dp数组dp[i] 就是以数组下标为 i 的数做为结尾的最大子序列和;
int dp[n];
//dp初始化
dp[0] = nums[0];
int ret = nums[0];
for(int i = 1;i<n;i++)
{
dp[i] = max(nums[i],nums[i]+dp[i-1]);//状态转移方程;
if(ret<dp[i]) ret = dp[i];
}
return ret;
}
};
不要二(贪心,数学)
解题思路
所谓欧几里得距离,就是两点之间得直线距离;(距离L=开根号((x2-x1)^2+(y2-y1)^2))
题目要距离L不能等于2,则L^2!=4,意味着(x2-x1)^2+(y2-y1)^2!=4,根据表达式两个整数平方的和为4能看出只有0+4,4+0,这两种情况(对应x2-x1=2或y2-y1=2);
则用贪心的思路,可以看出:假设放蛋糕的位置是(x1,y1),则不能放蛋糕的位置(x2,y2),满足x1=x2,y1-y2=2或者x1-x2=2,y1=y2.)
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int N, M;
cin >> N >> M;
vector<vector<int>>arr;
arr.resize(N);
//将棋盘内全放上蛋糕
for (int i = 0; i < N; i++) arr[i].resize(M, 1);
int cnt = 0;
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
if (arr[i][j] == 1)//某个蛋糕的位置,其位置i+2 合 j+2不能放,去掉蛋糕;
{
cnt++;
if (i + 2 < N)arr[i + 2][j] = 0;
if (j + 2 < M)arr[i][j + 2] = 0;
}
}
}
cout << cnt << endl;
return 0;
}
把字符串转换成整数(库函数atoi的实现)(类型转换,边界处理)
解题思路
本题关键是字符串可能100长度,那么显然会越界int~[-INT_MAX+1,INT_MAX了],atoi是直接取界返回的;
用一个大于int范围得long long ret依次进行字符转数字的处理,一但越界就停止转换,直接返回边界,牵扯类型转换;
class Solution {
public:
//类似于库函数stoi的实现;
//要点 int范围~[-INT_MAX+1,INT_MAX],越过了需要令其等于边界;
int StrToInt(string str) {
int flag = 1;
if(str[0]=='-') flag = -1;//判断负数;
int len = str.size();
long long ret = 0;//数组大小100 因此某个int可能越界,搞个大的LL来处理;
for(int i = 0;i<len;i++)
{
char tmp = str[i];
if(!isdigit(tmp))
{
if(tmp=='+'||tmp=='-') continue;
return 0;//出现非法字符题return 0库里return ret(非法字符前面
}
ret = ret*10+(tmp-'0');//字符串从头到尾转换成数字的核心逻辑
if(flag==1&&ret>INT_MAX)//正数ret某次处理完>INT_MAX了
{
ret = INT_MAX;
break;
}
if(flag==-1&&ret>static_cast<long long>(INT_MAX)+1)//负数ret某次处理完>INT_MAX+1了,这里必须提升一下整形INT_MAX的范围再+1,否则会溢出!!!
{
ret = INT_MAX+1;
break;
}
}
//最后LL强转成int返回;
return flag==1?static_cast<int>(ret):static_cast<int>(-1*ret);
}
};
另类加法(数学,位运算)
解题思路
不能用±*/;那就用位运算;
两个数求和,其实就是求和后当前位的数据+两个数求和的进位;
求和后当前位的数据:a^b(两数对应位置0,0->0;1,1->0;0,1->1)
求和的进位:(a&b)<<1(因为a&b找到求和前对应的位置都为1,所以求和后该位置应该置0进1位了)
class UnusualAdd {
public:
int addAB(int A, int B) {
//可能连续进位eg(111+001),所以用while
while(B)//无进位则退出;
{
int tmp = A&B;//找到需要置0进位的多个位置i;
A = A^B;
B = tmp<<1;//<<1,代表i位置进位1位后需要加的数,然后准备与原数继续相加;
}
return A;
}
};
走方格的方案数(动态规划,路径)
解题思路
DP,从头开始记录到达每一个位置所拥有的最多种路径,逐步推演到终点;
题目规定只能向下或者向上走,dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
能到达第一行和第一列上的位置所有路径均为1;
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n, m;
cin >> n >> m;
//初始化棋盘格;沿着边缘线走,则大小(n+1)*(m+1)
n++;
m++;
int dp[n][m];
memset(dp,0, n * m*4);
//只能向左或者向下走,则第一行&&第一列就都只有一种走法;dp数组初始化;
for (int i = 0; i < n; i++)
{
dp[i][0] = 1;
}
for (int i = 0; i < m; i++)
{
dp[0][i] = 1;
}
//动态规划思想;dp[i][j] = dp[i - 1][j] + dp[i][j - 1];dp[i][j]=到i,j位置最多的路条数;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (dp[i][j] == 0)
{
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
}
cout << dp[n - 1][m - 1] << endl;
return 0;
}
解题思路1
上述连续整数二叉树中父节点与子节点的关系为:root=child/2;因此a,b自下向上寻找相同的root即可
class LCA {
public:
int getLCA(int a, int b) {
// write code here
while(a!=b)
{
if(a<b) b/=2;//在下面的child先走,可以保证两边平衡找最近的根;
else a/=2;
}
return a;
}
};
解题思路2
左孩子:root2,右孩子:root2+1;在二进制表示为root的二进制序列尾部追加0或1;
因此某一个祖先root发展子代的时候只是不断追加0或1,则该祖先的二进制序列对于子代的序列是公共前缀;
那么本题从高位到低位找出a,b的最长公共前缀(离a,b最近的公共祖先)转换成十进制即可;
class LCA {
public:
string _to_string(int x){
string ans="";
while(x){
ans=(char)((x&1)+'0')+ans;//这个+运算顺序很巧妙地保证了转换后低高位一致的顺序行;
x/=2;
}
return ans;
}
int getLCA(int a, int b) {
string sa=int2b(a);
string sb=int2b(b);
int ans=0;
for(int i=0;i<sa.length()&&i<sb.length();i++){
//从高位到低位找最长公共前缀并转换成十进制
if(sa[i]==sb[i]) ans=ans*2+(sa[i]-'0');
else break;
}
return ans;
}
};
参数解析(模拟,字符串)
解题思路
经典的模拟字符串处理问题;
但值得注意的是模拟问题的if else if else…的合理顺序和格式,回事代码的逻辑清晰度和可读性大大提高;
本题就两种状态;
1.普通分割,遇到‘ ’停止记录;
2.”str“两引号之间的分割,就算遇到’ '也要继续追加;出了这个”str“状态还是会有‘ ’将其正确停止并记录
#include<bits/stdc++.h>
using namespace std;
int main() {
string a;
getline(cin, a);
vector<string> ans;
bool flag = false;//状态转换机?
int index = 0;
string tmp = "";
for (int i = 0; i < a.size(); i++) {
if(a[i]=='"')//遇到‘”’,状态切换;
{
flag = !flag;
}
else if(a[i]==' '&&!flag)//普通状态,遇到' '需要记录清空tmp了
{
ans.push_back(tmp);
tmp.clear();
}
else//要么普通状态没遇到“,要么特殊”输入“状态,都继续向后追加;
tmp+=a[i];
}
if (tmp != "")//输出最后一个子段
ans.push_back(tmp);
cout << ans.size() << endl;
for (int i = 0; i < ans.size(); i++)
cout << ans[i] << endl;
}
跳石板(贪心,动态规划)
解题思路
题目要求计算从n~m位置最少的跳跃次数,那么如果能依次算出n到m每个位置的最少次数逐渐向后推演,第m个算出的数据就是答案;
int dp[m+1]数组记录走到i位置石板所需要的最少跳跃次数;
int dp[m+1]初始设为-1(代表没走到的位置),dp[n] = 0为起始位置;
arr中记录当前位置i的所有约数,在dp数组中依次假设推演(贪心);
- 如果dp[i+arr[j]]==-1,代表该位置没到过,则dp[i+arr[j]] = dp[i]+1,代表从i位置一步到达i+arr[j]并记录;
- 如果dp[i+arr[j]]!=-1,代表该位置到过,则dp[i+arr[j]] = min(dp[i]+1,dp[i+arr[j]]),代表从i位置一步到达i+arr[j]并记录;这也就是状态转移方程
#include<bits/stdc++.h>
using namespace std;
//求a的所有公约数,存入数组
void f(int v,vector<int>&a)
{
for (int i = 2; i <= sqrt(v); ++i)//这里求约数的优化,O(n)->O(log2)大大提高了效率
{
if (v % i == 0)
{
a.push_back(i);
if (v / i != i) a.push_back(v / i);
}
}
}
int main()
{
int n,m;
cin>>n>>m;
int dp[m+1];//dp[i] == 走到i位置石板所需要的最少跳跃次数
memset(dp,-1,(m+1)*4);//-1代表当前不能达到;
dp[n] = 0; //初始化,注意n是起点;
for(int i = n;i<=m;i++)
{
if(dp[i]==-1) continue;//某个位置i都没到过,不需要考虑该i位置向后走了 直接跳过;
//所有公约数数组;
vector<int>arr;
f(i,arr);
sort(arr.begin(),arr.end());//这里把约数排序为了下面break优化越界约数(步长);
//将arr种所有 有可能的公约数(步数)都考虑一边;
for(int j = 0;j<arr.size();j++)
{
if(i+arr[j]>m) break;//某个约数(步长)越界了,break,后面的也超了
else
{
if(dp[i+arr[j]]!=-1)//之前某一步来过
{
//状态转移更新方程;
dp[i+arr[j]] = min(dp[i+arr[j]],dp[i]+1);
}
else dp[i+arr[j]] = dp[i]+1;//之前还没哪一步来过
}
}
}
cout<<dp[m]<<endl;
return 0;
}
幸运的袋子(dfs深度搜索+剪枝)
解题思路
对于任意两个正整数a,b如果满足 a+b>ab,则必有一个数为1,1很妙噢;
题目可以去除任意个小球,则说明这是一道灵活多答案组合,直接想想dfs深度搜索all in的出来不,发现貌似给数组排个序可以,适当剪枝,注意题目同数字小球算一种,那么别忘了去重;
#include<bits/stdc++.h>
using namespace std;
//dfs(~深搜)+剪枝;
int ans = 0;
int sum = 0;
int mul = 1;
int n;
int arr[1010];
void dfs(int index)
{
for(int i = index;i<n;i++)
{
//加入 for循环的dfs精髓1for循环的dfs精髓1for循环的dfs精髓1for循环的dfs精髓1for循环的dfs精髓1for循环的dfs精髓1
sum+=arr[i];
mul*=arr[i];
if(sum>mul)//满足条件 ans+1;
{
ans++;
dfs(i+1);
}
else if(arr[i]==1) dfs(i+1);///sum>mul但是arr[i]==1,再往下dfs一次有可能满足,那时候ans再加;
else
{
//不满足条件,恢复+break
sum-=arr[i];
mul/=arr[i];
break;//sum>mul&&arr[i]!=1,不满足 剪枝 跳出循环;
}
//恢复 for循环的dfs精髓2for循环的dfs精髓2for循环的dfs精髓2for循环的dfs精髓2for循环的dfs
//if elseif else走完,相当于i位置后面的子dfs都搞定了,则恢复i位置+“DFS向后去重”;(dfs的深搜性质必须去冲,不然会dfs出多组一样的答案,可以用1 1 1 2这个组合模拟一下即可看出来)
sum -= arr[i];
mul /= arr[i];
for (; i < n - 1 && arr[i] == arr[i + 1]; i++);//!去重!;
}
}
int main()
{
//根据数学推论,两个正整数a,b要想a+b>a*b,那么a,b其中一个必为1;
scanf("%d",&n);
for(int i = 0;i<n;i++) scanf("%d",arr+i);
sort(arr,arr+n);
dfs(0);
cout<<ans<<endl;
return 0;
}
解题思路
贪心思想---每次局部最优,最后全局最优(题目包含随机概率组合,问至少,那就用贪心all in)
思路:假设有一!非零!序列 a1<a2<a3<a4<…<an,最少选出多少个能够保证覆盖n种颜色?
答案是 sum(a1…an)-a1+1,类似鸽巢原理(a1显然是最小值)
所以我们利用这个公式挑某边用最少的次数覆盖所有颜色ans次,再从另一边随便拿1个return ans+1即可;
1.考虑0的影响(某种颜色手套为0,对应的left和right的这种颜色必须全拿走,否则可能配不上对不满足要求)拿走了以后sum+…的时候就不考虑这个拿走的了,min值的寻找也跟这个数无关了;
2.左边右边同时考虑 返回int ans = min(左 sum(a1…an)-a1 + 两边配不上对的手套 + 1(右随便拿一个),右 sum(a1…an)-a1 + 两边配不上对的手套 + 1(左边随便拿一个)即可; 不管左边覆盖所有颜色再从右边拿一个 还是右边覆盖所有颜色再从左边拿一个,都搞出来 挑其较小值返回即可;
class Gloves {
public:
int findMinimum(int n, vector<int> left, vector<int> right) {
int lsum = 0;
int rsum = 0;
int non = 0;//无效手套个数
int lmin = 30;//这里不能初始left[0],因为这个位置偶遇可能是0,那么永远也找不到left里的最小 正 整数了
int rmin = 30;
for (int i = 0; i < n; i++)
{
if (left[i]*right[i] == 0) non += (left[i] + right[i]);//配不上对的手套 全拿走
else
{
//需要拿掉的无效手套已经被全处理进none了,走到这里left[i]和right[i]都不为0,累加即可
lsum += left[i];
rsum += right[i];
//min的寻找放在else里,否则non拿走两边的无效手套后,无效手套可能成为min,但是它并没有算在sum+...里,就不满足 sum(a1...an)-a1+1,定理了
if(left[i]<lmin) lmin = left[i];
if(right[i]<rmin) rmin = right[i];
}
}
return non + min(lsum - lmin + 1, rsum - rmin + 1) + 1;
}
};
扑克牌大小(字符串,模拟)
解题思路
合理地使用substr以‘-’划分左右子字符串(两手牌);
牌依次合法出现,小王,大王要么只出现一张,如果两张同时出现则一定是王炸,且大于一切直接返回;
处理完王炸,剩下要处理的就是1-1,2-2,3-3,4-4,顺子-顺子了,string 的find妙用可以确定比较顺序;
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
string FindMax(const string& line)
{
//存在王炸,直接处理掉,免得后续4-2还得看这个2是不是王炸的情况
if (line.find("joker JOKER") != string::npos)
return "joker JOKER";
int dash = line.find('-');//stl库string中的find妙用
//分开两手牌(string或者substr都会补‘\0’放心用)
string car1 = line.substr(0, dash);
string car2 = line.substr(dash + 1);
//获取两手牌的张数
int car1_cnt = count(car1.begin(), car1.end(), ' ') + 1;//count函数+迭代器的妙用;
int car2_cnt = count(car2.begin(), car2.end(), ' ') + 1;
//获取两手牌的各自第一张牌
string car1_first = car1.substr(0, car1.find(' '));
string car2_first = car2.substr(0, car2.find(' '));
if (car1_cnt == car2_cnt) //两手牌的类型相同
{
string str = "345678910JQKA2jokerJOKER";//这里str的设定和find的使用方便巧妙地卡出了两副牌的大小
if (str.find(car1_first) > str.find(car2_first))
return car1;
return car2;
}
//两手牌类型不同or存在炸弹(若存在炸弹返回那个炸弹(只能存在一个且不是王炸))
if (car1_cnt == 4) //说明是炸弹
return car1;//之前处理过王炸cnt==2的情况了,这里
else if (car2_cnt == 4)
return car2;
return "ERROR";//没比出大小ERROR放最后返回;
}
int main()
{
string line, res;
while (getline(cin, line))
{
res = FindMax(line);
cout << res << endl;
}
return 0;
}
杨辉三角的变形
解题思路
杨辉三角问题可以转换成直角三角形,通过二维数组以及对应位置关系模拟构建;
本题按照题目意思,可以发现第n行有2n - 1个元素,第i,j元素等于上一行第j - 2,j - 1,j三列元素之和(高度规律性可观察得出),每一行的第一列和最后一列都为1;
如果是第二列注意特例则只是上一列j - 1和j两个元素之和。
#include<bits/stdc++.h>
using namespace std;
int main()
{
//高n 最底下最长n*2-1
int n;
cin >> n;
int m = 2*n-1;
int arr[n][m];
//全初始化为0
memset(arr, 0, 4*n*m);
for (int i = 0; i < n; i++)
{
//初始对角线和第一列
arr[i][2 * i] = 1;
arr[i][0] = 1;
}
//构建三角(注意观察三角转直角三角形后的规则!!!)
for (int i = 0; i < n; i++)
{
for (int j = 0; j < 2 * i; j++)
{
//第二列的特例!
if (j == 1)
{
arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1];
continue;
}
if (arr[i][j] == 0)
{
arr[i][j] = arr[i - 1][j - 1] + arr[i - 1][j] + arr[i - 1][j - 2];
}
}
}
for (int i = 0; i < m; i++)
{
if (arr[n-1][i] % 2 == 0)
{
cout << i+1<< endl;
return 0;
}
}
cout<<-1<<endl;
return 0;
}
因为数据范围和内存限制,牛客更新了测试用例,跑不过去了,显然数组的方法不可行了,寻找数学规律;
通过以上的数据分析答案规律为 {-1,-1,2,3,2,4,2,3,2,4,…} ,即第n行上第一个偶数所在的下标;
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin>>n;
if(n<=2)
{
cout<<-1<<endl;
return 0;
}
int ret[] = {2,3,2,4};
n-=3;//去掉第一行第二行的特殊情况,第三行相当对应周期下标0,第四行对应周期下表1;以此类推,直接%一次就好
n%=4;
cout<<ret[n]<<endl;
return 0;
}
统计每个月兔子的总数(模拟,递推)
解题思路
不要死磕DP,你会发现很难控制;
一看就有强规律,不妨模拟几次看看答案规律;
#include<bits/stdc++.h>
using namespace std;
// 观察一下 答案是斐波那契 f(n) = fn-1+fn-2
int main()
{
int n;
cin>>n;
int a = 1;
int c = 1;
int b = 1;
n-=2;
while(n--)
{
c = a+b;
a = b;
b = c;
}
cout<<c<<endl;
return 0;
}
字符串通配符(字符串,模拟,DFS)
解题思路
这道题类似于正则表达式的实现(Python网页爬虫处理url的时候接触过)
模拟,各种情况都考虑清楚;
思路2的DP算法优化到O(n^2)暂时不考虑
#include<bits/stdc++.h>
using namespace std;
class Solution {
public:
bool Fun(const char* ms, const char* str)//递归匹配;
{
if (*ms == '\0' && *str == '\0') return true;
else if (*ms == '\0' || *str == '\0') return false;
else if (isalnum(*str) && isalnum(*ms))
{
//这里别忘记不区分大小写;
if (tolower(*str) != tolower(*ms)) return false;
return Fun(ms + 1, str + 1);
}
else if (!isalnum(*str) && !isalnum(*ms))//两个.可以匹配
{
return Fun(ms + 1, str + 1);
}
else if (*ms == '?')//只能匹1个
{
//str要是'.'这种,就不满足*和?只能匹字母或数字
if (!isalnum(*str)) return false;
return Fun(ms + 1, str + 1);
}
else if (*ms == '*')//匹配0 1 或多个
{
while (*(ms + 1) == '*') ms++;//多个*连续需要没必要,剪一下枝
if (!isalnum(*str)) return Fun(ms + 1, str);
return Fun(ms + 1, str) || Fun(ms + 1, str + 1) || Fun(ms, str + 1);//类似于dfs
}
return false;
}
bool isMatch(string s, string p) {
return Fun(p.c_str(), s.c_str());
}
};
int main()
{
//递归向后匹
string str;
string ms;
cin >> ms >> str;
bool ret = Solution().isMatch(str,ms);
if (ret) cout << "true" << endl;
else cout << "false" << endl;
return 0;
}
最长公共子序列(动态规划)
解题思路
要求返回最长公共子序列(可以删减中间元素)的和,分解成从s1,s2的0,0位置,子问题的形式递推到i,j字符末尾,则dp[n][m]就是答案;
1.DP[i][j]数字含义:s1字符串i位置之前和s2字符串j位置之前的最大子段和;
i,j从1开始,i||j==0的位置(第一行or第一列)dp对应的值都为0(空串没有公共子段),dp数组的初始化;
2.递推公式:
- s1[i]==s2[j]时,dp[i][j] = dp[i-1][j-1]+1;
- s1[i]!=s2[j]时,dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
//经典二维dp问题
int n = text1.size();
int m = text2.size();
vector<vector<int>>dp(n + 1, vector<int>(m + 1));//这里已经将dp中的内容全部初始化为0了(第一行,第一列初始为0完成)
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=m;j++)
{
if(text1[i-1]==text2[j-1])dp[i][j] = dp[i-1][j-1]+1;
else
{
//i,j只能从dp[i-1][j]or dp[i][j-1]来了
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[n][m];
}
};
最长公共子串(动态规划)
解题思路
与上题子序列不同,这题子串要求只能删除连续的前缀或者后缀,而不能随便删除字符进行拟合;//那么相比上一题,这个题的公共子序列只能连续,容易很多 dp转换就一个 if(s1[i-1]==s2[j-1]) dp[i][j]=dp[i-1][j-1]+1了!;
遇上题不同的是 DP[i][j]代表s1串以i位置结尾,和s2串以j位置结尾的子段长度,进行一个max记录并记录start的位置最后返回切割串即可;
这题只需要考虑s1[i]=s2[j]的dp数组更新,因为他只允许删除连续的前缀或者后缀;
//思路:动态规划经典问题,加一个start标记即可,注意将较短子串最先出现的那个输出
#include<iostream>
#include<vector>
#include<string>
using namespace std;
void findMaxCommonStr(string s1,string s2)
{
if(s1.length()>s2.length())
swap(s1,s2);//s1用于保存较短的子串
int len1=s1.length(),len2=s2.length();
int maxLen=0,start=0;
vector<vector<int> >dp(len1+1,vector<int>(len2+1,0));
for(int i=1;i<=len1;++i)
for(int j=1;j<=len2;++j)
{
if(s1[i-1]==s2[j-1])
{
dp[i][j]=dp[i-1][j-1]+1;
if(dp[i][j]>maxLen)
{
maxLen=dp[i][j];
start=i-maxLen;//记录最长公共子串的起始位置
}
}
}
cout<<s1.substr(start,maxLen)<<endl;
}
int main()
{
string s1,s2;
while(cin>>s1>>s2)
{
findMaxCommonStr(s1,s2);
}
return 0;
}
最长公共字串计算(动态规划)
解题思路
类似于上一题,这题简化了 只要求输出max长度即可,上一题得根据这个长度找到max字串的起始位置输出子串;
DP[i][j]代表s1串以i位置结尾,和s2串以j位置结尾的子段长度;
#include<bits/stdc++.h>
using namespace std;
int main()
{
string s1;
string s2;
cin >> s1 >> s2;
int n = s1.size() ;
int m = s2.size() ;
int dp[n+1][m+1];
int max = 0;
memset(dp, 0, 4 *(n+1)*(m+1));
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (s1[i - 1] == s2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
if (dp[i][j] > max) max = dp[i][j];
}
}
cout << max << endl;
return 0;
}
洗牌(数组,递推,模拟)
解题思路
一坨牌 分两摊,左右交替放,洗round轮,模拟即可;
#include<bits/stdc++.h>
using namespace std;
int main()
{
int a;
cin >> a;
while (a--)
{
int n, round;
cin >> n >> round;
int cur[2 * n];
for (int i = 0; i < 2 * n; i++) cin >> cur[i];
//洗牌过程
while (round--)
{
vector<int>tmp{ cur,cur + 2 * n };
for (int i = 0; i < n; i++)//找出n与left[]和right[]的关系就可以一次循环同时放入对应位置
{
cur[2 * i] = tmp[i];//左手的牌排放的位置
cur[2 * i + 1] = tmp[i + n];//右手的牌排放的位置
}
}
//round轮洗完了,再输出最终洗牌结果;cur
for (int i = 0; i < 2 * n - 1; i++)
{
cout << cur[i] << ' ';
}
cout << cur[2 * n - 1] << endl;
}
return 0;
}
MP3光标位置(数组,滑动窗口,模拟)
解题思路
定长为4的滑动窗口思想,需要注意的是这里分窗口所在数组元素个数<=n(这种情况不用翻页 简化了很多)和窗口对应的数组>4(可能需要滑动)两种情况;
#include<bits/stdc++.h>
using namespace std;
int main()
{
//滑动窗口+两端点的情况?
int n;
cin >> n;
string s;
cin >> s;
//起始的第一页1~4
int l = 1;
int r = 4;
int pos = 1;
for (int i = 0; i < s.size(); i++)
{
//不用翻页的情况;
if (n <= 4)
{
for (int i = 0; i < n; i++)
{
if (s[i] == 'U')
{
if (pos == 1) pos = n;
else pos--;
}
else
{
if (pos == n) pos = 1;
else pos++;
}
}
for (int i = 1; i <= n; i++)
{
cout << i << ' ';
}
cout << endl << pos << endl;
return 0;
}
//n>4的正常逻辑
else
{
if (s[i] == 'U')
{
//窗口不用动
if (pos != l) pos--;
else//窗口需要移动了
{
if (l == 1)
{
l = n - 3;
r = n;
pos = n;
}
else
{
l--;
r--;
pos--;
}
}
}
else//D
{
if (pos != r) pos++;
else
{
if (r == n)
{
l = 1;
r = 4;
pos = 1;
}
else
{
r++;
l++;
pos++;
}
}
}
}
}
for (int i = l; i <= r; i++)
{
cout << i << ' ';
}
cout << endl << pos << endl;
return 0;
}
编辑距离(字符串,动态规划)
解题思路
类似于上面的二维字符串dp问题;
dp[i][j]表示word1的i位置之前和word的j位置之前的字符串,重构所需要的最少操作次数;因此本题一只递推到dp[n][m]即可;
只有三种操作方法,追加,删除or替换一个字符;
因此转移方程对应DP[i][j]的取值从下列3种的最小选
dp[i-1][j]+1>>w1删除第i个>>(因为w1的0i-1已经和w2的0j已经重构了,w1的i位置多余了一个字符删掉),
dp[i][j-1]+1>>w1的位置追加字符w2[i]>>(因为w1的0i和w2的0j-1重构了,w2的j位置还多一个字符,w1需要追加它),
dp[i-1][j-1]+1>>替换;>>w1的i-1和w2的j-1位置重构了,新的dp[i][j]的位置也可以从他俩的钱一个位置+1,代表w1的i换成w2的j的字符即可;
转移方程:dp[i][j] = min(dp[i-1][j]+1,mindp[i][j-1]+1,dp[i-1][j-1]+1);
dp初始化://!!注意!! 这个数组的初始化第一行第一列需要给值0~m/n这样递增初始化
//eg:dp\[0][3],w1的0位置之前是“”,w2的3位置之前有3个字母,所以dp\[0][3]应该等于3;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fdQnStY1-1659875472055)(C:/Users/15795/Desktop/img/a8d194545ef4437b858011d7b6487d15.png)]
class Solution {
public:
int minDistance(string word1, string word2) {
int n = word1.size();
int m = word2.size();
//dp代表w1到i位置之前与w2到j位置之前的"最短距离"
//要么删,要么加,要么替换
//地推方程:dp[i][j] = min
int dp[n+1][m+1];
//!!注意!! 这个数组的初始化第一行第一列需要给值0~m/n这样递增初始化
//eg:dp[0][3],w1的0位置之前是“”,w2的3位置之前有3个字母,所以dp[0][3]应该等于3;
//初始化
dp[0][0] = 0;
for(int i = 1;i<=n;i++)
{
dp[i][0] = i;
}
for(int j = 1;j<=m;j++)
{
dp[0][j] = j;
}
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=m;j++)
{
//dp转移
if(word1[i-1] == word2[j-1])
{
dp[i][j] = dp[i-1][j-1];
}
else
{
dp[i][j] = min(dp[i-1][j]+1,dp[i][j-1]+1);//减和加
dp[i][j] = min(dp[i][j],dp[i-1][j-1]+1);//替换
}
}
}
return dp[n][m];
}
};
年终奖(二维数组,动态规划)
解题思路(dp)
题目解析:6*6棋盘格,每个格子有一个数字代表该物品价值,需要找到一条路径,该路径上的价值和最大;
dp[i][j],代表走到位置ij时候的累加最大金额;
初始化:第一行第一列对应的位置是前面位置金额的累加,因为第一行只能一直向右走累加,第一列只能一直向下走累加;
转移方程:dp[i][j] = max(dp[i-1][j],dp[i][j-1])+board[i][j];//上走下来或者左走到右边,再加当前board的钱
class Bonus {
public:
int getMost(vector<vector<int> > board) {
// write code here
int dp[6][6];
memset(dp,0,6*6*4);
dp[0][0] = board[0][0];
//dp数组的初始化
for(int i = 1;i<6;i++)
{
dp[0][i] += dp[0][i-1]+board[0][i];//第一行
dp[i][0] += dp[i-1][0]+board[i][0];//第一列
}
for(int i = 1;i<6;i++)
{
for(int j = 1;j<6;j++)
{
//状态转移方程
dp[i][j] = (max(dp[i-1][j],dp[i][j-1])+board[i][j]);
}
}
return dp[5][5];
}
};
解题思路(dfs)
二维数组的dfs类似于迷宫求最短路径这种问题;
class Bonus {
public:
int sum = 0;
int max = 0;
void dfs(vector<vector<int> >& board,int a,int b)
{
sum+=board[a][b];
if(b!=5) dfs(board,a,b+1);
if(a!=5) dfs(board,a+1,b);
//到终点了
if(max<sum) max = sum;
sum-=board[a][b];
}
int getMost(vector<vector<int> > board) {
// write code here
dfs(board,0,0);
return max;
}
};
迷宫问题
解题思路
maze问题是经典的回溯算法,和dp问题一样 有着强格式模板话;
本题算是比较简单的 只有一条路径,那么dfs回溯用vector记录路径即可,注意回溯时的录入当前节点和弹出当前结点的过程,即一进入dfs就把当前位置写为走过,要是出这个dfs需要回溯,就把当前节点置为未走状态;
#include<bits/stdc++.h>
using namespace std;
int n, m;
vector<pair<int, int>>tmp;
vector<pair<int, int>>best;
void dfs(vector<vector<int>>& maze, int a, int b)
{
if(maze[a][b]==1) return;//位置不合法;
maze[a][b] = 1;//位置合法,设置为已经走过 继续dfs向下回溯找路径;
tmp.push_back({ a,b });//记录达到的节点;
if (a == n - 1 && b == m - 1)//走到终点
{
if (best.empty() || tmp.size() < best.size()) best = tmp;//这里也能把最短的记录到;
tmp.pop_back();
maze[a][b] = 0;
return;
}
//上下左右寻找出路;
if (a != 0)
dfs(maze, a - 1, b);
if (a != n - 1)
dfs(maze, a + 1, b);
if (b != 0)
dfs(maze, a, b - 1);
if (b != m - 1)
dfs(maze, a, b + 1);
//恢复路径 回溯;
maze[a][b] = 0;
tmp.pop_back();
return;
}
int main()
{
cin >> n >> m;
vector<vector<int>>maze(n, vector<int>(m, 0));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
cin >> maze[i][j];
}
}
dfs(maze, 0, 0);//从0,0开始寻找路径
for (auto& e : best)
{
printf("(%d,%d)\n", e.first, e.second);
}
return 0;
}
星际密码(递推规律)
解题思路
首先,给定整数x,则翻译的密码就是给定矩阵[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AUYFlRzJ-1656125056994)(C:/Users/15795/Desktop/img/image-20220625103532957.png)]的x次幂;
乘几次观察得出,这个密码事以f(1) = 1,f(2) = 2的fib数列构成,那么问题就简单很多了;
下面是比较挫的代码;
// write your code here cpp
#include<bits/stdc++.h>
using namespace std;
int arr[10001];
void f2(int& a)//只存后四位,fib(x)可能int存不下 没关系 存后四位即可;
//这个函数完全可以用 %10000来存后四位代替.....!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
{
if (a <= 9999) return;
string tmp = to_string(a);
a = stoi(tmp.substr(tmp.size() - 4));
}
void Init()//初始化fib数组 因为会连续输入好几个x 让你求fib(x),用WHILE那种循环求fib(x)的话的话会重复大量计算,超时!!应该用全局数组一遍记录好达到剪枝;
{
arr[1] = 1;
arr[2] = 2;
for (int i = 3; i <= 10000; i++)
{
arr[i] = arr[i - 1] + arr[i - 2];
f2(arr[i]);//强制存后四位
}
}
void f(int x)//这里完全可以用printf("%04d",x);来代替....!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
{
//因为肯可能求10000个fib 越界了,题目有要求只需要后四位,就对后四位操作;
string ret = "0000";
ret += to_string(arr[x]);
int len = ret.size();
cout << ret.substr(len - 4);
}
int main()
{
int flag;
Init();
while (cin >> flag)
{
for (int i = 0; i < flag; i++)
{
int x;
cin >> x;
f(x);
}
cout <<endl;
}
return 0;
}
优美版本(善于观察,学以致用。。。)
数根(字符串,高精度,整型,递归)
解题思路
即便是想到了应该cin到一个string里,因为数据范围最长可以到100多位数字!
但是,一直在同一个string里操作各位相加还有进位递归啥的不方便;
善于观察可以看出 即便是100多个9,累加也不会超int范围,那我们就用string浅录入过度一下,手动累加一边放入一个int中再用这个int进行递归逐位相加就好啦!
#include<bits/stdc++.h>
using namespace std;
int f(int x)//计算一个整数的各位和,最终为个位数时递归终止返回
{
if(x<10)
{
return x;
}
int tmp = 0;
while(x)
{
tmp += x%10;
x/=10;
}
return f(tmp);
}
int main()
{
string s;//最多输入长度为102的数字组合,就算102个9相加int也能装下;//
while(cin>>s)
{
int sum = 0;
for(int i = 0;i<s.size();i++)//则相当于放入第一次输入的大整数通过string将各位累加放入了int sum
{
sum+=s[i] - '0';
}
cout<<f(sum)<<endl;
}
return 0;
}
跳台阶扩展问题(变态版青蛙跳台阶,递归,数学)
解题思路
//青蛙跳台阶;
//1-1 2-2 3-4…
//和青蛙一次只能1or2步思路类似;
//达到第n的方法可以:
//从第n-1直接跨1个台阶,从第n-2直接跨2个台阶,从n-3一步跨3个台阶。。。从n-(n-1)个台阶一次跨n-1到终点
//递推公式:f(n) = f(0)+f(1)+… f(n-1) 普通青蛙跳台阶的递推公式:f(n) = f(n-1)+f(n-2)//一次只能跳1 or 2个台阶;’
class Solution {
public:
int jumpFloorII(int number) {
if(number == 1) return 1;//f(1) = 1
int sum = 1;//f(0) = 1;
for(int i = 1;i<number;i++)
{
sum+=sum;
}
return sum;
}
};
高精度加法
string Add(string& a, string& b)//高精度加
{
string ret;
int ra = a.size() - 1;
int rb = b.size() - 1;
int carry = 0;//进位
while (ra >= 0 || rb >= 0 || carry > 0)
{
int tmp = 0;
tmp += ra < 0 ? 0 : a[ra] - '0';
tmp += rb < 0 ? 0 : b[rb] - '0';
tmp += carry;
carry = tmp / 10;
tmp %= 10;
ret += '0' + tmp;
ra--;
rb--;
}
reverse(ret.begin(), ret.end());
return ret;
}
高精度减法
bool Cmp(string &a,string &b)//返回a<b
{
if (a.size() < b.size())return true;
else if (a.size() > b.size()) return false;
else {
for (int i = 0; i < a.size(); i++) {
if (s[i] < b[i])return true;
else if(s[i] > b[i]) return false;
}
return false;//a == b
}
}
string Sub(string &a,string &b)// a - b
{
int flag = 0;
//保证 大 - 小
//if (atoi(a.c_str()) < atoi(b.c_str())) {//这样比其实也是有风险的,万一高精度直接越界
// flag = 1;//最后需要输出'-'符号;
// swap(a, b);
//}
if(Cmp(a,b)){
flag = 1;//最后需要输出'-'符号;
swap(a, b);
}
string ret;
int ra = a.size() - 1;
int rb = b.size() - 1;
while (ra>=0||rb>=0)
{
if (ra>=0&&rb<0) {//被减数把减数减完了,还有剩余 直接挪下来;
ret += a[ra];
ra--;
continue;
}
if (a[ra] < b[rb]) {
//需要借位;
int index = ra-1;
while (a[index] == '0') index--;//找到借的那一位 位置下表
a[index]--;//借的那一位被拿掉;
for (int i = ra - index ; i > 1; i--) {
a[++index] = '9';//路途中补9
}
ret +=a[ra]-b[rb]+10 +'0';//进了10 然后再减b
}
else ret += a[ra]-b[rb] +'0';
ra--;
rb--;
}
if (flag) ret += '-';
reverse(ret.begin(),ret.end());
//去除借位之后可能产生的首部无效0(最高位为1 并被借走就这样多了个无效0);
if (ret[0] == '0')ret.erase(0,1);
return ret;
}
理论上,有了高精度加法和减法,就能(通过交换律)完成无论是正数还是负数之间的加减运算了!
高精度乘法
string f(string &a,string &b)
{
string ret = "0";
int ra = a.size()-1;
int rb = b.size()-1;
int carry = 0;//进位
int flag = 0;
for (int i = rb; i >= 0; i--) {//一共走乘数长度轮
string stmp;//记录每一轮的结果!
carry = 0;//进位清零!!
for (int i = 0; i < flag; i++) stmp += '0';//乘数下一位的轮次,需要在上一倍基础上扩大十倍后再加下一轮的结果!
for (int j = ra; j >= 0; j--) {//乘数的某一位,分别与被乘数的每一位相乘,并记录结果进入ret;
int tmp = 0;
tmp += (a[j] - '0') * (b[i] - '0')+carry;
carry = tmp / 10;
tmp %= 10;
stmp += tmp + '0';
}
if (carry)stmp += carry+'0';//看还有没有进位
reverse(stmp.begin(), stmp.end());
ret = Add(ret,stmp);//高精度加法
flag++;
}
return ret;
}
不用加减乘除做加法
考察位运算:
a^b和(a&b)<<1结合使用;
a^b相当于把不进位的0和1加起来了,进位的1和1抵消为0;
(a&b)<<1相当于把需要进位的两个1拿出去进位了,下一轮再加起来 (原进位位置已经被^把两个1消掉了)
class Solution {
public:
int Add(int num1, int num2) {
int a = num1;
int b = num2;
int carry;
int sum = a ^ b;
while (carry = (a&b)<<1) {
sum = a ^ b;
a = sum;//注意在sum与carry^之前,需要先记录当前sum,之后才能看当前sum与carry有没有进位,需不需要继续while循环+
b = carry;
sum = sum^carry;
}
return sum;
}
};
判断三角形
记住定理,三角形成立条件:任意两边之和大于第三边;
数据范围过大,使用高精度加法,进行判定;
#include<iostream>
#include<algorithm>
#include<stdlib.h>
using namespace std;
bool Cmp(string& s, string& b)//判断大整数a是不是大于b
{
if (s.size() > b.size())return true;
else if (s.size() < b.size()) return false;
else {
for (int i = 0; i < s.size(); i++) {
if (s[i] > b[i])return true;
else if(s[i]<b[i])return false;
}
return false;
}
return false;
}
string Add(string a, string b)//高精度加法
{
string ret;
int ra = a.size() - 1;
int rb = b.size() - 1;
int carry = 0;//进位
while (ra >= 0 || rb >= 0 || carry > 0)
{
int tmp = 0;
tmp += ra < 0 ? 0 : a[ra] - '0';
tmp += rb < 0 ? 0 : b[rb] - '0';
tmp += carry;
carry = tmp / 10;
tmp %= 10;
ret += '0' + tmp;
ra--;
rb--;
}
reverse(ret.begin(), ret.end());
return ret;
}
int main()
{
string a, b, c;
while (cin >> a >> b >> c) {
string tmp1 = Add(a, b);
string tmp2 = Add(a, c);
string tmp3 = Add(c, b);
if (Cmp(tmp1, c) && Cmp(tmp2, b) && Cmp(tmp3, a))//Add(q,b)>c 这种判断是错的,因为两个string类型表示的长int 不能直接用string原生的>进行大小判断;
cout << "Yes" << endl;
else cout << "No" << endl;
}
return 0;
}