目录
该文章刷题顺序按照代码随想录刷,只记录简单算法思路和源码
数组
链表
哈希表
字符串
双指针法
栈和队列
二叉树
回溯算法
回溯其实可以说是我们熟悉的DFS,本质上一种暴力枚举算法。回溯算法的唯一优化方法是剪枝。
回溯算法的模板
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
77、组合(中等)
class Solution {
public:
void backTracking(vector<vector<int>>& res,vector<int>& temp,int n,int k,int index){
//回溯
if(temp.size()==k) {
res.push_back(temp);
return;
}
for(int i=index;i<=n-(k-temp.size())+1;i++){ //剪枝优化
temp.push_back(i);
backTracking(res,temp,n,k,i+1); //注意:i+1而不是index+1
temp.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
//回溯的经典算法
vector<vector<int>> res;
vector<int> temp;
backTracking(res,temp,n,k,1);
return res;
}
};
216、组合总和3
class Solution {
public:
void backTracking(vector<vector<int>>& res,vector<int>& temp,int k,int n,int index){
//回溯
if(temp.size()==k && n==0){
res.push_back(temp);
return;
}
for(int i=index;i<=9;i++){
if(n<0) continue; //剪枝优化
temp.push_back(i);
n=n-i;
backTracking(res,temp,k,n,i+1);
temp.pop_back();
n=n+i;
}
}
vector<vector<int>> combinationSum3(int k, int n) {
/*回溯*/
vector<vector<int>> res;
vector<int> temp;
backTracking(res,temp,k,n,1);
return res;
}
};
17、电话号码的字母组合
class Solution {
public:
void backTracking(vector<string>& res,vector<string>& temp,string& ans,int n,int index){
//回溯
if(index==n) {
res.push_back(ans);
return;
}
for(int i=0;i<temp[index].size();i++){
ans.push_back(temp[index][i]);
backTracking(res,temp,ans,n,index+1);
ans.pop_back();
}
return;
}
vector<string> letterCombinations(string digits) {
/**/
vector<string> temp;
vector<string> res;
int n = digits.size();
if(n==0) return res;
for (char i : digits){
if(i=='2') temp.push_back("abc");
if(i=='3') temp.push_back("def");
if(i=='4') temp.push_back("ghi");
if(i=='5') temp.push_back("jkl");
if(i=='6') temp.push_back("mno");
if(i=='7') temp.push_back("pqrs");
if(i=='8') temp.push_back("tuv");
if(i=='9') temp.push_back("wxyz");
}
string ans;
backTracking(res,temp,ans,n,0);
return res;
}
};
39组合总和
class Solution {
public:
vector<vector<int>> res;
vector<int> temp;
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
/*回溯实现*/
backTracking(candidates,target,0);
return res;
}
void backTracking(vector<int>& candidates, int target, int index) {
/*回溯实现*/
if( target ==0 ){
res.push_back(temp);
return ;
}
if(target < 0) {
return ;
}
for(int i=index;i<candidates.size();i++){
if(candidates[i]>target) continue; //剪枝
temp.push_back(candidates[i]);
backTracking(candidates,target-candidates[i],i); //微调,这里是i而不是i+1,因为可以重复使用,保证本层使用了i之后下一层也可以使用i
temp.pop_back();
}
return ;
}
};
40、组合总和2(该题很好地讲解了去重的原理,多看)
class Solution {
public:
void backTracking(vector<vector<int>>& res,vector<int>& candidates,vector<int>& path,int target,int index,vector<bool>& used){
//回溯
if(candidates.size()==0 || target==0){
res.push_back(path);
return;
}
for(int i=index;i<candidates.size();i++){
if(i>0 && candidates[i]==candidates[i-1] && used[i-1]==false) continue; //细节,used[i-1]==false,如果为true,则为树枝上的去重
used[i] = true;
path.push_back(candidates[i]);
backTracking(res,candidates,path,target-candidates[i],i+1,used);
used[i] = false;
path.pop_back();
}
return;
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
/*典型回溯算法*/
vector<vector<int>> res;
vector<int> path;
vector<bool> used(candidates.size(),false);
sort(candidates.begin(),candidates.end());
backTracking(res,candidates,path,target,0,used);
return res;
}
};
131、分割回文串(多看,对于理解index和i的关系有好处)
class Solution {
public:
bool isOrNo(string s){
//该函数用于判断该子串是否是回文串
int n=s.size();
for(int i=0;i<n/2;i++){
if(s[i]!=s[n-1-i]) return false;
}
return true;
}
void backTracking(vector<vector<string>>& res,string& s,vector<string>& path,int index){
/*回溯*/
if( index ==s.size()){
res.push_back(path);
return;
}
for(int i=index;i<s.size();i++){
string s1;
for(int j=index;j<=i;j++){ //想清楚为什么是从index到i
s1.push_back(s[j]);
}
if(isOrNo(s1)){
path.push_back(s1);
backTracking(res,s,path,i+1);
path.pop_back();
}else{
continue;
}
}
return;
}
vector<vector<string>> partition(string s) {
/*回溯算法,分割*/
vector<vector<string>> res;
vector<string> path;
backTracking(res,s,path,0);
return res;
}
};
93、复原IP地址
class Solution {
public:
int cnt=0;
bool isIP(const string path){
//该函数用于判断字符是否为0-255之间
int n=path.size();
if(n>3) return false;
if(path[0]=='0' && n!=1) return false;
if(n==3){
int val = 0;
for(int i=0;i<3;i++){
val=val*10+path[i]-'0';
}
if(val>255) return false;
else return true;
}
return true;
}
void backTracking(vector<string>& res,const string s,string& path,int index){
//回溯
if(cnt==4 && index==s.size()){
res.push_back(path);
return;
}
for(int i=index;i<s.size();i++){
string temp;
if(cnt<=2){
for(int j=index;j<=i;j++){
temp.push_back(s[j]);
}
}else{
for(int j=index;j<s.size();j++){
temp.push_back(s[j]);
}
}
if(isIP(temp)){
int sz=path.size();
if(cnt==0) path+=temp;
else{
path=path+'.'+temp;
}
cnt++;
backTracking(res,s,path,i+1);
path.resize(sz);
cnt--;
}else{
break;
}
}
return;
}
vector<string> restoreIpAddresses(string s) {
/*分割字符串问题
*/
vector<string> res;
string path;
backTracking(res,s,path,0);
return res;
}
};
78、子集
class Solution {
public:
void backTracking(vector<vector<int>>& res,vector<int>& path,const vector<int> nums,int index){
//回溯
res.push_back(path);
if(index==nums.size()){
return;
}
for(int i=index;i<nums.size();i++){
path.push_back(nums[i]);
backTracking(res,path,nums,i+1);
path.pop_back();
}
return;
}
vector<vector<int>> subsets(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<vector<int>> res;
vector<int> path;
backTracking(res,path,nums,0);
return res;
}
};
贪心算法
贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
贪心算法一般分为如下四步:
将问题分解为若干个子问题
找出适合的贪心策略
求解每一个子问题的最优解
将局部最优解堆叠成全局最优解
455、分发饼干
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
/*将胃口值g进行排序,优先满足小胃口的*/
sort(g.begin(),g.end());
sort(s.begin(),s.end());
int n=g.size(),m=s.size();
int cnt =0;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(g[i]<=s[j]){
cnt++;
s[j]=0;
break;
}
}
}
return cnt;
}
};
376、摆动序列
动态规划
动规五部曲:
确定dp数组(dp table)以及下标的含义
确定递推公式
dp数组如何初始化
确定遍历顺序
举例推导dp数组
509、斐波那契数
class Solution {
public:
int fib(int n) {
/*动态规划问题*/
if(n==0 ) return 0;
if(n==2|| n==1) return 1;
vector<int> DP;
DP.push_back(0);
DP.push_back(1);
int i=2;
for(i;i<=n;i++){
DP.push_back(DP[i-2]+DP[i-1]);
}
return DP[n];
}
};
70、爬楼梯
class Solution {
public:
int climbStairs(int n) {
/*动态规划
递推公式f(n)=f(n-1)+f(n-2)
DP数组表示爬i阶有DP[i]种方法
初始化DP(0)=1,DP(1)=1,DP(2)=2
从前向后遍历*/
vector<int> DP(n+1);
DP[0]=1;DP[1]=1;
for(int i=2;i<=n;i++){
DP[i] = DP[i-1]+DP[i-2];
}
return DP[n];
}
};
可以优化DP数组,只存储两个值即可
class Solution {
public:
int climbStairs(int n) {
/*动态规划
递推公式f(n)=f(n-1)+f(n-2)
DP数组表示爬i阶有DP[i]种方法
初始化DP(0)=1,DP(1)=1,DP(2)=2
从前向后遍历*/
vector<int> DP(2);
DP[0]=1;DP[1]=1;
for(int i=2;i<=n;i++){
int temp = DP[0]+DP[1];
DP[0] = DP[1];
DP[1] = temp;
}
return DP[1];
}
};
746、使用最小花费爬楼梯
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
/*动态规划
DP[i]表示当前在第i个台阶,下一步累积的最低花费
初始化:DP[0]=0,DP[1]=0
递推公式:f(n)=min(f(n-1)+cost(n-1),f(n-2)+cost(n-2))
*/
int n=cost.size();
if(n==0) return 0;
vector<int> DP(n+1);
DP[0]=0;DP[1]=0;
for(int i=2;i<=n;i++){
DP[i] = min(DP[i-1]+cost[i-1],DP[i-2]+cost[i-2]);
}
return DP[n];
}
};
62、不同路径
class Solution {
public:
int uniquePaths(int m, int n) {
/*动态规划
DP[i][j]表示走到第i,j位置总共有的路径条数
初始化DP[0][j]=1,DP[i][0]=1;
递推公式:DP[i][j]=DP[i-1][j]+DP[i][j-1],注意边界条件
遍历顺序:*/
vector<vector<int>> A;
for(int i=0;i<m;i++){
vector<int> temp(n,1);
A.push_back(temp);
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
A[i][j]=A[i-1][j]+A[i][j-1];
}
}
return A[m-1][n-1];
}
};
63、不同路径2
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
/*动态规划:
DP[i][j]代表走到第i,j位置需要的路径数,如果有障碍则置为0
DP[i][0]=1,DP[0][j]=1,如果边界上有障碍,则后续为0
递推关系:分四种情况,没障碍,障碍在上面,障碍在左边,两边都有障碍
遍历顺序:*/
vector<vector<int>> DP;
int m = obstacleGrid.size();
int n = obstacleGrid[0].size();
//DP初始化
for(int i=0;i<m;i++){
vector<int> temp(n,0);
DP.push_back(temp);
}
for(int i=0;i<n;i++){
if(obstacleGrid[0][i]==1) break;
DP[0][i]=1;
}
for(int i=0;i<m;i++){
if(obstacleGrid[i][0]==1) break;
DP[i][0]=1;
}
//遍历DP数组
for(int j=1;j<n;j++){
for(int i=1;i<m;i++){
if(obstacleGrid[i-1][j]!=1 && obstacleGrid[i][j-1]!=1) {
DP[i][j]=DP[i-1][j]+DP[i][j-1];
}
else if(obstacleGrid[i-1][j]==1 && obstacleGrid[i][j-1]!=1){
DP[i][j]=DP[i][j-1];
}
else if(obstacleGrid[i][j-1]==1 && obstacleGrid[i-1][j]!=1){
DP[i][j]=DP[i-1][j];
}
else{
DP[i][j]=0;
}
}
}
if(obstacleGrid[m-1][n-1]==1) return 0;
else return DP[m-1][n-1];
}
};
343、整数拆分
该题难在递推关系,怎么推导。
class Solution {
public:
int integerBreak(int n) {
/*DP[i]表示拆分i,返回的乘积最大值
初始值:DP[1]=1,DP[2]=1,DP[3] =2
递推关系:DP[i] = max(j*DP[i-j],j*(i-j)),j从1到i
遍历顺序:依次*/
vector<int> DP(n+1,1);
DP[1]=1; DP[2]=1;
for(int i=3;i<n+1;i++){
for(int j=1;j<i;j++){
int temp = DP[i];
DP[i] = max(j*(i-j),j*DP[i-j]);
DP[i] = max(DP[i],temp);
}
}
return DP[n];
}
};
96、不同的二叉搜索树
关键在于递推公式为:
D
P
[
i
]
=
∑
j
=
0
i
−
1
D
P
[
j
]
D
P
[
i
−
j
−
1
]
DP[i]=\sum_{j=0}^{i-1} DP[j]DP[i-j-1]
DP[i]=j=0∑i−1DP[j]DP[i−j−1]
class Solution {
public:
int numTrees(int n) {
/*动态规划,难点在于递推公式*/
vector<int> DP(n+1,0);
DP[0]=1;DP[1]=1;
for(int i=2;i<=n;i++){
for(int j=0;j<i;j++){
DP[i] += DP[j]*DP[i-j-1];
}
}
return DP[n];
}
};
动规:背包问题
有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
01背包问题
每个物品只有一个,求背包能容纳的最大价值。(看代码随想录讲解,很详细)
两个关注点:1、递推公式的推导,2、两个for循环遍历的顺序(如果求组合数,外层for循环遍历物品,内层for遍历背包,如果求排列数,外层for遍历背包,内层for遍历物品,01背包内层for循环逆序遍历,完全背包内层for循环顺序遍历)。
注意:怎么理解遍历背包时,逆序是01背包,顺序是完全背包。因为,DP数组本身每一项DP[i]都是依赖于DP[0~i-1]前i-1项推导出来的,如果顺序则会出现套娃的情况,实现的效果为每个物品任取背包容量大小次。而逆序,先更新第i+1,后更新第i项,更新结果取决于初始值,则每个物品只取了一次。
416、分割等和子集
class Solution {
public:
bool canPartition(vector<int>& nums) {
/*每个数取一次,看成动态规划里的01背包问题,本题物品价值等于物品大小
DP[j]表示,背包大小为j的背包,所能装载的最大价值
初始化:DP[0]=0,DP[j]=0
递推公式:DP[j]=max{DP[j],DP[j-nums[i]]+nums[i]}
遍历顺序:先物品i,再背包大小j(逆序)
打印DP数组*/
int sum=0;
int n=nums.size();
for(int i=0;i<n;i++){
sum+=nums[i];
}
int target = sum/2;
if(sum%2==1) return false;
vector<int> DP(target+1,0);
for(int i=0;i<n;i++){
for(int j=target;j>=nums[i];j--){
DP[j] = max(DP[j],DP[j-nums[i]]+nums[i]);
}
}
if(DP[target]==target) return true;
return false;
}
};
1049、最后一块石头的重量2
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
/*分解为,将石头分为大小和几乎相等的两堆,
相撞后差的最小值,如果两堆大小相等则返回0*/
int n=stones.size();
int sum=0;
for(int i=0;i<n;i++){
sum+=stones[i];
}
int target = sum/2;
vector<int> DP(target+1,0);
for(int i=0;i<n;i++){
for(int j=target;j>=stones[i];j--){
DP[j] = max(DP[j],DP[j-stones[i]]+stones[i]);
}
}
return abs(sum-2*DP[target]);
}
};
494、目标和
DP[j]表示装满大小为j的背包有DP[j]中方法。
递推公式:
D
P
[
j
]
=
∑
i
=
0
n
−
1
D
P
[
j
−
n
u
m
s
[
i
]
]
DP[j]=\sum_{i=0}^{n-1} DP[j-nums[i]]
DP[j]=i=0∑n−1DP[j−nums[i]]
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
/*动规五部曲*/
int sum=0;
for(int i:nums) sum+=i;
if(abs(target)>sum) return 0;
if((target+sum)%2==1) return 0;
int left = (target+sum)/2;
vector<int> DP(left+1,0);
DP[0] =1;
for(int i=0;i<nums.size();i++){
for(int j=left;j>=nums[i];j--){
DP[j]+=DP[j-nums[i]];
}
}
return DP[left];
}
};
474、一和零
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
vector<vector<int>> dp(m + 1, vector<int> (n + 1, 0)); // 默认初始化0
for (string str : strs) { // 遍历物品
int oneNum = 0, zeroNum = 0;
for (char c : str) {
if (c == '0') zeroNum++;
else oneNum++;
}
for (int i = m; i >= zeroNum; i--) { // 遍历背包容量且从后向前遍历!
for (int j = n; j >= oneNum; j--) {
dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
}
}
}
return dp[m][n];
}
};
完全背包问题
每个物品可以使用无数次
518、零钱兑换2
递推公式:
D
P
[
j
]
=
∑
i
=
0
n
−
1
D
P
[
j
−
c
o
i
n
[
i
]
]
DP[j]=\sum_{i=0}^{n-1} DP[j-coin[i]]
DP[j]=i=0∑n−1DP[j−coin[i]]
class Solution {
public:
int change(int amount, vector<int>& coins) {
/*动规五部曲
dp[j] 就是所有的dp[j - coins[i]](考虑coins[i]的情况)相加。*/
int n=coins.size();
vector<int> DP(amount+1,0);
DP[0]=1;
for(int i=0;i<n;i++){
for(int j=coins[i];j<=amount;j++){
DP[j]+=DP[j-coins[i]];
}
}
return DP[amount];
}
};
377、组合总和4
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
/**/
int n=nums.size();
vector<int> DP(target+1,0);
DP[0]=1;
for (int i = 0; i <= target; i++) { // 遍历背包
for (int j = 0; j < n; j++) { // 遍历物品
if (i - nums[j] >= 0 && DP[i] < INT_MAX - DP[i - nums[j]]) {
DP[i] += DP[i - nums[j]];
}
}
}
return DP[target];
}
};
322、零钱兑换
递推公式:
D
P
[
j
]
=
m
i
n
i
=
0
n
−
1
(
D
P
[
j
]
,
D
P
[
j
−
c
o
i
n
s
[
i
]
]
+
1
)
DP[j]=min_{i=0}^{n-1}(DP[j],DP[j-coins[i]]+1)
DP[j]=mini=0n−1(DP[j],DP[j−coins[i]]+1)
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
/*动规五部曲*/
int n=coins.size();
vector<int> DP(amount+1,INT_MAX-1);
DP[0]=0;
for(int i=0;i<n;i++){
for(int j=coins[i];j<=amount;j++){
DP[j]=min(DP[j],DP[j-coins[i]]+1);
}
}
if(DP[amount]==INT_MAX-1) return -1;
else return DP[amount];
}
};
279、完全平方数
递推公式:
D
P
[
j
]
=
m
i
n
i
=
0
n
u
m
s
.
s
i
z
e
(
)
−
1
(
D
P
[
j
]
,
D
P
[
j
−
n
u
m
s
[
i
]
]
+
1
)
DP[j]=min_{i=0}^{nums.size()-1}(DP[j],DP[j-nums[i]]+1)
DP[j]=mini=0nums.size()−1(DP[j],DP[j−nums[i]]+1)
遍历顺序:完全背包问题,且求组合,先物品,后背包,背包顺序遍历。
class Solution {
public:
int numSquares(int n) {
/*递归五部曲*/
vector<int> nums;
for(int i=1;i*i<=n;i++){
nums.push_back(i*i);
}
vector<int> DP(n+1,INT_MAX);
DP[0]=0;DP[1]=1;
for(int i=0;i<nums.size();i++){
for(int j=nums[i];j<=n;j++){
DP[j]=min(DP[j],DP[j-nums[i]]+1);
}
}
return DP[n];
}
};
139、单词拆分
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(),wordDict.end());
int n=s.size();
vector<bool> DP(n+1,false);
DP[0] = true;
for(int j=1;j<=n;j++){ //遍历背包
for(int i=0;i<j;i++){ //遍历物品
string temp = s.substr(i,j-i);
if(wordSet.find(temp)!=wordSet.end() && DP[i])
DP[j] = true;
}
}
return DP[n];
}
};
动规:打家劫舍系列
198、打家劫舍
DP[j] 表示j个房间能偷到的最高金额
递推公式:
D
P
[
j
]
=
m
a
x
(
D
P
[
j
−
1
]
,
D
P
[
j
−
2
]
+
n
u
m
s
[
j
−
1
]
)
DP[j] = max(DP[j-1],DP[j-2]+nums[j-1])
DP[j]=max(DP[j−1],DP[j−2]+nums[j−1])
初始化:DP[0]=0,DP[1]=nums[0]
遍历顺序:顺序
打印DP数组
class Solution {
public:
int rob(vector<int>& nums) {
/*动规五部曲*/
int n=nums.size();
if(n==1) return nums[0];
vector<int> DP(n+1,0);
DP[1]=nums[0];
for(int i=2;i<=n;i++){
DP[i]=max(DP[i-1],DP[i-2]+nums[i-1]);
}
return DP[n];
}
};
213、打家劫舍2
内容同1,将1进行拆分成两个数组。
class Solution {
public:
int rob_old(vector<int>& nums) {
/*动规五部曲*/
int n=nums.size();
if(n==1) return nums[0];
vector<int> DP(n+1,0);
DP[1]=nums[0];
for(int i=2;i<=n;i++){
DP[i]=max(DP[i-1],DP[i-2]+nums[i-1]);
}
return DP[n];
}
int rob(vector<int>& nums) {
/*将打家劫舍1分装成函数,将该问题拆分为两个子数组,分别传入函数即可*/
int n = nums.size();
if(n==1) return nums[0];
vector<int> nums_pre,nums_sub;
for(int i=0;i<n;i++){
if(i==0){
nums_pre.push_back(nums[i]);
}else if(i==n-1){
nums_sub.push_back(nums[i]);
}else{
nums_sub.push_back(nums[i]);
nums_pre.push_back(nums[i]);
}
}
return max(rob_old(nums_pre),rob_old(nums_sub));
}
};
337、打家劫舍3(没怎么看懂)
动态规划,使用01DP数组
class Solution {
public:
int rob(TreeNode* root) {
vector<int> result = robTree(root);
return max(result[0], result[1]);
}
// 长度为2的数组,0:不偷,1:偷
vector<int> robTree(TreeNode* cur) {
if (cur == nullptr) return vector<int>{0, 0};
vector<int> left = robTree(cur->left);
vector<int> right = robTree(cur->right);
// 偷cur,那么就不能偷左右节点。
int val1 = cur->val + left[0] + right[0];
// 不偷cur,那么可以偷也可以不偷左右节点,则取较大的情况
int val2 = max(left[0], left[1]) + max(right[0], right[1]);
return {val2, val1};
}
};
动规:股票系列
121、买卖股票的最佳时机
DP[i]表示第i天卖出股票所能获得的最大利润为DP[i]
递推公式:
D
P
[
i
]
=
p
r
i
c
e
s
[
i
]
−
m
i
n
.
p
r
e
DP[i]=prices[i]-min.pre
DP[i]=prices[i]−min.pre其中min.pre表示第i天及之前所出现的最低价格
class Solution {
public:
int maxProfit(vector<int>& prices) {
/*动态规划*/
int min_pre=prices[0];
int n = prices.size();
vector<int> DP(n+1,0);
int max_val=0;
for(int i=1;i<n;i++){
min_pre = min(min_pre,prices[i]);
DP[i] = prices[i]-min_pre;
max_val = max(DP[i],max_val);
}
return max_val;
}
};
122、买卖股票的最佳时机2
没使用动态规划,直接做差求和。代码如下
class Solution {
public:
int maxProfit(vector<int>& prices) {
/*遍历一遍prices如果前项小于后项,则做差,否则为0*/
int n=prices.size();
int sum=0;
for(int i=1;i<n;i++){
int k = prices[i]-prices[i-1];
if(k>0) sum+=k;
}
return sum;
}
};
动规:子序列系列
300、最长递归子序列
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
/*难点在于DP数组的定义,以及递推公式*/
int n=nums.size();
vector<int> DP(n,1);
int max_val=1;
for(int i=1;i<n;i++){
for(int j=0;j<i;j++){
if(nums[i]>nums[j]) DP[i] = max(DP[j]+1,DP[i]);
}
max_val = max(max_val,DP[i]);
}
return max_val;
}
};
674、最长连续递增序列
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
int n=nums.size();
vector<int> DP(n,1);
int max_val = 1;
for(int i=1;i<n;i++){
if(nums[i]>nums[i-1]) DP[i]=DP[i-1]+1;
max_val=max(max_val,DP[i]);
}
return max_val;
}
};
718、最长重复(连续)子数组
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
/*本题难点在于DP数组的定义
DP[i][j]定义为以i-1为结尾的nums1,和以j-1为结尾的nums2,最长重复子数组的长度
递推公式:DP[i][j]=DP[i-1][j-1]+1,if(nums1[i]==nums2[j])*/
int m=nums1.size();
int n=nums2.size();
vector<vector<int>> DP(m+1,vector<int> (n+1,0));
int max_val=0;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(nums1[i-1]==nums2[j-1]) DP[i][j]=DP[i-1][j-1]+1;
max_val=max(max_val,DP[i][j]);
}
}
return max_val;
}
};
1143、最长公共子序列(不连续)
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
/*子序列,不连续*/
int m=text1.size();
int n=text2.size();
vector<vector<int>> DP(m+1,vector<int> (n+1,0));
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(text1[i-1]==text2[j-1]) DP[i][j] = DP[i-1][j-1]+1;
else DP[i][j] = max(DP[i][j-1],DP[i-1][j]);
}
}
return DP[m][n];
}
};
1035、不相交的线
class Solution {
public:
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
/*动态规划
DP[i][j]表示以i-1为结尾的nums1和以j-1为结尾的nums2的最大连线数
DP[i][j]=DP[i-1][j-1] if(nums1[i-1]==nums2[j-1])
=max(DP[i-1][j],DP[i][j-1]) else
初始化:全部为0
遍历顺序
打印DP数组*/
int n=nums1.size();
int m=nums2.size();
vector<vector<int>> DP(n+1,vector<int> (m+1,0));
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(nums1[i-1]==nums2[j-1]) DP[i][j]=DP[i-1][j-1]+1;
else DP[i][j] = max(DP[i-1][j],DP[i][j-1]);
}
}
return DP[n][m];
}
};
53、最大子数组和
如果使用双指针,会超时。建议动态规划
class Solution {
public:
int maxSubArray(vector<int>& nums) {
/*双指针*/
int n=nums.size();
if(n==0) return 0;
int sum=0;
int max_val=nums[0];
for(int left=0;left<n;left++){
for(int right=left;right<n;right++){
int temp_val=0;
for(int temp=left;temp<=right;temp++){
temp_val+=nums[temp];
}
if(temp_val<=0) {
max_val=max(max_val,nums[right]);
left=right+1;
continue;
}
max_val=max(max_val,temp_val);
}
}
return max_val;
}
};
动规:
DP[i]表示以i为结尾的数组nums中最大的子数组和为DP[i]
递推公式
D
P
[
i
]
=
m
a
x
(
D
P
[
i
−
1
]
+
n
u
m
s
[
i
]
,
n
u
m
s
[
i
]
)
DP[i]=max(DP[i-1]+nums[i],nums[i])
DP[i]=max(DP[i−1]+nums[i],nums[i])
class Solution {
public:
int maxSubArray(vector<int>& nums) {
/*难点在于递推公式即DP数组的定义*/
int n=nums.size();
if(n==0) return 0;
vector<int> DP(n,0);
DP[0] = nums[0];
int max_val=DP[0];
for(int i=1;i<n;i++){
DP[i] = max(DP[i-1]+nums[i],nums[i]);
max_val = max(DP[i],max_val);
}
return max_val;
}
};
392、判断子序列
class Solution {
public:
bool isSubsequence(string s, string t) {
//双指针
int s_size = s.size();
int t_size = t.size();
int i=0;
for(int j=0;j<t_size;j++){
if(t[j]==s[i]) i++;
}
if(i==s_size) return true;
else return false;
}
};
115、不同的子序列
uint64_t,注意写法
class Solution {
public:
int numDistinct(string s, string t) {
/*难点在于DP数组的定义,以及递推公式,初始化*/
int n=s.size();
int m=t.size();
vector<vector<uint64_t>> DP(n+1,vector<uint64_t> (m+1,0));
for(int i=0;i<=n;i++) DP[i][0] = 1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(s[i-1]==t[j-1]) DP[i][j] = DP[i-1][j-1]+DP[i-1][j];
else DP[i][j] = DP[i-1][j];
}
}
return DP[n][m];
}
};