2023/04/02
为了提升算法水平所以报了八期算法训练营,没想到收益匪浅,下面简单说一下这两个月来的收获(2023/02/01-2023/04/01)
以前自己看算法书的时候,总是因为各种原因导致放弃从而无法一直坚持,而代码随想录感觉十分通俗易懂,选的题目又十分经典都很有代表性,给了我很大的启发。
一、数组
作为科班学生,感觉数组还不是小菜一碟,结果看了代码随想录才发现我就是个小丑,数组很多理论基础部分都不扎实。
首先,数组是存放在连续内存空间上的相同类型数据的集合,其次数组的下标都是从0开始的,数组内存空间的地址都是连续的。另外,数组只能覆盖是不能删除的。这些基本理论通过代码随想录使得我的印象更加深刻了。
二、链表
数据结构中链表是在专门的一章中进行讲解的,所以比较熟悉,链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。感觉707.设计链表那道题又带着我把数据结构链表部分的理论基础给彻底地复习了一遍。
三、哈希表
哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素。通过这章我对哈希表的理解更加深刻了,并且了解了三种哈希结构及其适用场景以及用法,数组,set,map,之前set,map的用法一直不太熟,跟着代表随想录又复习了以下语法,真是一箭双雕。
四、字符串
字符串是若干字符组成的有限序列,也可以理解为是一个字符数组。这一章里面我主要深刻理解了KMP算法,虽然有模板,但是理解之后有助于我更好地利用,虽然最近又忘记了,所以这一块二刷的时候要注意。
五、双指针法
双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。
日志统计(蓝桥杯第九届真题)
//日志统计
#include
using namespace std;
typedef pair PII;
#define x first
#define y second
const int N=100010;
PII logs[N];
int cnt[N];
bool st[N];
int n,d,k;
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>d>>k;
for(int i=0;i>logs[i].x>>logs[i].y;
sort(logs,logs+n);
for(int i=0,j=0;i=d)
{
cnt[logs[j].y]--;
j++;
}
if(cnt[id]>=k) st[id]=true;
}
for(int i=0;i<100000;i++)
{
if(st[i]) cout<
翻转字符串/判断回文串
//翻转字符串
#include
using namespace std;
int main()
{
string s;
cin>>s;
for(int i=0,j=s.size()-1;i
六、栈与队列
栈和队列是一种重要的数据结构。虽然看似学过数据结构的我对这些肯定已经非常了解了,但是对于灵魂四问,还是代码随想录点醒了我“学海无涯苦作舟”捏。
灵魂四问如下:
七、二叉树
二叉树也是很重要的一种数据结构捏。代码随想录带着我复习了二叉搜索树以及平衡二叉树的概念,并且开始带领我入门回溯算法。主要总结如下:
涉及到二叉树的构造,无论普通二叉树还是二叉搜索树一定前序,都是先构造中节点。
求普通二叉树的属性,一般是后序,一般要通过递归函数的返回值做计算。
求二叉搜索树的属性,一定是中序了,要不白瞎了有序性了。
八、回溯算法
上数据结构的课程的时候,虽然懂得了dfs/回溯的基本原理,但是对其实现一直是处于一个一知半解的状态,通过代码随想录的学习,我真正地了解了回溯算法的本质,及其解题步骤。首先,回溯算法就是一种穷举,我认为回溯算法的题目必须将题意抽象为一棵树,这样才能继续向下分析。以及树枝去重和树层去重也让我进一步理解了回溯算法的细节,尤其是树层去重。
比如77.组合
//组合
#include
using namespace std;
int n,k;
vector< vector > result;
vector path;
void dfs(int n,int startIndex)
{
if(path.size()==k)
{
result.push_back(path);
return;
}
for(int i=startIndex;i>n>>k;
dfs(n,0);
for(int i=0;i
以及216.组合总和Ⅲ
//组合总和
#include
using namespace std;
int target,n;
//n数字个数,target为目标总和
vector< vector > result;
vector path;
void dfs(int n,int startIndex,int sum,int target)
{
if(sum==target&&path.size()==n)
{
result.push_back(path);
return;
}
for(int i=startIndex;i<=9;i++)
{
sum+=i;
path.push_back(i);
dfs(n,i+1,sum,target);
sum-=i;
path.pop_back();
}
}
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>target>>n;
dfs(n,1,0,target);
for(int i=0;i
以及17.电话号码的数字组合
//电话号码的数字组合
#include
using namespace std;
string letterMap[10]={
"",
"",
"abc",//2
"def",//3
"ghi",//4
"jkl",//5
"mno",//6
"pqrs",//7
"tuv",//8
"wxyz",//9
};
vector result;
string path;
void dfs(string s,int index)
{
if(path.size()==s.size())
{
result.push_back(path);
return;
}
int letter=s[index]-'0';
string letters=letterMap[letter];
for(int i=0;i>s;
dfs(s,0);
for(int i=0;i
39.组合总和
//组合总和问题
#include
using namespace std;
int target;
vector< vector > result;
vector path;
vector nums;
void dfs(vector& nums,int startIndex,int sum,int target)
{
if(sum>target) return;
if(sum==target)
{
result.push_back(path);
return;
}
for(int i=startIndex;i
40.组合总和Ⅱ
代码如下:
//组合总和Ⅱ
#include
using namespace std;
int target;
vector< vector > result;
vector path;
vector nums;
void dfs(vector& nums,int startIndex,int sum,int target,vector& used)
{
if(sum>target) return;
if(sum==target)
{
result.push_back(path);
return;
}
for(int i=startIndex;i0&&nums[i]==nums[i-1]&&used[i-1]==false) continue;
sum+=nums[i];
used[i]=true;
path.push_back(nums[i]);
dfs(nums,i+1,sum,target,used);
sum-=nums[i];
used[i]=false;
path.pop_back();
}
}
int main()
{
scanf("%d",&target);
int x;
while(scanf("%d",&x)!=EOF) nums.push_back(x);
vector used(nums.size(),false);
sort(nums.begin(),nums.end());
dfs(nums,0,0,target,used);
for(int i=0;i
131.分割回文串
#include
using namespace std;
vector< vector > result;
vector path;
bool huiwen(string s,int start,int end)
{
for(int i=start,j=end;i=s.size())
{
result.push_back(path);
return;
}
for(int i=startIndex;i>s;
dfs(s,0);
for(int i=0;i
93.复原IP地址
//复原IP地址
#include
using namespace std;
vector result;
string s;
bool check(string s,int start,int end)
{
if(start>end) return false;
if(s[start]=='0'&&start!=end) return false;
int num=0;
for(int i=start;i<=end;i++)
{
if(s[i]>'9'||s[i]<'0') return false;
num=num*10+(s[i]-'0');
if(num>255) return false;
}
return true;
}
void dfs(string s,int startIndex,int pointNum)
{
if(pointNum==3)
{
if(check(s,startIndex,s.size()-1))//检查最后一段字符串
result.push_back(s);
return;
}
for(int i=startIndex;i>s;
dfs(s,0,0);
for(int i=0;i
78.子集
//子集
#include
using namespace std;
vector< vector > result;
vector path;
vector nums;
void dfs(vector& nums,int index)
{
if(path.size()==nums.size())
{
result.push_back(path);
return;
}
for(int i=0;i<2;i++)
{
path.push_back(i);
dfs(nums,index+1);
path.pop_back();
}
}
int main()
{
int x;
while(scanf("%d",&x)!=EOF) nums.push_back(x);
dfs(nums,0);
for(int i=0;i
90.子集Ⅱ(需要进行树层去重)
//子集
#include
using namespace std;
vector< vector > result;
vector path;
vector nums;
void dfs(vector& nums,int startIndex,vector& used)
{
result.push_back(path);
for(int i=startIndex;i0&&nums[i]==nums[i-1]&&used[i-1]==false) continue;
used[i]=true;
path.push_back(nums[i]);
dfs(nums,i+1,used);
used[i]=false;
path.pop_back();
}
}
int main()
{
int x;
while(scanf("%d",&x)!=EOF) nums.push_back(x);
vector used(nums.size(),false);
dfs(nums,0,used);
for(int i=0;i
491.递增子序列 (此题有助于打破思维定势)
//递增子序列
#include
using namespace std;
vector< vector >result;
vector path;
vector nums;
void dfs(vector& nums,int startIndex)
{
if(path.size()>1)
result.push_back(path);
set uset;
for(int i=startIndex;i
46.全排列
//全排列
#include
using namespace std;
vector< vector > result;
vector path;
vector nums;
void dfs(vector& nums,int index,vector& used)
{
if(path.size()==nums.size())
{
result.push_back(path);
return;
}
for(int i=0;i used(nums.size(),false);
dfs(nums,0,used);
for(int i=0;i
47.全排列Ⅱ
//全排列
#include
using namespace std;
vector< vector > result;
vector path;
vector nums;
void dfs(vector& nums,int index,vector& used)
{
if(path.size()==nums.size())
{
result.push_back(path);
return;
}
for(int i=0;i0&&nums[i]==nums[i-1]&&used[i-1]==false) continue;
//树层去重
if(used[i]==false)
{
path.push_back(nums[i]);
used[i]=true;
dfs(nums,index+1,used);
used[i]=false;
path.pop_back();
}
}
}
int main()
{
int x;
while(scanf("%d",&x)!=EOF) nums.push_back(x);
vector used(nums.size(),false);
dfs(nums,0,used);
for(int i=0;i
51.N皇后
//N皇后问题
#include
using namespace std;
int n;
vector< vector > result;
bool check(int row,int col,vector& chessboard,int n)
{
//相当于剪枝了
//检查列
for(int i=0;i=0&&j>=0;i--,j--)
{
if(chessboard[i][j]=='Q')
return false;
}
//检查135°角
for(int i=row-1,j=col+1;i>=0&&j& chessboard)
{
if(row==n)
{
result.push_back(chessboard);
return;
}
for(int i=0;i>n;
vector chessboard(n,string(n,'.'));//初始化棋盘
dfs(n,0,chessboard);
for(int i=0;i
37.解数独
//解数独问题
#include
using namespace std;
bool check(int row,int col,char val,vector< vector >&board)
{
//判断行里面是否重复
for(int i=0;i<9;i++)
{
if(board[row][i]==val)
return false;
}
//判断列里面是否重复
for(int i=0;i<9;i++)
{
if(board[i][col]==val)
return false;
}
//判断九宫格里面是否重复
int startRow=(row/3)*3;
int startCol=(col/3)*3;
for(int i=startRow;i >&board)
{
for(int i=0;i > board(9,vector(9,'.'));
//初始化棋盘
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
cin>>board[i][j];
}
dfs(board);
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
cout<
九、贪心算法
贪心算法虽然没有固定的套路模板,但是我们也要注意积累贪心思路,由局部最优推至整体最优的思路真的值得品味。
其中双层贪心的题目令我收益匪浅,比如135.分发糖果、406.根据身高重建队列就是典型的双层贪心的题目。
分析如下:
局部最优:优先按身高高的people的k来插入。插入操作过后的people满足队列属性
全局最优:最后都做完插入操作,整个队列满足题目队列属性
代码如下:
//根据身高重建队列
#include
using namespace std;
int n;
bool cmp(vector& a,vector& b)
{
if(a[0]==b[0]) return a[1]b[0];
}
int main()
{
scanf("%d",&n);
vector< vector >people(n,vector(2,0));
vector< vector >que(n,vector(2,0));
for(int i=0;i
以及用452.最少数量的箭引爆气球也是非常经典的一道题目
代码如下:
//用最少数量的箭射爆气球
#include
using namespace std;
int n;
bool cmp(vector &a,vector& b)
{
return a[0] >& points)
{
if(points.size()==0) return 0;
int result=1;
sort(points.begin(),points.end(),cmp);
for(int i=1;ipoints[i-1][1])//两个箭不挨着
result++;
else//两个箭挨着
points[i][1]=min(points[i][1],points[i-1][1]);
}
return result;
}
int main()
{
scanf("%d",&n);
vector< vector >points(n,vector(2,0));
for(int i=0;i
同理还有435.无重叠区间
代码如下:
//用最少数量的箭射爆气球
#include
using namespace std;
int n;
bool cmp(vector &a,vector& b)
{
return a[1] >&points)
{
if(points.size()==0) return 0;
sort(points.begin(),points.end(),cmp);
int count=0;
for(int i=1;i >points(n,vector(2,0));
for(int i=0;i
再例如,763.划分字母区间
代码如下:本题思路十分巧妙捏
//划分字母区间
#include
using namespace std;
string s;
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>s;
int hash[26]={0};
for(int i=0;i result;
int left=0,right=0;
for(int i=0;i
还有合并区间:
//合并区间
#include
using namespace std;
int n;
bool cmp(vector& a,vector& b)
{
return a[0] >points(n,vector(2,0));
vector< vector >res;
for(int i=0;i=points[i][0])//有重叠
{
res.back()[1]=max(res.back()[1],points[i][1]);
}
else//没重叠
res.push_back(points[i]);
}
for(int i=0;i
单调递增的数字
代码如下:
暴力做法
//单调递增的数字
//暴力做法
#include
using namespace std;
bool check(int x)
{
int z=10;
while(x!=0)
{
int tmp=x%10;
if(z>=tmp) z=tmp;
else return false;
x/=10;
}
return true;
}
int main()
{
int x;
scanf("%d",&x);
for(int i=x;i>0;i--)
{
if(check(i))
{
printf("%d\n",i);
return 0;
}
}
}
贪心算法(复习了整数与字符串之间的转换方法)
//单调递增的数字
#include
#include
#include
using namespace std;
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int x;
cin>>x;
//数字转换为字符串的方法
stringstream ss;
ss<>s;
int flag;
for(int i=s.size()-1;i>=1;i--)
{
if(s[i]>x;
cout<
十、动态规划
五步求解法屡试不爽捏捏捏
多重背包模板代码:
//多重背包
#include
using namespace std;
const int N=101;
int n,m;
int weight[N],value[N],nums[N];
int dp[N][N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>weight[i]>>value[i]>>nums[i];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
for(int k=0;k<=nums[i]&&j>=k*weight[i];k++)
dp[i][j]=max(dp[i][j],dp[i-1][j-k*weight[i]]+k*value[i]);
}
}
cout<
例题:343.整数拆分
//整数拆分
#include
using namespace std;
const int N=100010;
int n;
int dp[N];
int main()
{
cin>>n;
dp[0]=0,dp[1]=1,dp[2]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j
以及不同的二叉搜索树
//不同的二叉搜索树
#include
using namespace std;
const int N=100010;
int n;
int dp[N];
int main()
{
cin>>n;
vector dp(n+1);
dp[0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
dp[i]+=(dp[j-1]*dp[i-j]);
}
cout<
416.分割等和子集
//分割等和子集
#include
using namespace std;
vector nums;
int main()
{
int x;
int sum=0;
while(scanf("%d",&x)!=EOF)
{
nums.push_back(x);
sum+=x;
}
if(sum%2==1)
{
printf("no\n");
return 0;
}
int target=sum/2;
vectordp(10001,0);
//初始化dp数组
for(int i=0;i=nums[i];j--)//再遍历体积
{
dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);//递归表达式
}
}
if(dp[target]==target)
{
printf("yes\n");
return 0;
}
else
{
printf("no\n");
return 0;
}
}
1049.最后一块石头的重量Ⅱ
代码如下:
//最后一块石头的重量
#include
using namespace std;
vector nums;
int main()
{
int x;
int sum=0;
while(scanf("%d",&x)!=EOF)
{
sum+=x;
nums.push_back(x);
}
int target=sum/2;
vector dp(10001,0);
for(int i=0;i=nums[i];j--)//再遍历体积
{
dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);
}
}
cout<
494.目标和
动态规划求解组合类问题的公式
//目标和
#include
using namespace std;
vector nums;
int target;
int main()
{
cin>>target;//目标和
int x;
int sum=0;
while(scanf("%d",&x)!=EOF)
{
nums.push_back(x);
sum+=x;
}
if(sum dp(10001,0);
//初始化dp数组
dp[0]=1;
for(int i=0;i=nums[i];j--)
{
dp[j]+=dp[j-nums[i]];
}
}
cout<
一和零
//一和零
#include
using namespace std;
vector strs;
int main()
{
int m,n;
cin>>m>>n;
string s;
while(cin>>s) strs.push_back(s);
vector< vector >dp(m+1,vector(n+1,0));
for(int i=0;i=zeroNum;x--)
{
for(int y=n;y>=oneNum;y--)
dp[x][y]=max(dp[x][y],dp[x-zeroNum][y-oneNum]+1);
}
}
cout<
补充
①完全背包是求排列顺序,那么就是先遍历背包,再遍历物品
②其余的完全背包都是先遍历物品,再遍历背包
最大上升子序列
//最大上升子序列
#include
using namespace std;
vector nums;
int main()
{
int x;
while(scanf("%d",&x)!=EOF) nums.push_back(x);
vectordp(nums.size()+1,1);
for(int i=0;i
最长连续递增序列
//最大连续递增子序列
#include
using namespace std;
vector nums;
int main()
{
int x;
while(scanf("%d",&x)!=EOF) nums.push_back(x);
vectordp(nums.size()+1,1);
int result=1;
for(int i=1;i
最长重复子数组
//最长重复子序列
#include
using namespace std;
vector nums1;
vector nums2;
int main()
{
int x,y;
while(scanf("%d",&x)!=EOF) nums1.push_back(x);
while(scanf("%d",&y)!=EOF) nums2.push_back(y);
vector< vector >dp(nums1.size()+1,vector(nums2.size()+1,0));
int result=0;
for(int i=1;i<=nums1.size();i++)
{
for(int j=1;j<=nums2.size();j++)
{
if(nums1[i-1]==nums2[j-1])
{
dp[i][j]=dp[i-1][j-1]+1;
result=max(result,dp[i][j]);
}
}
}
cout<
最长公共子序列/不相交的线
//最长公共子序列
#include
using namespace std;
int main()
{
string s1,s2;
cin>>s1>>s2;
vector< vector >dp(s1.size()+1,vector(s2.size()+1,0));
for(int i=1;i<=s1.size();i++)
{
for(int j=1;j<=s2.size();j++)
{
if(s1[i-1]==s2[j-1])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
}
}
cout<
最大子序和
dp[i]:包括下标i(以nums[i]为结尾)的最大连续子序列和为dp[i]
dp[i] = max(dp[i - 1] + nums[i], nums[i])
判断子序列
代码随想录还带着我对01背包问题、完全背包问题、多重背包问题有了全新的认识以及更加深刻的理解,我觉得再面试中遇到背包问题,我应该会非常自信地进行回答了。
十一、单调栈
单调栈之前也是一知半解,通过代码随想录让我有了全新的理解。首先一定明确,单调栈中存放的是数组的下标,可以求解的是那种右侧第一个大于的数字一类的题型。其中第六十天那题令我印象深刻。
刷完了上述专题,我对算法有了全新的理解,感谢代码随想录,并且准备开始二刷来复习以上获取的知识了。总之一刷是肯定不够的,代码随想录中的很多知识都还值得我再去细细品味。