leetcode(力扣)刷题笔记(c )【中】(力扣怎么刷java题)-pg电子平台

大家好!今天让小编来大家介绍下关于leetcode(力扣)刷题笔记(c )【中】的问题,以下是酷知号的小编对此问题的归纳整理,让我们一起来看看吧。

文章预览:

  • 回溯算法
    • 77. 组合
    • 216.组合总和iii
    • 17.电话号码的字母组合
    • 39. 组合总和
    • 40.组合总和ii
    • 131.分割回文串
    • 93.复原ip地址
    • 78. 子集
    • 90.子集ii
    • 491.递增子序列
    • 46.全排列
    • 47. 全排列 ii
    • 332.重新安排行程
    • 第51题. n皇后
    • 37. 解数独
  • 贪心算法
    • 455. 分发饼干
    • 376. 摆动序列
    • 53. 最大子和
    • 122. 买卖股票的最佳时机 ii
    • 55. 跳跃游戏
    • 45. 跳跃游戏 ii
    • 1005. k 次取反后最大化的数组和
    • 134. 加油站
    • 135. 分发糖果
    • 860.柠檬水找零
    • 406.根据身高重建队列
    • 452. 用最少数量的箭引爆气球
    • 435. 无重叠区间
    • 763.划分字母区间
    • 56. 合并区间
    • 738. 单调递增的数字
    • 714. 买卖股票的最佳时机含手续费
    • 968.监控二叉树
  • 动态规划
    • 509. 斐波那契数
    • 70. 爬楼梯
    • 746. 使用最小花费爬楼梯
    • 62.不同路径
    • 63. 不同路径 ii
    • 343. 整数拆分
    • 96.不同的二叉搜索树
    • 0-1背包理论基础
    • 416. 分割等和子集
    • 1049. 最后一块石头的重量 ii
    • 494. 目标和
    • 474.一和零
    • 518. 零钱兑换 ii
    • 377. 组合总和 ⅳ
    • 322. 零钱兑换
    • 279.完全平方数
    • 139. 单词拆分
    • 198. 打家劫舍
    • 213. 打家劫舍 ii
    • 337. 打家劫舍 iii
    • 121. 买卖股票的最佳时机
    • 122.买卖股票的最佳时机ii
    • 123.买卖股票的最佳时机iii
    • 188.买卖股票的最佳时机iv
    • 309.最佳买卖股票时机含冷冻期
    • 714.买卖股票的最佳时机含手续费
    • 300.最长递增子序列
    • 674. 最长连续递增序列
    • 718. 最长重复子数组
    • 1143.最长公共子序列
    • 1035.不相交的线
    • 53. 最大子数组和
    • 392. 判断子序列
    • 115. 不同的子序列
    • 583. 两个字符串的删除操作
    • 72. 编辑距离
    • 编辑距离总结篇
    • 647. 回文子串
    • 516.最长回文子序列

参考刷题链接代码随想录

77. 组合

c
法一:回溯

class solution {
public:vector<vector<int>> result;vector<int> temp;void backtracking(int max,int min,int k,int depth){if(depth==k){result.push_back(temp);return;}depth;//当前为第几层,树的深度for(int i=min;i<=max;i){temp.push_back(i);backtracking(max,i1,k,depth);temp.pop_back();//回溯}}vector<vector<int>> combine(int n, int k) {//k为树的深度,n为第一层的宽度backtracking(n,1,k,0);return result;}
};

剪枝优化
这种写法和代码随想录的写法不一样,但是思路一致

class solution {
public:vector<vector<int>> result;vector<int> temp;void backtracking(int max,int min,int k,int depth){if(max-min1depth<k) return;//剪枝优化,如果可以访问到的元素不够k个数则返回,不进行循环if(depth==k){result.push_back(temp);return;}depth;//当前为第几层,树的深度,相当于已经选择的元素个数for(int i=min;i<=max;i){temp.push_back(i);backtracking(max,i1,k,depth);temp.pop_back();//回溯}}vector<vector<int>> combine(int n, int k) {//k为树的深度,n为第一层的宽度backtracking(n,1,k,0);return result;}
};

216.组合总和iii

c
法一:回溯

class solution {
public:vector<vector<int>> result;vector<int> temp;//存放路径值int s=0;//和void backtracking(int max,int min,int s,int k,int n){if(s==n&&temp.size()==k){//相加之和为n且为k个数result.push_back(temp);return;}for(int i=min;i<=max;i){temp.push_back(i);s =i;backtracking(max,i1,s,k,n);s-=i;temp.pop_back();}}vector<vector<int>> combinationsum3(int k, int n) {int max=9;int min=1;backtracking(max,min,s,k,n);return result;}
};

剪枝优化

class solution {
public:vector<vector<int>> result;vector<int> temp;int s=0;void backtracking(int max,int min,int s,int k,int n){if(max-min1temp.size()<k||s>n) return;//剪枝优化,个数不足或者超过了目标值均返回if(temp.size()==k){//且为k个数if(s==n) result.push_back(temp);//相加之和为nreturn;//访问了k个数就返回}for(int i=min;i<=max;i){temp.push_back(i);s =i;backtracking(max,i1,s,k,n);s-=i;temp.pop_back();}}vector<vector<int>> combinationsum3(int k, int n) {int max=9;int min=1;backtracking(max,min,s,k,n);return result;}
};

17.电话号码的字母组合

c
法一:回溯
注意需要按照给出电话按键的顺序进行字母的组合
如果在现场面试的时候,一定要注意各种输入异常的情况,例如本题输入1 * #按键。

class solution {
private:const string lettermap[10] = {"", // 0"", // 1"abc", // 2"def", // 3"ghi", // 4"jkl", // 5"mno", // 6"pqrs", // 7"tuv", // 8"wxyz", // 9};
public:vector<string> result;string temp;void backtracking(string &digits,int index){if(index==digits.size()){result.push_back(temp);return;}int k=digits[index]-'0';//取digits的第index个值index;string letter=lettermap[k];for(int i=0;i<letter.size();i){temp.push_back(letter[i]);backtracking(digits,index);temp.pop_back();}}vector<string> lettercombinations(string digits) {if(digits.size()==0) return result;backtracking(digits,0);return result;}
};

39. 组合总和

c
法一:回溯

class solution {
public:vector<vector<int>> result;vector<int> temp;int s=0;void backtracking(vector<int>& candidates,int target,int index){if(s>target) return;if(s==target){result.push_back(temp);return;}for(int i=index;i<candidates.size();i){//注意每次开始的下标不是0temp.push_back(candidates[i]);s =candidates[i];backtracking(candidates,target,i);s-=candidates[i];temp.pop_back();}}vector<vector<int>> combinationsum(vector<int>& candidates, int target) {//每一层的元素都相同backtracking(candidates,target,0);return result;}
};

剪枝优化:如果已经知道下一层的sum会大于target,就没有必要进入下一层递归了。

class solution {
public:vector<vector<int>> result;vector<int> temp;int s=0;void backtracking(vector<int>& candidates,int target,int index){if(s>target) return;if(s==target){result.push_back(temp);return;}for(int i=index;i<candidates.size()&&s<=target;i){//条件判断里加了剪枝优化temp.push_back(candidates[i]);s =candidates[i];backtracking(candidates,target,i);s-=candidates[i];temp.pop_back();}}vector<vector<int>> combinationsum(vector<int>& candidates, int target) {//每一层的元素都相同backtracking(candidates,target,0);return result;}
};

40.组合总和ii

c
法一:回溯,在插入路径时利用find函数进行去重,但是超时了,代码如下

class solution {
public: vector<vector<int>> result;vector<int> temp;int s=0;void backtracking(vector<int>& candidates, int target,int index){if(s>target) return;if(s==target){vector<vector<int>>::iterator it=find(result.begin(),result.end(),temp);if(it==result.end()) result.push_back(temp);return;}for(int i=index;i<candidates.size();i){s =candidates[i];temp.push_back(candidates[i]);backtracking(candidates,target,i1);s-=candidates[i];temp.pop_back();}}vector<vector<int>> combinationsum2(vector<int>& candidates, int target) {sort(candidates.begin(),candidates.end());backtracking(candidates,target,0);return result;}
};

优化:如果同一树层中遇到了重复元素就跳过,因为前面的重复元素和后面元素的组合一定会重复。注意if条件中i>index必须写在&&的前面,不然会报错。

class solution {
public: vector<vector<int>> result;vector<int> temp;int s=0;void backtracking(vector<int>& candidates, int target,int index){if(s>target) return;if(s==target){result.push_back(temp);return;}for(int i=index;i<candidates.size();i){if(i>index&&candidates[i]==candidates[i-1]) continue;//去重步骤,同一层的节点值不能重复s =candidates[i];temp.push_back(candidates[i]);backtracking(candidates,target,i1);s-=candidates[i];temp.pop_back();}}vector<vector<int>> combinationsum2(vector<int>& candidates, int target) {sort(candidates.begin(),candidates.end());backtracking(candidates,target,0);return result;}
};

131.分割回文串

c
法一:回溯。
每层按照1~s.size()的大小依次进行截取

class solution {
public:vector<vector<string>> result;vector<string> temp;bool ispalindrome(const string &s,int start,int end){//这里必须要加const,因为回溯函数中加了//双指针判断是否为回文串for(int i=start,j=end;i<j;i,j--){if(s[i]!=s[j]) return false;}return true;}void backtracking(const string &s,int start_index){if(start_index >= s.size()){//分割结束result.push_back(temp);return;}for(int i=start_index;i<s.size();i){if(ispalindrome(s,start_index,i)) {//是回文串string str=s.substr(start_index,i-start_index1);//start_index位置开始的子串temp.push_back(str);}else continue;//不是回文串就跳过backtracking(s,i1);temp.pop_back();}}vector<vector<string>> partition(string s) {backtracking(s,0);return result;}
};

93.复原ip地址

c
法一:回溯
先分割三次,就会形成四段字符串,循环里每次都会对前三段字符串是否有效进行判断,结束条件里面会对第四段进行判断,但是都不能忘记path需要回溯

class solution {
public:vector<string> result;string path;bool isvalid(const 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){num = num * 10  (s[i] - '0');if(num>255) return false;}return true;}void backtracking(const string &s,int index,int depth){if(depth==3){//分割三次后就进行处理判断剩下的字符串是否符合要求if(isvalid(s,index,s.size()-1)){string str=s.substr(index,s.size()-index);path =str;result.push_back(path);for(int j=0;j<str.size();j)path.pop_back();//这里也需要回溯}return;}for(int i=index;i<s.size();i){string str=s.substr(index,i-index1);//[index,i]之间的字符串if(isvalid(s,index,i)){//字符串符合要求path =str; depth;if(depth<4) path ="."; backtracking(s,i1,depth);for(int j=0;j<str.size();j)path.pop_back();//这样写每次只会弹出一个字符,但是str不止一个字符,所以需要加循环处理if(depth<4) path.pop_back();depth--;}else break;}}vector<string> restoreipaddresses(string s) {//树的深度为四层,if (s.size() < 4 || s.size() > 12) return result; // 算是剪枝了backtracking(s,0,0);return result;}
};

78. 子集

c
法一:回溯

class solution {
public:vector<vector<int>> result;vector<int> temp;void backtracking(vector<int>& nums,int index){if(true){//每种分割的结果都放进去result.push_back(temp);//后面就不要再加上return了}for(int i=index;i<nums.size();i){temp.push_back(nums[i]);backtracking(nums,i1);temp.pop_back();}}vector<vector<int>> subsets(vector<int>& nums) {backtracking(nums,0);return result;}
};

90.子集ii

c
法一:回溯
注意:去重必须要先对数组进行排序!

class solution {
public:vector<vector<int>> result;vector<int> temp;void backtracking(vector<int>& nums,int index){result.push_back(temp);for(int i=index;i<nums.size();i){if(i>index&&nums[i]==nums[i-1]){continue;}else{temp.push_back(nums[i]);backtracking(nums,i1);temp.pop_back();                }}}vector<vector<int>> subsetswithdup(vector<int>& nums) {sort(nums.begin(),nums.end());backtracking(nums,0);return result;}
};

491.递增子序列

c
法一:回溯
踩坑记录:采用和上一题90题类似的去重写法,但是这样做只能去除前后出现的重复数字,如果后面又出现了重复数字就去重不了,比如数组[1,2,2,7,5,7]

class solution {
public:vector<vector<int>> result;vector<int> temp;void backtracjing(vector<int>& nums,int index){if(temp.size()>1) result.push_back(temp);for(int i=index;i<nums.size();i){if(i>index&&nums[i]==nums[i-1]){//因为并不是有序数组,所以这样做只能去除前后出现的重复数字,如果后面又出现了重复数字就去重不了continue;}if(temp.size()>0&&nums[i]<temp.back()) continue;temp.push_back(nums[i]);backtracjing(nums,i1);temp.pop_back();}}vector<vector<int>> findsubsequences(vector<int>& nums) {backtracjing(nums,0);return result;}
};

改进后:需要直到同一层中哪些数字用过了,一个for循环就代表横向树的一层,所以在for循环上面添加一个unordered_set容器(该容器底层为哈希表,且不会自动排序,这道题并不需要对元素进行排序),unordered_set中不允许有重复的元素。同一层的元素会出现在路径容器的同一个位置,所以不能出现重复值,回溯代码最后不需要对unordered_set删除nums[i]这个元素,因为unordered_set只记录同一层的元素哪些用过了

class solution {
public:vector<vector<int>> result;vector<int> temp;  void backtracjing(vector<int>& nums,int index){if(temp.size()>1) result.push_back(temp);unordered_set<int> use_num;//这里也可以用数组,耗时更少for(int i=index;i<nums.size();i){if(use_num.find(nums[i])!=use_num.end()){continue;}use_num.insert(nums[i]);//注意后面不需要再删除这个元素if(temp.size()>0&&nums[i]<temp.back()) continue;//保持递增顺序temp.push_back(nums[i]);backtracjing(nums,i1);temp.pop_back();}}vector<vector<int>> findsubsequences(vector<int>& nums) {backtracjing(nums,0);return result;}
};

46.全排列

c
法一:回溯
需要查找temp里面已经用过的元素,如果用过了就不能再用了,且每次循环都是从0开始

class solution {
public:vector<vector<int>> result;vector<int> temp;  void backtracjing(vector<int>& nums,int index){if(temp.size()>1) result.push_back(temp);unordered_set<int> use_num;for(int i=index;i<nums.size();i){if(use_num.find(nums[i])!=use_num.end()){continue;}use_num.insert(nums[i]);//注意后面不需要再删除这个元素if(temp.size()>0&&nums[i]<temp.back()) continue;//保持递增顺序temp.push_back(nums[i]);backtracjing(nums,i1);temp.pop_back();}}vector<vector<int>> findsubsequences(vector<int>& nums) {backtracjing(nums,0);return result;}
};

47. 全排列 ii

c
法一:回溯
也可以使用unordered_set来记录同一层的元素使用情况,利用find函数来进行去重
需要注意的是:使用set去重的版本相对于数组的版本效率都要低很多

class solution {
public:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& nums,int* use_num){if(path.size()==nums.size()) {result.push_back(path);return;}for(int i=0;i<nums.size();i){//use_num[i-1]==0:同层use_num[i-1]使用过//use_num[i-1]==1:同一树枝use_num[i-1]使用过if(i>0&&nums[i]==nums[i-1]&&use_num[i-1]==0) continue;//对同一层进行去重效率更高if(use_num[i]==1) continue;//跳过本身use_num[i]=1;//1代表用过了path.push_back(nums[i]);backtracking(nums,use_num);path.pop_back();use_num[i]=0;}}vector<vector<int>> permuteunique(vector<int>& nums) {int size=nums.size();int use_num[size];//记录路径元素是否用过,默认初始化为0sort(nums.begin(),nums.end());backtracking(nums,use_num);return result;}
};

332.重新安排行程

c
法一:回溯
踩坑记录:先找出第一个机票再做回溯的做法是不对的,因为找出的最小排序不一定能把所有的机票用完
正确做法: 利用unordered_map<出发机场, map<到达机场, 航班次数>> targets来储存所有的行程中相同出发地的行程,通过判断航班次数来判断该行程是否已经用过。map中所有元素都是pair

class solution {
public:vector<string> result;// unordered_map<出发机场, map<到达机场, 航班次数>> targetsunordered_map<string, map<string, int>> targets;bool backtracking(vector<vector<string>>& tickets){if(result.size()==(1tickets.size())){return true;}for(pair<const string,int>& temp:targets[result[result.size()-1]]){//找出result的最后一个地点,也就是下一个行程的出发地点if(temp.second>0){//说明还没用过result.push_back(temp.first);//只需要放入到达机场temp.second--;if(backtracking(tickets)) return true;//result.pop_back();temp.second;}}return false;//如果没找到行程就会返回false}vector<string> finditinerary(vector<vector<string>>& tickets) {for(vector<string> &temp:tickets){targets[temp[0]][temp[1]];//targets[temp[0]]相当于是key=temp[0]对应的map容器,targets[temp[0]][temp[1]]相当于是对map容器中key=temp[1]进行赋值}result.push_back("jfk");//初始机场backtracking(tickets);return result;}
};

第51题. n皇后

c
法一:回溯
思路:通过used_column来记录用过的列,通过use_loc来记录皇后位置和会被攻击的地方,只需要将下面每一层的对角线的位置标记就可以了,但是n=6时报错了,出现皇后放在了对角线的位置。

class solution {
public:vector<vector<string>> result;vector<string> conver(vector<vector<int>>& use_loc,int n){vector<string> temp;for(int i=0;i<n;i){string str="";for(int j=0;j<n;j){if(use_loc[i][j]==2){str ="q";}else str =".";}temp.push_back(str);}return temp;}void backtracking(int n,vector<vector<int>>& use_loc,int depth,vector<bool>& used_column){if(depth==n){vector<string> temp;temp=conver(use_loc,n);result.push_back(temp);return;}for(int i=0;i<n;i){if(used_column[i]) continue;//跳过用过的列if(use_loc[depth][i]==1) continue;//跳过会被攻击的地方use_loc[depth][i]=2;//皇后//下面每一层的对角线for(int j=depth1,k=i-1;j<n && k>=0;j,k--){use_loc[j][k]=1;}for(int j=depth1,k=i1;j<n && k<n;j,k){use_loc[j][k]=1;}used_column[i]=true;depth;backtracking(n,use_loc,depth,used_column);depth--;use_loc[depth][i]=0;for(int j=depth1,k=i-1;j<n && k>=0;j,k--){use_loc[j][k]=0;}for(int j=depth1,k=i1;j<n && k<n;j,k){use_loc[j][k]=0;}used_column[i]=false;}}vector<vector<string>> solvenqueens(int n) {vector<vector<int>> use_loc(n,vector<int>(n,0));//全部初始化为0vector<bool> used_column(n,false);//用过的列backtracking(n,use_loc,0,used_column);return result;}
};

后来找出了错误,因为有些皇后的攻击的位置会有交叉,在回溯的时候将一些攻击位置置0后是不对的,该位置有可能是上层皇后的攻击位置,所以不能简单的置0和置1,应该每个位置的数值是被攻击的次数,这样回溯时才不会出错

class solution {
public:vector<vector<string>> result;vector<string> conver(vector<vector<int>>& use_loc,int n){vector<string> temp;for(int i=0;i<n;i){string str="";for(int j=0;j<n;j){if(use_loc[i][j]==-1){str ="q";}else str =".";}temp.push_back(str);}return temp;}void backtracking(int n,vector<vector<int>>& use_loc,int depth,vector<bool>& used_column){if(depth==n){vector<string> temp;temp=conver(use_loc,n);result.push_back(temp);return;}for(int i=0;i<n;i){if(used_column[i]) continue;//跳过用过的列if(use_loc[depth][i]>0) continue;//跳过会被攻击的地方use_loc[depth][i]=-1;//皇后//下面每一层的对角线for(int j=depth1,k=i-1;j<n && k>=0;j,k--){use_loc[j][k];}for(int j=depth1,k=i1;j<n && k<n;j,k){use_loc[j][k];}used_column[i]=true;depth;backtracking(n,use_loc,depth,used_column);depth--;use_loc[depth][i]=0;for(int j=depth1,k=i-1;j<n && k>=0;j,k--){use_loc[j][k]--;}for(int j=depth1,k=i1;j<n && k<n;j,k){use_loc[j][k]--;}used_column[i]=false;}}vector<vector<string>> solvenqueens(int n) {vector<vector<int>> use_loc(n,vector<int>(n,0));//全部初始化为0vector<bool> used_column(n,false);//用过的列backtracking(n,use_loc,0,used_column);return result;}
};

法二:改成下面这种判断上面的对角线中是否有皇后就会通过

class solution {
public:vector<vector<string>> result;vector<vector<int>> use_loc;vector<string> conver(vector<vector<int>>& use_loc,int n){vector<string> temp;for(int i=0;i<n;i){string str="";for(int j=0;j<n;j){if(use_loc[i][j]==2){str ="q";}else str =".";}temp.push_back(str);}return temp;}void backtracking(int n,vector<vector<int>>& use_loc,int depth,vector<bool>& used_column){if(depth==n){vector<string> temp;temp=conver(use_loc,n);result.push_back(temp);return;}for(int i=0;i<n;i){//i控制列if(used_column[i]) continue;//跳过用过的列if(use_loc[depth][i]==1) continue;//跳过会被攻击的地方//下面每一层的对角线bool r=false;for(int j=depth-1,k=i1;j>=0 && k<n;j--,k){if(use_loc[j][k]==2) r=true;}if(r) continue;r=false;for(int j=depth-1,k=i-1;j>=0 && k>=0;j--,k--){if(use_loc[j][k]==2) r=true;}if(r) continue;use_loc[depth][i]=2;//皇后used_column[i]=true;depth;backtracking(n,use_loc,depth,used_column);//回溯depth--;use_loc[depth][i]=0;// for(int j=depth 1,k=i-1;j=0;j  ,k--){//     use_loc[j][k]=0;// }// for(int j=depth 1,k=i 1;j//     use_loc[j][k]=0;// }used_column[i]=false;}}vector<vector<string>> solvenqueens(int n) {vector<vector<int>> t(n,vector<int>(n,0));//全部初始化为0use_loc=t;vector<bool> used_column(n,false);//用过的列backtracking(n,use_loc,0,used_column);return result;}
};

37. 解数独

c
法一:回溯
踩坑记录:字符里面没有’10’,最高就是’9’

class solution {
public:bool isvalid(int row,int col,char val,vector<vector<char>>& 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<startrow3;i){for(int j=startcol;j<startcol3;j){if(board[i][j]==val)return false;}}return true;}bool backtracking(vector<vector<char>>& board){for(int i=0;i<board.size();i){for(int j=0;j<board[0].size();j){if(board[i][j]=='.') {for(char k='1';k<='9';k){//注意这里是<='9',不能写<'10',没有10这个字符if(isvalid(i,j,k,board)){board[i][j]=k;if(backtracking(board)) return true;board[i][j]='.';                            }}return false;//9个数都不对,返回false                    }}}return true;}void solvesudoku(vector<vector<char>>& board) {backtracking(board);}
};

455. 分发饼干

c
法一:小饼干先喂饱小胃口

class solution {
public:int findcontentchildren(vector<int>& g, vector<int>& s) {sort(g.begin(),g.end());sort(s.begin(),s.end());int sum=0;vector<bool> uset(s.size(),false);for(int i=0;i<g.size();i){if(s.size()>0&&s.back()<g[i]) break;//如果s的最大值都小于孩子的胃口值,那直接退出循环for(int j=0;j<s.size();j){if(uset[j]) continue;if(s[j]>=g[i]){sum;uset[j]=true;break;}}}return sum;}
};

376. 摆动序列

c
法一:贪心算法。对于连续增长或连续递减的节点们应该怎么删除?保持这两个区间的端点,删掉其他的节点就可,所以只需要记录峰值个数就可以,但是需要处理最左边和最右边的节点。
保持区间波动,只需要把单调区间上的元素移除就可以了。

class solution {
public:int wigglemaxlength(vector<int>& nums) {if(nums.size()<=1) return nums.size();int prediff=0;//把第一个节点的差值设为0int curdiff=0;//当前节点和前一个节点的差值int result=1;//包含了第一个节点for(int i=1;i<nums.size();i){curdiff=nums[i]-nums[i-1];if((curdiff>0&&prediff<=0)||(curdiff<0&&prediff>=0)){result;prediff=curdiff;}}return result;}
};

53. 最大子数组和

c
法一:暴力解法。设置两层循环,但是超过了时间限制

class solution {
public:int maxsubarray(vector<int>& nums) {int result = int32_min;int count = 0;for (int i = 0; i < nums.size(); i) { // 设置起始位置count = 0;for (int j = i; j < nums.size(); j) { // 每次从起始位置i开始遍历寻找最大值count  = nums[j];result = count > result ? count : result;}}return result;}
};

法二:贪心算法。当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。从而推出全局最优:选取最大“连续和”
其关键在于:不能让“连续和”为负数的时候加上下一个元素,而不是 不让“连续和”加上一个负数。

class solution {
public:int maxsubarray(vector<int>& nums) {int result=int_min;int cout=0;//区间连续和for(int i=0;i<nums.size();i){cout =nums[i];if(cout>result) result=cout;//取出最大连续和,不断调整终止位置if(cout<0) cout=0;//如果出现负数,连续和清零,相当于不断调整区间起始位置}return result;}
};

122. 买卖股票的最佳时机 ii

c
法一:贪心算法

class solution {
public:int maxprofit(vector<int>& prices) {int result=0;for(int i=0;i<prices.size();i){if(i>0&&prices[i]>prices[i-1]){//今天大于前一天就卖出result =(prices[i]-prices[i-1]);}}return result;}
};

55. 跳跃游戏

c
法一:贪心

class solution {
public:bool canjump(vector<int>& nums) {//能走到的位置处判断:当前位置元素值 当前位置下标值>=终点下标值int cover=0;//能走到的覆盖范围for(int i=0;i<=cover;i){cover=max(inums[i],cover);//及时更新覆盖范围if(nums[i]i1>=nums.size()){return true;}}return false;}
};

45. 跳跃游戏 ii

c
法一:贪心。在当前覆盖范围内的点:寻找下一个覆盖的更远的点

class solution {
public:int jump(vector<int>& nums) {//当前覆盖范围内的点:寻找下一个覆盖的更远的点int result=0;//跳跃次数int curcover=0;//当前覆盖范围int nextcover;//下一个点的覆盖范围int index;if(nums.size()==1) return 0;//特殊情况for(int i=0;i<nums.size();){curcover=nums[i];nextcover=0;result;//不管当前点的覆盖范围有没有包含终点,步数都需要加一,所以nums的大小至少为2if(curcoveri1>=nums.size()){//说明当前覆盖范围已经到达终点break;}//当前覆盖范围未到达终点for(int j=1;j<=curcover;j){//判断下一步的下标和覆盖范围if(nextcover<nums[ij]ij){nextcover=nums[ij]ij;index=ij;}}i=index;//下一步的下标}return result;}
};

1005. k 次取反后最大化的数组和

法一:贪心算法
写法一:使用for循环,但是这种写法容易漏掉nums.size()

class solution {
public:int largestsumafterknegations(vector<int>& nums, int k) {//负数个数>k:找出最小的k个负数变成正数//负数个数n//所以不管k-n为奇数还是偶数都可以选择最小的正数进行处理int sum=0;sort(nums.begin(),nums.end());int index=0;for(int i=0;i<nums.size()&&k>0;i){if(nums[i]<0){k--;nums[i]=-nums[i];}else{if(k%2==0) break;else{sort(nums.begin(),nums.end());nums[0]=-nums[0];k--;break;}}}//对nums.size()if(k>0&&k%2==1){sort(nums.begin(),nums.end());nums[0]=-nums[0];}for(int i=0;i<nums.size();i){sum =nums[i];}return sum;}
};

134. 加油站

法一:贪心
写法一:超出了时间限制

class solution {
public:int cancompletecircuit(vector<int>& gas, vector<int>& cost) {//从第 i 个加油站开往第 i 1 个加油站需要消耗汽油 cost[i] 升//第 i 1 个加油站有汽油 gas[i 1] 升//净含量=gas[i]-cost[i]>0的点才可以作为出发点vector<int> temp;vector<int> start_index;//出发点if(gas.size()==1){if(gas[0]-cost[0]>=0) return 0;else return -1;}//不能组成环路for(int i=0;i<gas.size();i){if(gas[i]-cost[i]>0){start_index.push_back(i);}temp.push_back(gas[i]-cost[i]);}if(start_index.size()==0) return -1;//没找到出发点int sum=0;int result;for(int i=0;i<start_index.size();i){//循环检查每个出发点sum=0;//检查是否能走完全程for(int j=start_index[i];j<temp.size()start_index[i];j){if(j<temp.size()) sum =temp[j];else sum =temp[j-temp.size()];if(sum<0) break;//小于零说明到不了下一个加油站}if(sum>=0) result=start_index[i];}return result;}
};

写法二:对写法一进行改进
利用cur来计算区间的净含量,如果一旦小于0,说明这个区间都不能作为出发点

class solution {
public:int cancompletecircuit(vector<int>& gas, vector<int>& cost) {//从第 i 个加油站开往第 i 1 个加油站需要消耗汽油 cost[i] 升//第 i 1 个加油站有汽油 gas[i 1] 升//净含量=gas[i]-cost[i]>0的点才可以作为出发点int cur=0;int res_total=0;int result=0;//这里必须初始化,如果cur一直大于0,那0号位置就是出发点for(int i=0;i<gas.size();i){cur =gas[i]-cost[i];res_total =(gas[i]-cost[i]);if(cur<0){result=i1;cur=0;}}if(res_total<0) return -1;return result;//净含量总量大于等于0,说明找到的出发点是能跑完全程的}
};

135. 分发糖果

法一:贪心算法

class solution {
public:int candy(vector<int>& ratings) {int result=0;vector<int> candynum(ratings.size(),1);//先给每个孩子都分配一个糖果if(ratings.size()==1) return 1;//从前往后判断,遇到更高评分的就加一个糖果for(int i=1;i<ratings.size();i){if(ratings[i]>ratings[i-1]){candynum[i]=candynum[i-1]1;}}//从后往前进行判断for(int i=ratings.size()-2;i>=0;i--){if(ratings[i]>ratings[i1]){if(candynum[i]<=candynum[i1])//判断糖果数是否满足要求candynum[i]=candynum[i1]1;}}//汇总for(int i=0;i<candynum.size();i){result =candynum[i];}return result;}
};

860.柠檬水找零

c
法一:贪心

class solution {
public:bool lemonadechange(vector<int>& bills) {unordered_map<int,int> money{{5,0},{10,0},{20,0}};//第一位是钱的金额,第二位是钱的数量for(int i=0;i<bills.size();i){if(bills[i]==5){money[5];}if(bills[i]==10){if(money[5]==0) return false;else{money[5]--;money[10];}}if(bills[i]==20){if(money[10]>0&&money[5]>0){//优先消耗一个10和一个5,如果不够,再消耗三个5money[5]--;money[10]--;   money[20];                }else if(money[10]==0&&money[5]>=3){money[5]=money[5]-3;money[20]; }else return false;}}return true;}
};

406.根据身高重建队列

法一:贪心
注意:类内sort自定义排序函数需定义为static否则报错。具体可见下面链接
类内sort自定义排序函数需定义为static否则报错
版本一:使用vector

class solution {
public:static bool cmp(vector<int>& a,vector<int>& b){if(a[0]==b[0]) return a[1]<b[1];//身高相同则k小的排前面else return a[0]>b[0];//从高到矮进行排序}vector<vector<int>> reconstructqueue(vector<vector<int>>& people) {//身高矮的插在身高高的前面不会有影响//所以先按照身高高矮进行排队,身高相同则k小的排前面sort(people.begin(),people.end(),cmp);vector<vector<int>> que;//按照k进行插入,k就相当于要插入的下标for(int i=0;i<people.size();i){int index=people[i][1];que.insert(que.begin()index,people[i]);}return que;}
};

版本二:使用list,耗时更短
注意:使用list时,不能直接用迭代器 几个的写法,因为链表里面的地址不是连续的

// 版本二
class solution {
public:// 身高从大到小排(身高相同k小的站前面)static bool cmp(const vector<int>& a, const vector<int>& b) {if (a[0] == b[0]) return a[1] < b[1];return a[0] > b[0];}vector<vector<int>> reconstructqueue(vector<vector<int>>& people) {sort (people.begin(), people.end(), cmp);list<vector<int>> que; // list底层是链表实现,插入效率比vector高的多for (int i = 0; i < people.size(); i) {int position = people[i][1]; // 插入到下标为position的位置std::list<vector<int>>::iterator it = que.begin();while (position--) { // 寻找在插入位置it;}que.insert(it, people[i]);}return vector<vector<int>>(que.begin(), que.end());}
};

452. 用最少数量的箭引爆气球

法一:贪心。先将气球直径按照开始的x坐标进行排序,再判断哪些气球具有交集,没有交集就更新新的开始直径范围,并且不断更新右边界

class solution {
public:static bool cmp(vector<int>& a,vector<int>& b){return a[0]<b[0];}int findminarrowshots(vector<vector<int>>& points) {//算交集sort(points.begin(),points.end(),cmp);int result=points.size();//假设所有气球都没有交集vector<int> start=points[0];for(int i=1;i<points.size();i){if(points[i][0]>start[1]){//没有交集start=points[i];}else{//有交集start[1]=start[1]>points[i][1]?points[i][1]:start[1];//更新直径共同范围的end,取二者最小值result--;};}return result;}
};

435. 无重叠区间

法一:贪心。先按照左边界从小到大进行排序

class solution {
public:static bool cmp(vector<int>& a,vector<int>& b){return a[0]<b[0];}int eraseoverlapintervals(vector<vector<int>>& intervals) {//后边界和前边界重叠不算重叠//选定start后将和start有重叠的区间全算作一整块,看一共有多少块就是最小数量//因为只要这些区间划分为一块了,那么这一块的区间只能留一个区间下来了sort(intervals.begin(),intervals.end(),cmp);int result=0;vector<int> start=intervals[0];for(int i=1;i<intervals.size();i){if(intervals[i][0]>=start[1]){//第i个区间至少和前面界定的重叠区间内的区间内的一个区间(具有最小右边界的区间)不重叠start=intervals[i];}else{//有重叠start[1]=min(start[1],intervals[i][1]);//更新最小右边界result;//移除区间的数量}}return result;}
};

763.划分字母区间

法一:和代码随想录一样
区间内的字符出现最远下标就是最终的右边界了,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点
注意:string里面的单个字符是用单引号表示

class solution {
public:vector<int> partitionlabels(string s) {vector<int> result;int farthest_pos[26]={0};//每个字母在s中出现的最远位置int right=0;int left=0;for(int i=0;i<s.size();i){farthest_pos[s[i]-'a']=i;}for(int i=0;i<s.size();i){right=max(right,farthest_pos[s[i]-'a']);if(right==i){result.push_back(right-left1);left=i1;}}return result;       }
};

56. 合并区间

法一:贪心

class solution {
public:static bool cmp(vector<int>& a,vector<int>& b){return a[0]<b[0];}vector<vector<int>> merge(vector<vector<int>>& intervals) {sort(intervals.begin(),intervals.end(),cmp);//按开始坐标从小到大进行排序vector<vector<int>> result;if(intervals.size()==0) return result;result.push_back(intervals[0]);for(int i=1;i<intervals.size();i){if(intervals[i][0]<=result[result.size()-1][1]){//有重叠//每次合并都取最大的右边界!而不是取新区间的右边界//排序只是按照左边界进行排序的,右边界不一定result[result.size()-1][1]=max(result[result.size()-1][1],intervals[i][1]);}else{//没有重叠result.push_back(intervals[i]);}}return result;}
};

738. 单调递增的数字

法一:暴力算法,超时

class solution {
public:bool isvalid(int n){vector<int> num;while(n){if(num.size()>0&&(n%10>num.back())) return false;num.push_back(n%10);//放入最后一位数字n=n/10;}return true;}int monotoneincreasingdigits(int n) {while(n){if(isvalid(n)) return n;n--;}return n;}
};

法二:贪心算法

class solution {
public:int monotoneincreasingdigits(int n) {string strnum = to_string(n);int index;//开始赋'9'的下标起始位置for(int i=strnum.size()-1;i>0;i--){if(strnum[i]<strnum[i-1]){index=i;strnum[i-1]--;}}for(int i=index;i<strnum.size();i){strnum[i]='9';}return stoi(strnum);}
};

714. 买卖股票的最佳时机含手续费

法一:贪心。这道题不是很好理解,需要多思考思考

class solution {
public:int maxprofit(vector<int>& prices, int fee) {int result = 0;int minprice = prices[0]; // 记录最低价格for (int i = 1; i < prices.size(); i) {//prices[i]   fee相当于是股票的成本价格// 情况二:相当于买入//经过情况一的minprice = prices[i] - fee操作后,这里的判断条件相当于是prices[i] fee<之前的prices,相当于此时卖出会亏,买也很便宜,所以适合买入。算上手续费已经不盈利了,所以适合在此时的点买入if (prices[i] < minprice) minprice = prices[i];//此时相当于下一轮买入了// 情况三:保持原有状态(因为此时买则不便宜,卖则亏本)if (prices[i] >= minprice && prices[i] <= minprice  fee) {continue;}// 计算利润,可能有多次计算利润,最后一次计算利润才是真正意义的卖出//必须要大于买入价格 手续才能有正利润if (prices[i] > minprice  fee) {//因为后面minprice= prices[i] - fee,所以这里需要加上fee进行对比,相当于和原始prices进行对比result  = prices[i] - minprice - fee;minprice = prices[i] - fee; // 情况一,这一步很关键。避免多减去手续费}}return result;}
};

968.监控二叉树

法一:贪心。大体思路就是从低到上,先给叶子节点的父节点放个摄像头,然后隔两个节点放一个摄像头,直至到二叉树头结点。

class solution {
public:
int result=0;int tranversel(treenode* cur){//0:无覆盖//1:摄像头//2:有覆盖if(cur==null) return 2;//空节点设为有覆盖的状态,因为叶子节点不放摄像头int left=tranversel(cur->left);int right=tranversel(cur->right);if(left==2&&right==2) return 0;if(left==0||right==0){//左右节点至少有一个未覆盖的情况result;return 1;//安装摄像头}if(left==1||right==1) return 2;return -1;}int mincameracover(treenode* root) {if(tranversel(root)==0) result;return result;}
};

509. 斐波那契数

法一:使用递归

class solution {
public:int add_recur(int n){if(n==0) return 0;if(n==1) return 1;return add_recur(n-1)add_recur(n-2);}int fib(int n) {return add_recur(n);}
};

法二:动态规划

class solution {
public:int fib(int n) {if(n<=1) return n;//注意必须加上这个vector<int> dp(n1);dp[0]=0;dp[1]=1;for(int i=2;i<n1;i){dp[i]=dp[i-1]dp[i-2];}return dp[n];}
};

70. 爬楼梯

法一:动态规划

class solution {
public:int climbstairs(int n) {if (n <= 1) return n; // 因为下面直接对dp[2]操作了,防止空指针vector<int> dp(n  1);dp[1] = 1;dp[2] = 2;for (int i = 3; i <= n; i) { // 注意i是从3开始的dp[i] = dp[i - 1]  dp[i - 2];}return dp[n];}
};

法二:动态规划完全背包

class solution {
public:int climbstairs(int n) {vector<int> dp(n1,0);dp[0]=1;for(int j=1;j<=n;j){for(int i=1;i<3;i){if(j>=i) dp[j] =dp[j-i];}}return dp.back();}
};

746. 使用最小花费爬楼梯

法一:动态规划

class solution {
public:int mincostclimbingstairs(vector<int>& cost) {vector<int> dp(cost.size()1);dp[0]=0;dp[1]=0;for(int i=2;i<=cost.size();i){dp[i]=min(dp[i-1]cost[i-1],dp[i-2]cost[i-2]);}return dp.back();}
};

62.不同路径

法一:动态规划

class solution {
public:int uniquepaths(int m, int n) {//当前位置路径总和=左边方块的路径数 上面方块的路径数//第1行和第1列只有左边或者上面的路径数// if(m==1||n==1) return 1;//下面的初始化已经考虑到这种情况了int dp[m][n];dp[0][0]=0;for(int i=0;i<m;i) dp[i][0]=1;for(int i=0;i<n;i) dp[0][i]=1;for(int i=1;i<m;i){for(int j=1;j<n;j){dp[i][j]=dp[i-1][j]dp[i][j-1];}}return dp[m-1][n-1];}
};

63. 不同路径 ii

法一:动态规划
有障碍的话,其实就是标记对应的dp table(dp数组)保持初始值(0)就可以了。

class solution {
public:int uniquepathswithobstacles(vector<vector<int>>& obstaclegrid) {int m=obstaclegrid.size();//行数int n=obstaclegrid[0].size();//列数vector<vector<int>> dp(m,vector<int>(n,0));for(int i=0;i<m;i){if(obstaclegrid[i][0]==1) break;//遇到了障碍物,退出循环dp[i][0]=1;}for(int i=0;i<n;i){if(obstaclegrid[0][i]==1) break;dp[0][i]=1;}for(int i=1;i<m;i){for(int j=1;j<n;j){if(obstaclegrid[i][j]==1) continue;//遇到障碍物,继续下一个位置dp[i][j]=dp[i-1][j]dp[i][j-1];}}return dp[m-1][n-1];}
};

343. 整数拆分

法一:动态规划。
dp[i]:分拆数字i,可以得到的最大乘积为dp[i]。

class solution {
public:int integerbreak(int n) {//dp[i]:分拆数字i,可以得到的最大乘积为dp[i]。vector<int> dp(n1,1);for(int i=3;i<=n;i){for(int j=1;j<=i/2;j){dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));//记得和dp[i]进行对比,因为这是循环,前面也找过拆分i的最大值,所以需要全局对比}}return dp[n];}
};

96.不同的二叉搜索树

法一:动态规划
实际上就是求不同搜索树的数量,不用把具体的节点值都列出来

class solution {
public:int numtrees(int n) {//i个不同元素节点组成的二叉搜索树的个数为dp[i]vector<int> dp(n1);dp[0]=1;for(int i=1;i<=n;i){for(int j=1;j<=i;j){//j-1相当于是左子树节点树,i-1-(j-1)=右子树节点//左子树和右子树分别有多少个节点dp[i] =dp[j-1]*dp[i-j];//i为根节点,还剩下i-1个节点}}return dp.back();}
};

0-1背包理论基础

一维数组:每次循环在用下一个物品时使用的一维数组的各个值,就相当于二维数组时上一层的一行值,这里只是换成了一维数组,在遍历不同物品时不停地覆盖更改值

void test_1_wei_bag_problem() {vector<int> weight = {1, 3, 4};vector<int> value = {15, 20, 30};int bagweight = 4;// 初始化vector<int> dp(bagweight  1, 0);for(int i = 0; i < weight.size(); i) { // 遍历物品for(int j = bagweight; j >= weight[i]; j--) { // 遍历背包容量dp[j] = max(dp[j], dp[j - weight[i]]  value[i]);}}cout << dp[bagweight] << endl;
}int main() {test_1_wei_bag_problem();
}

416. 分割等和子集

法一:动态规划一维数组,target为数组总和的一半,只要能找出子集和等于target的就符合要求

class solution {
public:bool canpartition(vector<int>& nums) {int sum=0;vector<int> dp(10001,0);for(int i=0;i<nums.size();i){sum =nums[i];}if (sum%2==1) return false;int target=sum/2;for(int i=0;i<nums.size();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. 最后一块石头的重量 ii

法一:动态规划。
本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。

class solution {
public:int laststoneweightii(vector<int>& stones) {vector<int> dp(1501,0);int target=0;int sum=0;for(int i=0;i<stones.size();i){sum =stones[i];}target=sum/2;for(int i=0;i<stones.size();i){for(int j=target;j>=stones[i];j--){dp[j]=max(dp[j],dp[j-stones[i]]stones[i]);}} return sum-dp[target]-dp[target];       }
};

494. 目标和

法一:动态规划。
dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法
dp[j] = dp[j – nums[i]]
dp[j]=不需要num[i]就能够凑出j的情况(nums[i-1]对应的dp[j]) 需要num[i]凑出j空间的情况
第一个循环在遍历物品,前面已经遍历过的物品都可以放入背包中,在不同的背包容量下,方法各是多少种

class solution {
public:int findtargetsumways(vector<int>& nums, int target) {int sum=0;for(int i=0;i<nums.size();i) sum =nums[i];if(abs(target)>sum) return 0;if((targetsum)%2==1) return 0;int bagsize=(targetsum)/2;vector<int> dp(bagsize1,0);dp[0]=1;for(int i=0;i<nums.size();i){for(int j=bagsize;j>=nums[i];j--){dp[j] =dp[j-nums[i]];}}return dp.back();}
};

474.一和零

法一:动态规划
实际上还是01背包,只是物品的重量有两个维度

class solution {
public:int findmaxform(vector<string>& strs, int m, int n) {vector<vector<int>> dp(m1,vector<int>(n1,0));for(string str:strs){//遍历物品int zeronum=0;int onenum=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. 零钱兑换 ii

法一:动态规划完全背包
dp[0]=1还说明了一种情况:如果正好选了coins[i]后,也就是j-coins[i] == 0的情况表示这个硬币刚好能选,此时dp[0]为1表示只选coins[i]存在这样的一种选法。
注意:
外层for循环遍历物品(钱币),内层for遍历背包(金钱总额):得到组合数
外层for循环遍历背包(金钱总额),内层循环遍历物品(钱币):得到排列数

class solution {
public:int change(int amount, vector<int>& coins) {vector<int> dp(amount1,0);//dp[i]代表可以凑成背包容量为i的总组合数dp[0]=1;//后面需要累加,所以初始化为1for(int i=0;i<coins.size();i){//遍历物品for(int j=coins[i];j<=amount;j){dp[j] =dp[j-coins[i]];}}return dp.back();}
};

如果求组合数(没有顺序)就是外层for循环遍历物品,内层for遍历背包。

如果求排列数就是外层for遍历背包,内层for循环遍历物品。

可以这样想:先遍历背包容量再遍历各个物品时,每个物品只要可以放进背包都会被循环一遍,当背包容量增加时,上一次循环过的物品还可以再次循环,这样就有相同的一些数字形成不同的组合,比如当j=3时,1和2在j=2和j=3时都可以装进背包,所以就会出现{1,2}和{2,1}。

377. 组合总和 ⅳ

法一:动态规划完全背包
踩坑记录:题目数据保证答案符合 32 位整数范围,但是dp数组中间的某些值可能会超过int_max,既然答案不会超过,那就说明答案不会用到这些位置的数。c 测试用例有两个数相加超过int的数据,所以需要在if里加上dp[i] < int_max – dp[i – num]。

class solution {
public:int combinationsum4(vector<int>& nums, int target) {vector<long long int> dp(target1,0);dp[0]=1;for(int j=0;j<=target;j){for(int i=0;i<nums.size();i){if(j>=nums[i]&& dp[j]dp[j-nums[i]]<int_max) dp[j] =dp[j-nums[i]];}}return dp.back();}
};

322. 零钱兑换

法一:完全背包
踩坑记录:必须加int_max的判断,不然 1就会溢出

class solution {
public:int coinchange(vector<int>& coins, int amount) {vector<int> dp(amount1,int_max);dp[0]=0;for(int i=0;i<coins.size();i){for(int j=coins[i];j<=amount;j){//完全背包正序遍历if(dp[j-coins[i]]!=int_max)dp[j]=min(dp[j],dp[j-coins[i]]1);}}if(dp.back()==int_max) return -1;else return dp.back();}
};

279.完全平方数

法一:完全背包

class solution {
public:int numsquares(int n) {vector<int> nums;//完全平方数数组for(int i=1;i<=100;i){nums.push_back(i*i);}vector<int> dp(n1,int_max);dp[0]=0;for(int i=0;i<nums.size();i){for(int j=nums[i];j<=n;j){if(dp[j-nums[i]]!=int_max)dp[j]=min(dp[j],dp[j-nums[i]]1);}}return dp.back();}
};

写法二:物品重量就是i*i,不需要创建一个完全数数组

class solution {
public:int numsquares(int n) {vector<int> dp(n  1, int_max);dp[0] = 0;for (int i = 1; i * i <= n; i) { // 遍历物品for (int j = i * i; j <= n; j) { // 遍历背包dp[j] = min(dp[j - i * i]  1, dp[j]);}}return dp[n];}
};

139. 单词拆分

法一:完全背包

class solution {
public:bool wordbreak(string s, vector<string>& worddict) {//完全背包、排列问题//dp[i] : 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词vector<bool> dp(s.size()1,false);dp[0]=true;for(int j=1;j<=s.size();j){//先遍历背包for(int i=0;i<j;i){  //遍历物品            string str=s.substr(i,j-i);//看单词是否出现在字典里if(find(worddict.begin(),worddict.end(),str)!=worddict.end()&&dp[i])//vector没有find内置函数dp[j]=true;}}return dp.back();}
};

198. 打家劫舍

法一:动态规划

class solution {
public:int rob(vector<int>& nums) {if(nums.size()==1) return nums[0];vector<int> dp(nums.size(),0);dp[0]=nums[0];dp[1]=max(nums[0],nums[1]);for(int i=2;i<nums.size();i){dp[i]=max(dp[i-2]nums[i],dp[i-1]);}return dp.back();}
};

213. 打家劫舍 ii

法一:动态规划
首尾元素只能存在一个,所以可以分开考虑去求解两种情况的打劫最大值

class solution {
public:int rob(vector<int>& nums) {//不考虑尾元素if(nums.size()==1) return nums[0];if(nums.size()==2) return max(nums[0],nums[1]);vector<int> dp1(nums.size()-1,0);dp1[0]=nums[0];dp1[1]=max(nums[0],nums[1]);for(int i=2;i<nums.size()-1;i){dp1[i]=max(dp1[i-2]nums[i],dp1[i-1]);}//不考虑首元素vector<int> dp2(nums.size()-1,0);dp2[0]=nums[1];dp2[1]=max(nums[1],nums[2]);for(int i=2;i<nums.size()-1;i){dp2[i]=max(dp2[i-2]nums[i1],dp2[i-1]);//注意这种dp的定义下这里的nums是错位的}return max(dp1.back(),dp2.back());}
};

337. 打家劫舍 iii

法一:动态规划 递归
刚开始想的是用层序遍历,每一层相当于一个屋子,前后相邻屋子不能一起打劫,但是这种想法是有问题的,一层的节点不一定全部要打劫

class solution {
public:int rob(treenode* root) {//层次遍历,相邻屋子不能一起打劫vector<int> result=robtree(root);return max(result[0],result[1]);}//返回长度为2的vector数组:0为不偷,1为偷vector<int> robtree(treenode* cur){if(cur==null) return {0,0};vector<int> left=robtree(cur->left);vector<int> right=robtree(cur->right);int val1=cur->valleft[0]right[0];//当前节点偷int val2=max(left[0],left[1])max(right[0],right[1]);//当前节点不偷return {val2,val1};}
};

121. 买卖股票的最佳时机

法一:动态规划
用二维数组来表示每一天的股票持有或者不持有所得最高现金
dp[i][0]:持有股票,相当于记录了1-i天中的股票最低价
dp[i][1]:不持有股票,相当于记录了1-i天中的最高利润

class solution {
public:int maxprofit(vector<int>& prices) {int len=prices.size();vector<vector<int>> dp(len,vector<int>(2));//有len个二维数组dp[0][0]=-prices[0];//持有股票dp[0][1]=0;//不持有股票for(int i=1;i<len;i){dp[i][0]=max(dp[i-1][0],-prices[i]);dp[i][1]=max(prices[i]dp[i-1][0],dp[i-1][1]);//不持有股票,相当于卖出}return dp[len-1][1];}
};

122.买卖股票的最佳时机ii

法一:动态规划
dp[i – 1][1] – prices[i]:表示买入股票,所得现金就是昨天不持有股票的所得现金减去 今天的股票价格。只有昨天不持有股票,今天才可以买入

class solution {
public:int maxprofit(vector<int>& prices) {int len=prices.size();vector<vector<int>> dp(len,vector<int>(2));//有len个二维数组dp[0][0]=-prices[0];//持有股票dp[0][1]=0;//不持有股票for(int i=1;i<len;i){dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i]);//持有股票dp[i][1]=max(prices[i]dp[i-1][0],dp[i-1][1]);//不持有股票,相当于卖出}return dp[len-1][1];}
};

123.买卖股票的最佳时机iii

法一:动态规划

class solution {
public:int maxprofit(vector<int>& prices) {// 1-第一次持有股票// 2-第一次不持有股票// 3-第二次持有股票// 4-第二次不持有股票int len=prices.size();if(len==0) return 0;vector<vector<int>> dp(len,vector<int>(5,0));dp[0][1]=-prices[0];dp[0][3]=-prices[0];for(int i=1;i<len;i){dp[i][1]=max(dp[i-1][1],-prices[i]);//前期已经买入或者在第i天买入dp[i][2]=max(dp[i-1][2],dp[i-1][1]prices[i]);//前期已经卖出或者在第i天卖出dp[i][3]=max(dp[i-1][3],dp[i-1][2]-prices[i]);//前期已经买入或者在第i天买入第二股,那么上一个状态一定是第一次不持有股票dp[i][4]=max(dp[i-1][4],dp[i-1][3]prices[i]);//前期已经卖出或者在第i天卖出}return dp[len-1][4];}
};

188.买卖股票的最佳时机iv

法一:动态规划

class solution {
public:int maxprofit(int k, vector<int>& prices) {//1第一次持股,2第一次不持股,3第二次持股,4第二次不持股,i(奇数)第(i 1)/2次持股int len=prices.size();vector<vector<int>> dp(len,vector<int>(2*k1,0));for(int i=1;i<2*k1;i){if(i%2==1){dp[0][i]=-prices[0];//初始化,在第0天就持股的最大金额}}for(int i=1;i<len;i){for(int j=1;j<2*k1;j){if(j%2==1){//持股dp[i][j]=max(dp[i-1][j-1]-prices[i],dp[i-1][j]);  }else{//不持股dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]prices[i]);}}}return dp[len-1][2*k];}
};

309.最佳买卖股票时机含冷冻期

法一:动态规划
踩坑记录:max只能比较两个数,所以三个数的比较需要使用max嵌套

class solution {
public:int maxprofit(vector<int>& prices) {int n = prices.size();if (n == 0) return 0;vector<vector<int>> dp(n, vector<int>(4, 0));dp[0][0] -= prices[0]; // 持股票for (int i = 1; i < n; i) {dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][3], dp[i - 1][1]) - prices[i]);dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);dp[i][2] = dp[i - 1][0]  prices[i];dp[i][3] = dp[i - 1][2];}return max(dp[n - 1][3],max(dp[n - 1][1], dp[n - 1][2]));}
};

714.买卖股票的最佳时机含手续费

法一:动态规划

class solution {
public:int maxprofit(vector<int>& prices, int fee) {//0持有股票,1不持有int len = prices.size();if(len==0) return 0;vector<vector<int>> dp(len,vector<int>(2,0));dp[0][0]=-prices[0];for(int i=1;i<len;i){dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i]);dp[i][1]=max(dp[i-1][1],dp[i-1][0]prices[i]-fee);//减掉手续费}return dp[len-1][1];}
};

300.最长递增子序列

法一:动态规划
dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度
位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 1 的最大值。

所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] 1);

只要i比之前的某个数大,就可以组成递增序列,再去判断和哪个数组成的递增序列的长度大

class solution {
public:int lengthoflis(vector<int>& nums) {if(nums.size()<=1) return nums.size();vector<int> dp(nums.size(),1);int result=0;for(int i=1;i<nums.size();i){for(int j=0;j<i;j){if(nums[i]>nums[j]){dp[i]=max(dp[i],dp[j]1);}}if(dp[i]>result) result=dp[i];}return result;}
};

674. 最长连续递增序列

法一:动态规划

class solution {
public:int findlengthoflcis(vector<int>& nums) {vector<int> dp(nums.size(),1);dp[0]=1;int result=1;for(int i=1;i<nums.size();i){if(nums[i]>nums[i-1]){dp[i]=dp[i-1]1;}if(result<dp[i]) result=dp[i];}return result;}
};

718. 最长重复子数组

法一:动态规划
dp[i][j]:以i结尾的nums1和以j结尾的nums2的最长公共子数组长度
这里的子数组是连续的

class solution {
public:int findlength(vector<int>& nums1, vector<int>& nums2) {// dp[i][j]:以i结尾的nums1和以j结尾的nums2的最长公共子数组长度vector<vector<int>> dp(nums1.size(),vector<int>(nums2.size(),0));int result=0;// 要对第一行,第一列经行初始化// 初始化里面如果出现了相等的数,那result记得初始化为1for (int i = 0; i < nums1.size(); i){if (nums1[i] == nums2[0]){dp[i][0] = 1;result=1;}}for (int j = 0; j < nums2.size(); j) {if (nums1[0] == nums2[j]){dp[0][j] = 1;result=1;}}for(int i=1;i<nums1.size();i){for(int j=1;j<nums2.size();j){if(nums1[i]==nums2[j]){dp[i][j]=dp[i-1][j-1]1;}if(dp[i][j]>result) result=dp[i][j];}}return result;}
};

1143.最长公共子序列

法一:动态规划
和上一题区别在于这里不一定连续

class solution {
public:int longestcommonsubsequence(string text1, string text2) {
//dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]vector<vector<int>> dp(text1.size()1,vector<int>(text2.size()1,0));int result=0;for(int i=1;i<=text1.size();i){for(int j=1;j<=text2.size();j){if(text1[i-1]==text2[j-1]){dp[i][j]=dp[i-1][j-1]1;}else{//不相等的话字符串倒退一个取最大值dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}if(dp[i][j]>result) result=dp[i][j];}}return result;}
};

1035.不相交的线

法一:动态规划
和1143是一个道理,其实就是求最长公共子序列

class solution {
public:int maxuncrossedlines(vector<int>& nums1, vector<int>& nums2) {
//dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]vector<vector<int>> dp(nums1.size()1,vector<int>(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;}else{//不相等的话字符串倒退一个取最大值dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}}}return dp[nums1.size()][nums2.size()];}
};

53. 最大子数组和

法一:动态规划
dp[i]:包括下标i(以nums[i]为结尾)的最大连续子序列和为dp[i]。
所以最大子数组不一定是以最后一个元素为结尾,可能在中间出现

class solution {
public:int maxsubarray(vector<int>& nums) {// dp[i]:包括下标i(以nums[i]为结尾)的最大连续子序列和为dp[i]。vector<int> dp(nums.size(),0);dp[0]=nums[0];int result=dp[0];for(int i=1;i<nums.size();i){dp[i]=max(dp[i-1]nums[i],nums[i]);if(dp[i]>result) result=dp[i];}return result;}
};

392. 判断子序列

法一:动规
实际就是求求最长公共子序列长度
将1143的代码搬过来再做长度判断就可以ac了
dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]。

class solution {
public:bool issubsequence(string s, string t) {if(s.size()>t.size()) return false;//可以转换成求最长公共子序列长度// dp[i][j]:长度为[0, i-1]的字符串s与长度为[0, j-1]的字符串t的最长公共子序列为dp[i][j]vector<vector<int>> dp(s.size()1,vector<int>(t.size()1,0));int result=0;for(int i=1;i<=s.size();i){for(int j=1;j<=t.size();j){if(s[i-1]==t[j-1]){dp[i][j]=dp[i-1][j-1]1;}else{dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}if(dp[i][j]>result) result=dp[i][j];}}if(result==s.size()) return true;else return false;}
};

对以上代码进行优化:实际上在s[i-1]!=t[j-1]时只需要对t进行操作即可。
dp[i][j] = dp[i][j – 1]相当于删除了t[j-1],这里的删除可以理解成跳过了t[j-1]

class solution {
public:bool issubsequence(string s, string t) {if(s.size()>t.size()) return false;//可以转换成求最长公共子序列长度// dp[i][j]:长度为[0, i-1]的字符串s与长度为[0, j-1]的字符串t的最长公共子序列为dp[i][j]vector<vector<int>> dp(s.size()1,vector<int>(t.size()1,0));int result=0;for(int i=1;i<=s.size();i){for(int j=1;j<=t.size();j){if(s[i-1]==t[j-1]){dp[i][j]=dp[i-1][j-1]1;}else{dp[i][j]=dp[i][j-1];//这里只对t进行删除}if(dp[i][j]>result) result=dp[i][j];}}if(result==s.size()) return true;else return false;}
};

115. 不同的子序列

法一:动态规划
只在s中进行删除操作
需要理解 dp[i][j]=dp[i-1][j] 表示不用s[i-1]去匹配
注意位数那里还是需要加判断,不然会溢出,跟之前做过的一道题类似
[c ]-uint8_t,uint16_t,uint32_t,uint64_t代表含义及其标准定义
int_max和int_min的定义及使用(含溢出问题)

class solution {
public:int numdistinct(string s, string t) {if(s.size()<t.size()) return 0;// dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。vector<vector<long long>> dp(s.size()  1, vector<long long>(t.size()  1));//初始化for(int i=0;i<=s.size();i) dp[i][0]=1;for(int i=1;i<=t.size();i) dp[0][i]=0;for(int i=1;i<=s.size();i){for(int j=1;j<=t.size();j){if(s[i-1]==t[j-1]&&dp[i-1][j-1]dp[i-1][j]<int_max){dp[i][j]=dp[i-1][j-1]dp[i-1][j];}else dp[i][j]=dp[i-1][j];//不用s[i-1]去匹配}}return dp[s.size()][t.size()];}
};

583. 两个字符串的删除操作

法一:动规
dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。
dp[i][j]=min(dp[i-1][j] 1,dp[i][j-1] 1):看下面的图,比如sea和ea达到相等,删除一个元素(dp[i][j-1]);se和eat达到相等需要删除3个元素,那么当循环到最右下角时,a和t不相等,那么sea和ea达到相等,再删掉不相等的t就可以达到相等,同理删掉a也是这样,再取最小值就行

class solution {
public:int mindistance(string word1, string word2) {vector<vector<int>> dp(word1.size()  1, vector<int>(word2.size()  1));for(int i=0;i<=word1.size();i) dp[i][0]=i;for(int i=0;i<=word2.size();i) dp[0][i]=i;for(int i=1;i<=word1.size();i){for(int j=1;j<=word2.size();j){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);//删除word1[i - 1]或word2[j - 1]}}}return dp[word1.size()][word2.size()];}
};

72. 编辑距离

法一:动规
添加元素可以理解为删除另一个元素,删除元素就是dp[i-1][j]或者dp[i][j-1],替换就是dp[i – 1][j – 1] 1

class solution {
public:int mindistance(string word1, string word2) {vector<vector<int>> dp(word1.size()  1, vector<int>(word2.size()  1));for(int i=0;i<=word1.size();i) dp[i][0]=i;for(int i=0;i<=word2.size();i) dp[0][i]=i;for(int i=1;i<=word1.size();i){for(int j=1;j<=word2.size();j){if(word1[i-1]==word2[j-1]){dp[i][j]=dp[i-1][j-1];//不做任何删除操作}else{dp[i][j]=min(dp[i-1][j],min(dp[i][j-1],dp[i-1][j-1]))1;}}}return dp[word1.size()][word2.size()];}
};

编辑距离总结篇

添加元素可以理解为删除另一个元素,删除元素就是dp[i-1][j]或者dp[i][j-1],删除某个元素可以理解成跳过该元素,继续下一个元素

替换就是dp[i – 1][j – 1] 1

647. 回文子串

法一:动规

class solution {
public:int countsubstrings(string s) {// dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));int result=0;for(int i=s.size()-1;i>=0;i--){for(int j=i;j<s.size();j){if(s[i]==s[j]){if(j-i<=1){//一个或两个长度大小的字符串dp[i][j]=true;result;}else{if(dp[i1][j-1]){//这里的if可以和else合并dp[i][j]=true; result;}}}}}return result;}
};

516.最长回文子序列

法一:动规
和647题的区别在于这题不一定是连续的

class solution {
public:int longestpalindromesubseq(string s) {// dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度为dp[i][j]。vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));for(int i=0;i<s.size();i) dp[i][i]=1;for(int i=s.size()-1;i>=0;i--){for(int j=i1;j<s.size();j){if(s[i]==s[j]){dp[i][j]=dp[i1][j-1]2;}else dp[i][j]=max(dp[i1][j],dp[i][j-1]);}    }return dp[0][s.size()-1];}
};

以上就是小编对于leetcode(力扣)刷题笔记(c )【中】问题和相关问题的解答了,leetcode(力扣)刷题笔记(c )【中】的问题希望对你有用!

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文链接:https://www.andon8.com/399826.html

网站地图