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,i+1,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-min+1+depth<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,i+1,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,i+1,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-min+1+temp.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,i+1,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,i+1);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,i+1);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_index+1);//start_index位置開始的子串temp.push_back(str);}else continue;//不是回文串就跳過backtracking(s,i+1);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-index+1);//[index,i]之間的字符串if(isValid(s,index,i)){//字符串符合要求path+=str; depth++;if(depth<4) path+="."; backtracking(s,i+1,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,i+1);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,i+1);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,i+1);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,i+1);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,i+1);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()==(1+tickets.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=depth+1,k=i-1;j<n && k>=0;j++,k--){use_loc[j][k]=1;}for(int j=depth+1,k=i+1;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=depth+1,k=i-1;j<n && k>=0;j++,k--){use_loc[j][k]=0;}for(int j=depth+1,k=i+1;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=depth+1,k=i-1;j<n && k>=0;j++,k--){use_loc[j][k]++;}for(int j=depth+1,k=i+1;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=depth+1,k=i-1;j<n && k>=0;j++,k--){use_loc[j][k]--;}for(int j=depth+1,k=i+1;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=i+1;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<n && k>=0;j++,k--){// use_loc[j][k]=0;// }// for(int j=depth+1,k=i+1;j<n && k<n;j++,k++){// 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<startRow+3;i++){for(int j=startCol;j<startCol+3;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(i+nums[i],cover);//及時更新覆蓋范圍if(nums[i]+i+1>=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(curCover+i+1>=nums.size()){//說明當前覆蓋范圍已經到達終點break;}//當前覆蓋范圍未到達終點for(int j=1;j<=curCover;j++){//判斷下一步的下標和覆蓋范圍if(nextCover<nums[i+j]+i+j){nextCover=nums[i+j]+i+j;index=i+j;}}i=index;//下一步的下標}return result;}
};
1005. K 次取反后最大化的數組和
法一:貪心算法
寫法一:使用for循環,但是這種寫法容易漏掉nums.size()<k且全是負數的情況做處理的情況
class Solution {
public:int largestSumAfterKNegations(vector<int>& nums, int k) {//負數個數>k:找出最小的k個負數變成正數//負數個數n<k:找出最小的n個負數變成正數,k-n為偶數不用操作,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()<k且全是負數的情況做處理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=i+1;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[i+1]){if(candyNum[i]<=candyNum[i+1])//判斷糖果數是否滿足要求candyNum[i]=candyNum[i+1]+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-left+1);left=i+1;}}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(n+1);dp[0]=0;dp[1]=1;for(int i=2;i<n+1;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(n+1,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(n+1,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(n+1);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((target+sum)%2==1) return 0;int bagSize=(target+sum)/2;vector<int> dp(bagSize+1,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(m+1,vector<int>(n+1,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(amount+1,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(target+1,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(amount+1,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(n+1,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[i+1],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->val+left[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*k+1,0));for(int i=1;i<2*k+1;i++){if(i%2==1){dp[0][i]=-prices[0];//初始化,在第0天就持股的最大金額}}for(int i=1;i<len;i++){for(int j=1;j<2*k+1;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[i+1][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=i+1;j<s.size();j++){if(s[i]==s[j]){dp[i][j]=dp[i+1][j-1]+2;}else dp[i][j]=max(dp[i+1][j],dp[i][j-1]);} }return dp[0][s.size()-1];}
};
總結
以上是生活随笔為你收集整理的leetcode(力扣)刷题笔记(c++)【中】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为DHCP故障常用排查命令
- 下一篇: 中兴通讯扬帆国际化难掩主场失意:内外销市