leetcode(力扣)刷题笔记(c++)【中】
文章預(yù)覽:
- 回溯算法
-
- 77. 組合
- 216.組合總和III
- 17.電話號(hào)碼的字母組合
- 39. 組合總和
- 40.組合總和II
- 131.分割回文串
- 93.復(fù)原IP地址
- 78. 子集
- 90.子集II
- 491.遞增子序列
- 46.全排列
- 47. 全排列 II
- 332.重新安排行程
- 第51題. N皇后
- 37. 解數(shù)獨(dú)
- 貪心算法
-
- 455. 分發(fā)餅干
- 376. 擺動(dòng)序列
- 53. 最大子數(shù)組和
- 122. 買賣股票的最佳時(shí)機(jī) II
- 55. 跳躍游戲
- 45. 跳躍游戲 II
- 1005. K 次取反后最大化的數(shù)組和
- 134. 加油站
- 135. 分發(fā)糖果
- 860.檸檬水找零
- 406.根據(jù)身高重建隊(duì)列
- 452. 用最少數(shù)量的箭引爆氣球
- 435. 無重疊區(qū)間
- 763.劃分字母區(qū)間
- 56. 合并區(qū)間
- 738. 單調(diào)遞增的數(shù)字
- 714. 買賣股票的最佳時(shí)機(jī)含手續(xù)費(fèi)
- 968.監(jiān)控二叉樹
- 動(dòng)態(tài)規(guī)劃
-
- 509. 斐波那契數(shù)
- 70. 爬樓梯
- 746. 使用最小花費(fèi)爬樓梯
- 62.不同路徑
- 63. 不同路徑 II
- 343. 整數(shù)拆分
- 96.不同的二叉搜索樹
- 0-1背包理論基礎(chǔ)
- 416. 分割等和子集
- 1049. 最后一塊石頭的重量 II
- 494. 目標(biāo)和
- 474.一和零
- 518. 零錢兌換 II
- 377. 組合總和 Ⅳ
- 322. 零錢兌換
- 279.完全平方數(shù)
- 139. 單詞拆分
- 198. 打家劫舍
- 213. 打家劫舍 II
- 337. 打家劫舍 III
- 121. 買賣股票的最佳時(shí)機(jī)
- 122.買賣股票的最佳時(shí)機(jī)II
- 123.買賣股票的最佳時(shí)機(jī)III
- 188.買賣股票的最佳時(shí)機(jī)IV
- 309.最佳買賣股票時(shí)機(jī)含冷凍期
- 714.買賣股票的最佳時(shí)機(jī)含手續(xù)費(fèi)
- 300.最長遞增子序列
- 674. 最長連續(xù)遞增序列
- 718. 最長重復(fù)子數(shù)組
- 1143.最長公共子序列
- 1035.不相交的線
- 53. 最大子數(shù)組和
- 392. 判斷子序列
- 115. 不同的子序列
- 583. 兩個(gè)字符串的刪除操作
- 72. 編輯距離
- 編輯距離總結(jié)篇
- 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++;//當(dāng)前為第幾層,樹的深度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;}
};
剪枝優(yōu)化
這種寫法和代碼隨想錄的寫法不一樣,但是思路一致
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;//剪枝優(yōu)化,如果可以訪問到的元素不夠k個(gè)數(shù)則返回,不進(jìn)行循環(huán)if(depth==k){result.push_back(temp);return;}depth++;//當(dāng)前為第幾層,樹的深度,相當(dāng)于已經(jīng)選擇的元素個(gè)數(shù)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個(gè)數(shù)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;}
};
剪枝優(yōu)化
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;//剪枝優(yōu)化,個(gè)數(shù)不足或者超過了目標(biāo)值均返回if(temp.size()==k){//且為k個(gè)數(shù)if(s==n) result.push_back(temp);//相加之和為nreturn;//訪問了k個(gè)數(shù)就返回}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.電話號(hào)碼的字母組合
c++
法一:回溯
注意需要按照給出電話按鍵的順序進(jìn)行字母的組合
如果在現(xiàn)場面試的時(shí)候,一定要注意各種輸入異常的情況,例如本題輸入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個(gè)值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++){//注意每次開始的下標(biāo)不是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;}
};
剪枝優(yōu)化:如果已經(jīng)知道下一層的sum會(huì)大于target,就沒有必要進(jìn)入下一層遞歸了。
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++){//條件判斷里加了剪枝優(yōu)化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++
法一:回溯,在插入路徑時(shí)利用find函數(shù)進(jìn)行去重,但是超時(shí)了,代碼如下
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;}
};
優(yōu)化:如果同一樹層中遇到了重復(fù)元素就跳過,因?yàn)榍懊娴闹貜?fù)元素和后面元素的組合一定會(huì)重復(fù)。注意if條件中i>index必須寫在&&的前面,不然會(huì)報(bào)錯(cuò)。
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;//去重步驟,同一層的節(jié)點(diǎn)值不能重復(fù)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()的大小依次進(jìn)行截取
class Solution {
public:vector<vector<string>> result;vector<string> temp;bool isPalindrome(const string &s,int start,int end){//這里必須要加const,因?yàn)榛厮莺瘮?shù)中加了//雙指針判斷是否為回文串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()){//分割結(jié)束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.復(fù)原IP地址
c++
法一:回溯
先分割三次,就會(huì)形成四段字符串,循環(huán)里每次都會(huì)對前三段字符串是否有效進(jìn)行判斷,結(jié)束條件里面會(huì)對第四段進(jìn)行判斷,但是都不能忘記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){//分割三次后就進(jìn)行處理判斷剩下的字符串是否符合要求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();//這樣寫每次只會(huì)彈出一個(gè)字符,但是str不止一個(gè)字符,所以需要加循環(huán)處理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){//每種分割的結(jié)果都放進(jìn)去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++
法一:回溯
注意:去重必須要先對數(shù)組進(jìn)行排序!
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題類似的去重寫法,但是這樣做只能去除前后出現(xiàn)的重復(fù)數(shù)字,如果后面又出現(xiàn)了重復(fù)數(shù)字就去重不了,比如數(shù)組[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]){//因?yàn)椴⒉皇怯行驍?shù)組,所以這樣做只能去除前后出現(xiàn)的重復(fù)數(shù)字,如果后面又出現(xiàn)了重復(fù)數(shù)字就去重不了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;}
};
改進(jìn)后:需要直到同一層中哪些數(shù)字用過了,一個(gè)for循環(huán)就代表橫向樹的一層,所以在for循環(huán)上面添加一個(gè)unordered_set容器(該容器底層為哈希表,且不會(huì)自動(dòng)排序,這道題并不需要對元素進(jìn)行排序),unordered_set中不允許有重復(fù)的元素。同一層的元素會(huì)出現(xiàn)在路徑容器的同一個(gè)位置,所以不能出現(xiàn)重復(fù)值,回溯代碼最后不需要對unordered_set刪除nums[i]這個(gè)元素,因?yàn)閡nordered_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;//這里也可以用數(shù)組,耗時(shí)更少for(int i=index;i<nums.size();i++){if(use_num.find(nums[i])!=use_num.end()){continue;}use_num.insert(nums[i]);//注意后面不需要再刪除這個(gè)元素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里面已經(jīng)用過的元素,如果用過了就不能再用了,且每次循環(huán)都是從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]);//注意后面不需要再刪除這個(gè)元素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函數(shù)來進(jìn)行去重
需要注意的是:使用set去重的版本相對于數(shù)組的版本效率都要低很多
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;//對同一層進(jìn)行去重效率更高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];//記錄路徑元素是否用過,默認(rèn)初始化為0sort(nums.begin(),nums.end());backtracking(nums,use_num);return result;}
};
332.重新安排行程
c++
法一:回溯
踩坑記錄:先找出第一個(gè)機(jī)票再做回溯的做法是不對的,因?yàn)檎页龅淖钚∨判虿灰欢馨阉械臋C(jī)票用完
正確做法: 利用unordered_map<出發(fā)機(jī)場, map<到達(dá)機(jī)場, 航班次數(shù)>> targets來儲(chǔ)存所有的行程中相同出發(fā)地的行程,通過判斷航班次數(shù)來判斷該行程是否已經(jīng)用過。map中所有元素都是pair
class Solution {
public:vector<string> result;// unordered_map<出發(fā)機(jī)場, map<到達(dá)機(jī)場, 航班次數(shù)>> 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的最后一個(gè)地點(diǎn),也就是下一個(gè)行程的出發(fā)地點(diǎn)if(temp.second>0){//說明還沒用過result.push_back(temp.first);//只需要放入到達(dá)機(jī)場temp.second--;if(backtracking(tickets)) return true;//result.pop_back();temp.second++;}}return false;//如果沒找到行程就會(huì)返回false}vector<string> findItinerary(vector<vector<string>>& tickets) {for(vector<string> &temp:tickets){targets[temp[0]][temp[1]]++;//targets[temp[0]]相當(dāng)于是key=temp[0]對應(yīng)的map容器,targets[temp[0]][temp[1]]相當(dāng)于是對map容器中key=temp[1]進(jìn)行賦值}result.push_back("JFK");//初始機(jī)場backtracking(tickets);return result;}
};
第51題. N皇后
c++
法一:回溯
思路:通過used_column來記錄用過的列,通過use_loc來記錄皇后位置和會(huì)被攻擊的地方,只需要將下面每一層的對角線的位置標(biāo)記就可以了,但是n=6時(shí)報(bào)錯(cuò)了,出現(xiàn)皇后放在了對角線的位置。
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;//跳過會(huì)被攻擊的地方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;}
};
后來找出了錯(cuò)誤,因?yàn)橛行┗屎蟮墓舻奈恢脮?huì)有交叉,在回溯的時(shí)候?qū)⒁恍┕粑恢弥?后是不對的,該位置有可能是上層皇后的攻擊位置,所以不能簡單的置0和置1,應(yīng)該每個(gè)位置的數(shù)值是被攻擊的次數(shù),這樣回溯時(shí)才不會(huì)出錯(cuò)
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;//跳過會(huì)被攻擊的地方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;}
};
法二:改成下面這種判斷上面的對角線中是否有皇后就會(huì)通過
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;//跳過會(huì)被攻擊的地方//下面每一層的對角線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. 解數(shù)獨(dú)
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這個(gè)字符if(isValid(i,j,k,board)){board[i][j]=k;if(backtracking(board)) return true;board[i][j]='.'; }}return false;//9個(gè)數(shù)都不對,返回false }}}return true;}void solveSudoku(vector<vector<char>>& board) {backtracking(board);}
};
貪心算法
455. 分發(fā)餅干
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的最大值都小于孩子的胃口值,那直接退出循環(huán)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. 擺動(dòng)序列
c++
法一:貪心算法。對于連續(xù)增長或連續(xù)遞減的節(jié)點(diǎn)們應(yīng)該怎么刪除?保持這兩個(gè)區(qū)間的端點(diǎn),刪掉其他的節(jié)點(diǎn)就可,所以只需要記錄峰值個(gè)數(shù)就可以,但是需要處理最左邊和最右邊的節(jié)點(diǎn)。
保持區(qū)間波動(dòng),只需要把單調(diào)區(qū)間上的元素移除就可以了。
class Solution {
public:int wiggleMaxLength(vector<int>& nums) {if(nums.size()<=1) return nums.size();int preDiff=0;//把第一個(gè)節(jié)點(diǎn)的差值設(shè)為0int curDiff=0;//當(dāng)前節(jié)點(diǎn)和前一個(gè)節(jié)點(diǎn)的差值int result=1;//包含了第一個(gè)節(jié)點(diǎn)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. 最大子數(shù)組和
c++
法一:暴力解法。設(shè)置兩層循環(huán),但是超過了時(shí)間限制
class Solution {
public:int maxSubArray(vector<int>& nums) {int result = INT32_MIN;int count = 0;for (int i = 0; i < nums.size(); i++) { // 設(shè)置起始位置count = 0;for (int j = i; j < nums.size(); j++) { // 每次從起始位置i開始遍歷尋找最大值count += nums[j];result = count > result ? count : result;}}return result;}
};
法二:貪心算法。當(dāng)前“連續(xù)和”為負(fù)數(shù)的時(shí)候立刻放棄,從下一個(gè)元素重新計(jì)算“連續(xù)和”,因?yàn)樨?fù)數(shù)加上下一個(gè)元素 “連續(xù)和”只會(huì)越來越小。從而推出全局最優(yōu):選取最大“連續(xù)和”
其關(guān)鍵在于:不能讓“連續(xù)和”為負(fù)數(shù)的時(shí)候加上下一個(gè)元素,而不是 不讓“連續(xù)和”加上一個(gè)負(fù)數(shù)。
class Solution {
public:int maxSubArray(vector<int>& nums) {int result=INT_MIN;int cout=0;//區(qū)間連續(xù)和for(int i=0;i<nums.size();i++){cout+=nums[i];if(cout>result) result=cout;//取出最大連續(xù)和,不斷調(diào)整終止位置if(cout<0) cout=0;//如果出現(xiàn)負(fù)數(shù),連續(xù)和清零,相當(dāng)于不斷調(diào)整區(qū)間起始位置}return result;}
};
122. 買賣股票的最佳時(shí)機(jī) 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) {//能走到的位置處判斷:當(dāng)前位置元素值+當(dāng)前位置下標(biāo)值>=終點(diǎn)下標(biāo)值int cover=0;//能走到的覆蓋范圍for(int i=0;i<=cover;i++){cover=max(i+nums[i],cover);//及時(shí)更新覆蓋范圍if(nums[i]+i+1>=nums.size()){return true;}}return false;}
};
45. 跳躍游戲 II
c++
法一:貪心。在當(dāng)前覆蓋范圍內(nèi)的點(diǎn):尋找下一個(gè)覆蓋的更遠(yuǎn)的點(diǎn)
class Solution {
public:int jump(vector<int>& nums) {//當(dāng)前覆蓋范圍內(nèi)的點(diǎn):尋找下一個(gè)覆蓋的更遠(yuǎn)的點(diǎn)int result=0;//跳躍次數(shù)int curCover=0;//當(dāng)前覆蓋范圍int nextCover;//下一個(gè)點(diǎn)的覆蓋范圍int index;if(nums.size()==1) return 0;//特殊情況for(int i=0;i<nums.size();){curCover=nums[i];nextCover=0;result++;//不管當(dāng)前點(diǎn)的覆蓋范圍有沒有包含終點(diǎn),步數(shù)都需要加一,所以nums的大小至少為2if(curCover+i+1>=nums.size()){//說明當(dāng)前覆蓋范圍已經(jīng)到達(dá)終點(diǎn)break;}//當(dāng)前覆蓋范圍未到達(dá)終點(diǎn)for(int j=1;j<=curCover;j++){//判斷下一步的下標(biāo)和覆蓋范圍if(nextCover<nums[i+j]+i+j){nextCover=nums[i+j]+i+j;index=i+j;}}i=index;//下一步的下標(biāo)}return result;}
};
1005. K 次取反后最大化的數(shù)組和
法一:貪心算法
寫法一:使用for循環(huán),但是這種寫法容易漏掉nums.size()<k且全是負(fù)數(shù)的情況做處理的情況
class Solution {
public:int largestSumAfterKNegations(vector<int>& nums, int k) {//負(fù)數(shù)個(gè)數(shù)>k:找出最小的k個(gè)負(fù)數(shù)變成正數(shù)//負(fù)數(shù)個(gè)數(shù)n<k:找出最小的n個(gè)負(fù)數(shù)變成正數(shù),k-n為偶數(shù)不用操作,k-n為奇數(shù)時(shí)則選最小的正數(shù)進(jìn)行處理//所以不管k-n為奇數(shù)還是偶數(shù)都可以選擇最小的正數(shù)進(jì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且全是負(fù)數(shù)的情況做處理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. 加油站
法一:貪心
寫法一:超出了時(shí)間限制
class Solution {
public:int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {//從第 i 個(gè)加油站開往第 i+1 個(gè)加油站需要消耗汽油 cost[i] 升//第 i+1 個(gè)加油站有汽油 gas[i+1] 升//凈含量=gas[i]-cost[i]>0的點(diǎn)才可以作為出發(fā)點(diǎn)vector<int> temp;vector<int> start_index;//出發(fā)點(diǎn)if(gas.size()==1){if(gas[0]-cost[0]>=0) return 0;else return -1;}//不能組成環(huán)路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;//沒找到出發(fā)點(diǎn)int sum=0;int result;for(int i=0;i<start_index.size();i++){//循環(huán)檢查每個(gè)出發(fā)點(diǎn)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;//小于零說明到不了下一個(gè)加油站}if(sum>=0) result=start_index[i];}return result;}
};
寫法二:對寫法一進(jìn)行改進(jìn)
利用cur來計(jì)算區(qū)間的凈含量,如果一旦小于0,說明這個(gè)區(qū)間都不能作為出發(fā)點(diǎn)
class Solution {
public:int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {//從第 i 個(gè)加油站開往第 i+1 個(gè)加油站需要消耗汽油 cost[i] 升//第 i+1 個(gè)加油站有汽油 gas[i+1] 升//凈含量=gas[i]-cost[i]>0的點(diǎn)才可以作為出發(fā)點(diǎn)int cur=0;int res_total=0;int result=0;//這里必須初始化,如果cur一直大于0,那0號(hào)位置就是出發(fā)點(diǎn)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,說明找到的出發(fā)點(diǎn)是能跑完全程的}
};
135. 分發(fā)糖果
法一:貪心算法
class Solution {
public:int candy(vector<int>& ratings) {int result=0;vector<int> candyNum(ratings.size(),1);//先給每個(gè)孩子都分配一個(gè)糖果if(ratings.size()==1) return 1;//從前往后判斷,遇到更高評分的就加一個(gè)糖果for(int i=1;i<ratings.size();i++){if(ratings[i]>ratings[i-1]){candyNum[i]=candyNum[i-1]+1;}}//從后往前進(jìn)行判斷for(int i=ratings.size()-2;i>=0;i--){if(ratings[i]>ratings[i+1]){if(candyNum[i]<=candyNum[i+1])//判斷糖果數(shù)是否滿足要求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}};//第一位是錢的金額,第二位是錢的數(shù)量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){//優(yōu)先消耗一個(gè)10和一個(gè)5,如果不夠,再消耗三個(gè)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.根據(jù)身高重建隊(duì)列
法一:貪心
注意:類內(nèi)sort自定義排序函數(shù)需定義為static否則報(bào)錯(cuò)。具體可見下面鏈接
類內(nèi)sort自定義排序函數(shù)需定義為static否則報(bào)錯(cuò)
版本一:使用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];//從高到矮進(jìn)行排序}vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {//身高矮的插在身高高的前面不會(huì)有影響//所以先按照身高高矮進(jìn)行排隊(duì),身高相同則k小的排前面sort(people.begin(),people.end(),cmp);vector<vector<int>> que;//按照k進(jìn)行插入,k就相當(dāng)于要插入的下標(biāo)for(int i=0;i<people.size();i++){int index=people[i][1];que.insert(que.begin()+index,people[i]);}return que;}
};
版本二:使用list,耗時(shí)更短
注意:使用list時(shí),不能直接用迭代器+幾個(gè)的寫法,因?yàn)殒湵砝锩娴牡刂凡皇沁B續(xù)的
// 版本二
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底層是鏈表實(shí)現(xiàn),插入效率比vector高的多for (int i = 0; i < people.size(); i++) {int position = people[i][1]; // 插入到下標(biāo)為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. 用最少數(shù)量的箭引爆氣球
法一:貪心。先將氣球直徑按照開始的x坐標(biāo)進(jìn)行排序,再判斷哪些氣球具有交集,沒有交集就更新新的開始直徑范圍,并且不斷更新右邊界
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();//假設(shè)所有氣球都沒有交集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. 無重疊區(qū)間
法一:貪心。先按照左邊界從小到大進(jìn)行排序
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有重疊的區(qū)間全算作一整塊,看一共有多少塊就是最小數(shù)量//因?yàn)橹灰@些區(qū)間劃分為一塊了,那么這一塊的區(qū)間只能留一個(gè)區(qū)間下來了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個(gè)區(qū)間至少和前面界定的重疊區(qū)間內(nèi)的區(qū)間內(nèi)的一個(gè)區(qū)間(具有最小右邊界的區(qū)間)不重疊start=intervals[i];}else{//有重疊start[1]=min(start[1],intervals[i][1]);//更新最小右邊界result++;//移除區(qū)間的數(shù)量}}return result;}
};
763.劃分字母區(qū)間
法一:和代碼隨想錄一樣
區(qū)間內(nèi)的字符出現(xiàn)最遠(yuǎn)下標(biāo)就是最終的右邊界了,如果找到字符最遠(yuǎn)出現(xiàn)位置下標(biāo)和當(dāng)前下標(biāo)相等了,則找到了分割點(diǎn)
注意:string里面的單個(gè)字符是用單引號(hào)表示
class Solution {
public:vector<int> partitionLabels(string s) {vector<int> result;int farthest_pos[26]={0};//每個(gè)字母在s中出現(xiàn)的最遠(yuǎn)位置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. 合并區(qū)間
法一:貪心
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);//按開始坐標(biāo)從小到大進(jìn)行排序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]){//有重疊//每次合并都取最大的右邊界!而不是取新區(qū)間的右邊界//排序只是按照左邊界進(jìn)行排序的,右邊界不一定result[result.size()-1][1]=max(result[result.size()-1][1],intervals[i][1]);}else{//沒有重疊result.push_back(intervals[i]);}}return result;}
};
738. 單調(diào)遞增的數(shù)字
法一:暴力算法,超時(shí)
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);//放入最后一位數(shù)字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'的下標(biāo)起始位置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. 買賣股票的最佳時(shí)機(jī)含手續(xù)費(fèi)
法一:貪心。這道題不是很好理解,需要多思考思考
class Solution {
public:int maxProfit(vector<int>& prices, int fee) {int result = 0;int minPrice = prices[0]; // 記錄最低價(jià)格for (int i = 1; i < prices.size(); i++) {//prices[i] + fee相當(dāng)于是股票的成本價(jià)格// 情況二:相當(dāng)于買入//經(jīng)過情況一的minPrice = prices[i] - fee操作后,這里的判斷條件相當(dāng)于是prices[i]+fee<之前的prices,相當(dāng)于此時(shí)賣出會(huì)虧,買也很便宜,所以適合買入。算上手續(xù)費(fèi)已經(jīng)不盈利了,所以適合在此時(shí)的點(diǎn)買入if (prices[i] < minPrice) minPrice = prices[i];//此時(shí)相當(dāng)于下一輪買入了// 情況三:保持原有狀態(tài)(因?yàn)榇藭r(shí)買則不便宜,賣則虧本)if (prices[i] >= minPrice && prices[i] <= minPrice + fee) {continue;}// 計(jì)算利潤,可能有多次計(jì)算利潤,最后一次計(jì)算利潤才是真正意義的賣出//必須要大于買入價(jià)格+手續(xù)才能有正利潤if (prices[i] > minPrice + fee) {//因?yàn)楹竺鎚inPrice= prices[i] - fee,所以這里需要加上fee進(jìn)行對比,相當(dāng)于和原始prices進(jìn)行對比result += prices[i] - minPrice - fee;minPrice = prices[i] - fee; // 情況一,這一步很關(guān)鍵。避免多減去手續(xù)費(fèi)}}return result;}
};
968.監(jiān)控二叉樹
法一:貪心。大體思路就是從低到上,先給葉子節(jié)點(diǎn)的父節(jié)點(diǎn)放個(gè)攝像頭,然后隔兩個(gè)節(jié)點(diǎn)放一個(gè)攝像頭,直至到二叉樹頭結(jié)點(diǎn)。
class Solution {
public:
int result=0;int tranversel(TreeNode* cur){//0:無覆蓋//1:攝像頭//2:有覆蓋if(cur==NULL) return 2;//空節(jié)點(diǎn)設(shè)為有覆蓋的狀態(tài),因?yàn)槿~子節(jié)點(diǎn)不放攝像頭int left=tranversel(cur->left);int right=tranversel(cur->right);if(left==2&&right==2) return 0;if(left==0||right==0){//左右節(jié)點(diǎn)至少有一個(gè)未覆蓋的情況result++;return 1;//安裝攝像頭}if(left==1||right==1) return 2;return -1;}int minCameraCover(TreeNode* root) {if(tranversel(root)==0) result++;return result;}
};
動(dòng)態(tài)規(guī)劃
509. 斐波那契數(shù)
法一:使用遞歸
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);}
};
法二:動(dòng)態(tài)規(guī)劃
class Solution {
public:int fib(int n) {if(n<=1) return n;//注意必須加上這個(gè)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. 爬樓梯
法一:動(dòng)態(tài)規(guī)劃
class Solution {
public:int climbStairs(int n) {if (n <= 1) return n; // 因?yàn)橄旅嬷苯訉p[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];}
};
法二:動(dòng)態(tài)規(guī)劃完全背包
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. 使用最小花費(fèi)爬樓梯
法一:動(dòng)態(tài)規(guī)劃
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.不同路徑
法一:動(dòng)態(tài)規(guī)劃
class Solution {
public:int uniquePaths(int m, int n) {//當(dāng)前位置路徑總和=左邊方塊的路徑數(shù)+上面方塊的路徑數(shù)//第1行和第1列只有左邊或者上面的路徑數(shù)// if(m==1||n==1) return 1;//下面的初始化已經(jīng)考慮到這種情況了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
法一:動(dòng)態(tài)規(guī)劃
有障礙的話,其實(shí)就是標(biāo)記對應(yīng)的dp table(dp數(shù)組)保持初始值(0)就可以了。
class Solution {
public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int m=obstacleGrid.size();//行數(shù)int n=obstacleGrid[0].size();//列數(shù)vector<vector<int>> dp(m,vector<int>(n,0));for(int i=0;i<m;i++){if(obstacleGrid[i][0]==1) break;//遇到了障礙物,退出循環(huán)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;//遇到障礙物,繼續(xù)下一個(gè)位置dp[i][j]=dp[i-1][j]+dp[i][j-1];}}return dp[m-1][n-1];}
};
343. 整數(shù)拆分
法一:動(dòng)態(tài)規(guī)劃。
dp[i]:分拆數(shù)字i,可以得到的最大乘積為dp[i]。
class Solution {
public:int integerBreak(int n) {//dp[i]:分拆數(shù)字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]進(jìn)行對比,因?yàn)檫@是循環(huán),前面也找過拆分i的最大值,所以需要全局對比}}return dp[n];}
};
96.不同的二叉搜索樹
法一:動(dòng)態(tài)規(guī)劃
實(shí)際上就是求不同搜索樹的數(shù)量,不用把具體的節(jié)點(diǎn)值都列出來
class Solution {
public:int numTrees(int n) {//i個(gè)不同元素節(jié)點(diǎn)組成的二叉搜索樹的個(gè)數(shù)為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相當(dāng)于是左子樹節(jié)點(diǎn)樹,i-1-(j-1)=右子樹節(jié)點(diǎn)//左子樹和右子樹分別有多少個(gè)節(jié)點(diǎn)dp[i]+=dp[j-1]*dp[i-j];//i為根節(jié)點(diǎn),還剩下i-1個(gè)節(jié)點(diǎn)}}return dp.back();}
};
0-1背包理論基礎(chǔ)
一維數(shù)組:每次循環(huán)在用下一個(gè)物品時(shí)使用的一維數(shù)組的各個(gè)值,就相當(dāng)于二維數(shù)組時(shí)上一層的一行值,這里只是換成了一維數(shù)組,在遍歷不同物品時(shí)不停地覆蓋更改值
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. 分割等和子集
法一:動(dòng)態(tài)規(guī)劃一維數(shù)組,target為數(shù)組總和的一半,只要能找出子集和等于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
法一:動(dòng)態(tài)規(guī)劃。
本題其實(shí)就是盡量讓石頭分成重量相同的兩堆,相撞之后剩下的石頭最小,這樣就化解成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. 目標(biāo)和
法一:動(dòng)態(tài)規(guī)劃。
dp[j] 表示:填滿j(包括j)這么大容積的包,有dp[j]種方法
dp[j] += dp[j - nums[i]]
dp[j]=不需要num[i]就能夠湊出j的情況(nums[i-1]對應(yīng)的dp[j])+需要num[i]湊出j空間的情況
第一個(gè)循環(huán)在遍歷物品,前面已經(jīng)遍歷過的物品都可以放入背包中,在不同的背包容量下,方法各是多少種
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.一和零
法一:動(dòng)態(tài)規(guī)劃
實(shí)際上還是01背包,只是物品的重量有兩個(gè)維度
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++;}//遍歷背包,兩個(gè)維度先后順序無所謂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
法一:動(dòng)態(tài)規(guī)劃完全背包
dp[0]=1還說明了一種情況:如果正好選了coins[i]后,也就是j-coins[i] == 0的情況表示這個(gè)硬幣剛好能選,此時(shí)dp[0]為1表示只選coins[i]存在這樣的一種選法。
注意:
外層for循環(huán)遍歷物品(錢幣),內(nèi)層for遍歷背包(金錢總額):得到組合數(shù)
外層for循環(huán)遍歷背包(金錢總額),內(nèi)層循環(huán)遍歷物品(錢幣):得到排列數(shù)
class Solution {
public:int change(int amount, vector<int>& coins) {vector<int> dp(amount+1,0);//dp[i]代表可以湊成背包容量為i的總組合數(shù)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();}
};
如果求組合數(shù)(沒有順序)就是外層for循環(huán)遍歷物品,內(nèi)層for遍歷背包。
如果求排列數(shù)就是外層for遍歷背包,內(nèi)層for循環(huán)遍歷物品。
可以這樣想:先遍歷背包容量再遍歷各個(gè)物品時(shí),每個(gè)物品只要可以放進(jìn)背包都會(huì)被循環(huán)一遍,當(dāng)背包容量增加時(shí),上一次循環(huán)過的物品還可以再次循環(huán),這樣就有相同的一些數(shù)字形成不同的組合,比如當(dāng)j=3時(shí),1和2在j=2和j=3時(shí)都可以裝進(jìn)背包,所以就會(huì)出現(xiàn){1,2}和{2,1}。
377. 組合總和 Ⅳ
法一:動(dòng)態(tài)規(guī)劃完全背包
踩坑記錄:題目數(shù)據(jù)保證答案符合 32 位整數(shù)范圍,但是dp數(shù)組中間的某些值可能會(huì)超過INT_MAX,既然答案不會(huì)超過,那就說明答案不會(huì)用到這些位置的數(shù)。C++測試用例有兩個(gè)數(shù)相加超過int的數(shù)據(jù),所以需要在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就會(huì)溢出
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.完全平方數(shù)
法一:完全背包
class Solution {
public:int numSquares(int n) {vector<int> nums;//完全平方數(shù)數(shù)組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,不需要?jiǎng)?chuàng)建一個(gè)完全數(shù)數(shù)組
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,表示可以拆分為一個(gè)或多個(gè)在字典中出現(xiàn)的單詞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);//看單詞是否出現(xiàn)在字典里if(find(wordDict.begin(),wordDict.end(),str)!=wordDict.end()&&dp[i])//vector沒有find內(nèi)置函數(shù)dp[j]=true;}}return dp.back();}
};
198. 打家劫舍
法一:動(dòng)態(tài)規(guī)劃
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
法一:動(dòng)態(tài)規(guī)劃
首尾元素只能存在一個(gè),所以可以分開考慮去求解兩種情況的打劫最大值
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是錯(cuò)位的}return max(dp1.back(),dp2.back());}
};
337. 打家劫舍 III
法一:動(dòng)態(tài)規(guī)劃+遞歸
剛開始想的是用層序遍歷,每一層相當(dāng)于一個(gè)屋子,前后相鄰屋子不能一起打劫,但是這種想法是有問題的,一層的節(jié)點(diǎn)不一定全部要打劫
class Solution {
public:int rob(TreeNode* root) {//層次遍歷,相鄰屋子不能一起打劫vector<int> result=robTree(root);return max(result[0],result[1]);}//返回長度為2的vector數(shù)組: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];//當(dāng)前節(jié)點(diǎn)偷int val2=max(left[0],left[1])+max(right[0],right[1]);//當(dāng)前節(jié)點(diǎn)不偷return {val2,val1};}
};
121. 買賣股票的最佳時(shí)機(jī)
法一:動(dòng)態(tài)規(guī)劃
用二維數(shù)組來表示每一天的股票持有或者不持有所得最高現(xiàn)金
dp[i][0]:持有股票,相當(dāng)于記錄了1-i天中的股票最低價(jià)
dp[i][1]:不持有股票,相當(dāng)于記錄了1-i天中的最高利潤
class Solution {
public:int maxProfit(vector<int>& prices) {int len=prices.size();vector<vector<int>> dp(len,vector<int>(2));//有l(wèi)en個(gè)二維數(shù)組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]);//不持有股票,相當(dāng)于賣出}return dp[len-1][1];}
};
122.買賣股票的最佳時(shí)機(jī)II
法一:動(dòng)態(tài)規(guī)劃
dp[i - 1][1] - prices[i]:表示買入股票,所得現(xiàn)金就是昨天不持有股票的所得現(xiàn)金減去 今天的股票價(jià)格。只有昨天不持有股票,今天才可以買入
class Solution {
public:int maxProfit(vector<int>& prices) {int len=prices.size();vector<vector<int>> dp(len,vector<int>(2));//有l(wèi)en個(gè)二維數(shù)組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]);//不持有股票,相當(dāng)于賣出}return dp[len-1][1];}
};
123.買賣股票的最佳時(shí)機(jī)III
法一:動(dòng)態(tài)規(guī)劃
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]);//前期已經(jīng)買入或者在第i天買入dp[i][2]=max(dp[i-1][2],dp[i-1][1]+prices[i]);//前期已經(jīng)賣出或者在第i天賣出dp[i][3]=max(dp[i-1][3],dp[i-1][2]-prices[i]);//前期已經(jīng)買入或者在第i天買入第二股,那么上一個(gè)狀態(tài)一定是第一次不持有股票dp[i][4]=max(dp[i-1][4],dp[i-1][3]+prices[i]);//前期已經(jīng)賣出或者在第i天賣出}return dp[len-1][4];}
};
188.買賣股票的最佳時(shí)機(jī)IV
法一:動(dòng)態(tài)規(guī)劃
class Solution {
public:int maxProfit(int k, vector<int>& prices) {//1第一次持股,2第一次不持股,3第二次持股,4第二次不持股,i(奇數(shù))第(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.最佳買賣股票時(shí)機(jī)含冷凍期
法一:動(dòng)態(tài)規(guī)劃
踩坑記錄:max只能比較兩個(gè)數(shù),所以三個(gè)數(shù)的比較需要使用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.買賣股票的最佳時(shí)機(jī)含手續(xù)費(fèi)
法一:動(dòng)態(tài)規(guī)劃
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);//減掉手續(xù)費(fèi)}return dp[len-1][1];}
};
300.最長遞增子序列
法一:動(dòng)態(tài)規(guī)劃
dp[i]表示i之前包括i的以nums[i]結(jié)尾的最長遞增子序列的長度
位置i的最長升序子序列等于j從0到i-1各個(gè)位置的最長升序子序列 + 1 的最大值。
所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
只要i比之前的某個(gè)數(shù)大,就可以組成遞增序列,再去判斷和哪個(gè)數(shù)組成的遞增序列的長度大
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. 最長連續(xù)遞增序列
法一:動(dòng)態(tài)規(guī)劃
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. 最長重復(fù)子數(shù)組
法一:動(dòng)態(tài)規(guī)劃
dp[i][j]:以i結(jié)尾的nums1和以j結(jié)尾的nums2的最長公共子數(shù)組長度
這里的子數(shù)組是連續(xù)的
class Solution {
public:int findLength(vector<int>& nums1, vector<int>& nums2) {// dp[i][j]:以i結(jié)尾的nums1和以j結(jié)尾的nums2的最長公共子數(shù)組長度vector<vector<int>> dp(nums1.size(),vector<int>(nums2.size(),0));int result=0;// 要對第一行,第一列經(jīng)行初始化// 初始化里面如果出現(xiàn)了相等的數(shù),那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.最長公共子序列
法一:動(dòng)態(tài)規(guī)劃
和上一題區(qū)別在于這里不一定連續(xù)
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{//不相等的話字符串倒退一個(gè)取最大值dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}if(dp[i][j]>result) result=dp[i][j];}}return result;}
};
1035.不相交的線
法一:動(dòng)態(tài)規(guī)劃
和1143是一個(gè)道理,其實(shí)就是求最長公共子序列
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{//不相等的話字符串倒退一個(gè)取最大值dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}}}return dp[nums1.size()][nums2.size()];}
};
53. 最大子數(shù)組和
法一:動(dòng)態(tài)規(guī)劃
dp[i]:包括下標(biāo)i(以nums[i]為結(jié)尾)的最大連續(xù)子序列和為dp[i]。
所以最大子數(shù)組不一定是以最后一個(gè)元素為結(jié)尾,可能在中間出現(xiàn)
class Solution {
public:int maxSubArray(vector<int>& nums) {// dp[i]:包括下標(biāo)i(以nums[i]為結(jié)尾)的最大連續(xù)子序列和為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. 判斷子序列
法一:動(dòng)規(guī)
實(shí)際就是求求最長公共子序列長度
將1143的代碼搬過來再做長度判斷就可以AC了
dp[i][j] 表示以下標(biāo)i-1為結(jié)尾的字符串s,和以下標(biāo)j-1為結(jié)尾的字符串t,相同子序列的長度為dp[i][j]。
class Solution {
public:bool isSubsequence(string s, string t) {if(s.size()>t.size()) return false;//可以轉(zhuǎn)換成求最長公共子序列長度// 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;}
};
對以上代碼進(jìn)行優(yōu)化:實(shí)際上在s[i-1]!=t[j-1]時(shí)只需要對t進(jìn)行操作即可。
dp[i][j] = dp[i][j - 1]相當(dāng)于刪除了t[j-1],這里的刪除可以理解成跳過了t[j-1]
class Solution {
public:bool isSubsequence(string s, string t) {if(s.size()>t.size()) return false;//可以轉(zhuǎn)換成求最長公共子序列長度// 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進(jìn)行刪除}if(dp[i][j]>result) result=dp[i][j];}}if(result==s.size()) return true;else return false;}
};
115. 不同的子序列
法一:動(dòng)態(tài)規(guī)劃
只在s中進(jìn)行刪除操作
需要理解 dp[i][j]=dp[i-1][j] 表示不用s[i-1]去匹配
注意位數(shù)那里還是需要加判斷,不然會(huì)溢出,跟之前做過的一道題類似
[c++]-uint8_t,uint16_t,uint32_t,uint64_t代表含義及其標(biāo)準(zhǔn)定義
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為結(jié)尾的s子序列中出現(xiàn)以j-1為結(jié)尾的t的個(gè)數(shù)為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. 兩個(gè)字符串的刪除操作
法一:動(dòng)規(guī)
dp[i][j]:以i-1為結(jié)尾的字符串word1,和以j-1位結(jié)尾的字符串word2,想要達(dá)到相等,所需要?jiǎng)h除元素的最少次數(shù)。
dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1):看下面的圖,比如sea和ea達(dá)到相等,刪除一個(gè)元素(dp[i][j-1]);se和eat達(dá)到相等需要?jiǎng)h除3個(gè)元素,那么當(dāng)循環(huán)到最右下角時(shí),a和t不相等,那么sea和ea達(dá)到相等,再刪掉不相等的t就可以達(dá)到相等,同理刪掉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. 編輯距離
法一:動(dòng)規(guī)
添加元素可以理解為刪除另一個(gè)元素,刪除元素就是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()];}
};
編輯距離總結(jié)篇
添加元素可以理解為刪除另一個(gè)元素,刪除元素就是dp[i-1][j]或者dp[i][j-1],刪除某個(gè)元素可以理解成跳過該元素,繼續(xù)下一個(gè)元素
替換就是dp[i - 1][j - 1] + 1
647. 回文子串
法一:動(dòng)規(guī)
class Solution {
public:int countSubstrings(string s) {// dp[i][j]:表示區(qū)間范圍[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){//一個(gè)或兩個(gè)長度大小的字符串dp[i][j]=true;result++;}else{if(dp[i+1][j-1]){//這里的if可以和else合并dp[i][j]=true; result++;}}}}}return result;}
};
516.最長回文子序列
法一:動(dòng)規(guī)
和647題的區(qū)別在于這題不一定是連續(xù)的
class Solution {
public:int longestPalindromeSubseq(string s) {// dp[i][j]:字符串s在[i, j]范圍內(nèi)最長的回文子序列的長度為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];}
};
總結(jié)
以上是默认站点為你收集整理的leetcode(力扣)刷题笔记(c++)【中】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为DHCP故障常用排查命令
- 下一篇: 中兴通讯扬帆国际化难掩主场失意:内外销市