五大经典算法二 回溯

回溯算法在解决多选择问题时特别有效,一般思路如下:在当前场景下,存在若干种选择去操作,有可能两种结果:一是违反相应条件限制,只能返回(back),另一种是该选择选到最后居然正确并结束。故在回溯时存在三要素,能总结出这样的三要素问题便可以迅速解决:

1 找到选择

2 限制条件,即选择操作在此条件下才进行

3 结束

回溯在迷宫问题等应用广泛,下面的Leetcode22题Generate Parentheses 也是很典型的回溯问题。

Generate Parenthese要求给出n对括号下的所有可能例如

n=3

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]
从题目寻找三要素:

1 选择:

加左括号

加右括号

2 条件

左括号没有用完(才可以加左括号)

右括号数目小于左括号数目(才可以加右括号)

3 结束

左右括号均用完

思路:


     
     
  1. if (左右括号都已用完) {
  2. 加入解集,返回
  3. }
  4. //否则开始试各种选择
  5. if (还有左括号可以用) {
  6. 加一个左括号,继续递归
  7. }
  8. if (右括号小于左括号) {
  9. 加一个右括号,继续递归
  10. }
代码:


     
     
  1. void backtrade(string sublist,vector<string> &res,int left,int right)//left 是左括号剩余
  2. {
  3. if(left== 0&&right== 0){res.push_back(sublist); return;
  4. } //左右括号用完
  5. if(left> 0){backtrade(sublist+ "(",res,left -1,right);} //左括号可以用
  6. if(left<right){backtrade(sublist+ ")",res,left,right -1);}
  7. }
  8. vector< string> generateParenthesis( int n){
  9. vector< string> res;
  10. backtrade( "",res,n,n);
  11. return res;
  12. }

是一个非常典型的套路,有许多题都会用到,基本上大部分NP问题的求解都是用这个方式,比如N-QueensSudoku SolverCombination SumCombinationsPermutationsWord Break IIPalindrome Partitioning等,所以大家只要把这个套路掌握熟练

例如Leetcode 51. N-Queens
当n=4时,4X4棋盘有两种摆法(Q代表皇后,.表示空白):


     
     
  1. [
  2. [ ".Q..", // Solution 1
  3. "...Q",
  4. "Q...",
  5. "..Q."],
  6. [ "..Q.", // Solution 2
  7. "Q...",
  8. "...Q",
  9. ".Q.."]
  10. ]
这类NP问题时间复杂度肯定是指数量级,解题思路是回溯,即通过循环下递归,条件约束回溯,直到满足结束条件。在这里回溯三要素:

一 选择:添加Q还是点

二 条件:满足NQueens布局条件

三 所有行都满足条件时存储棋盘并结束

定义一个数组colForrow[n],colForrow[i]=j代表第i行第j列上是皇后。这是问题的需要耗费的空间复杂度O(N)

问题的首先需要检查加入当前皇后的合法性:

检查行:即当前行row不存在第二个皇后;

检查列:即当前列不存在第二个皇后;

因为我们采用逐行求解,故在当前行布置皇后前该行全部空白自动满足,主要考虑列,即当前行与前面的不冲突。

检查对角线:就是行的差和列的差的绝对值不要相等就可以

总体设计思路:

采用循环递归机制即在每一层递归函数内,用一个循环(循环是针对列的循环)把一个皇后填入对应行的每一列,如果当前加入第j列棋盘合法则递归处理下一行,如果在处理下一行时发现遍历所有列都不能满足棋盘规则,则退出该层递归,回溯返回到上一行。注意此时不用再把添加的元素移除,因为这里用的是一个一维数组去存皇后在对应行的哪一列,当回溯到上一行时该数组自动回到原来状态。在进入上一行后,由于循环将列数j加1继续判断。直到所有行都完成保存结果并结束


     
     
  1. //Queens
  2. string vectorcharTostring(vector<char> ss){
  3. string res= "";
  4. for( int i= 0;i<ss.size();i++)
  5. res+=ss[i];
  6. res+= '\0';
  7. return res;
  8. }
  9. bool checkValidQueen(int row,int colForrow[])
  10. {
  11. for( int i= 0;i<row;i++)
  12. if(colForrow[row]==colForrow[i]|| abs(colForrow[row]-colForrow[i])==row-i) return 0;
  13. return 1;
  14. } //除了刚加的,前面的都是合法,故只需检查当前行和前面
  15. void helper_queen(int n,int row,int colForrow[], vector< vector<string> >& res)
  16. {
  17. if(row==n){
  18. vector< string> item;
  19. for( int i= 0;i<n;i++)
  20. {
  21. vector< char> strRow;
  22. for( int j= 0;j<n;j++)
  23. if(colForrow[i]==j)strRow.push_back( 'Q');
  24. else strRow.push_back( '.');
  25. string tmp=vectorcharTostring(strRow);
  26. item.push_back(tmp);
  27. }
  28. //for(int i=0;i<item.size();i++)cout<<item[i]<<endl;
  29. res.push_back(item); //存储完毕
  30. return;
  31. }
  32. for( int i= 0;i<n;i++)
  33. {
  34. colForrow[row]=i;
  35. if(checkValidQueen(row,colForrow))
  36. helper_queen(n,row+ 1,colForrow,res);
  37. }
  38. } //逐行求解,在每一行尝试每个列位置
  39. vector< vector< string> > solveNQueens( int n){
  40. vector< vector< string> >res;
  41. int * colForrow=( int*) malloc( sizeof( int)*n);
  42. helper_queen(n, 0,colForrow,res);
  43. return res;
  44. }
以n=4来简单描述思路:



 再例如39. Combination Sum
给定一候选序列和一个目标值,要求从候选序列中选出若干数据使得它们之和与目标值相等,这里每个选出的数据可以被重复使用。还是回溯思想:

一 选择:选择一个元素

二 条件:

三 直到和与taget相等

设计思路:

也是循环递归方式,每次递归中某次循环i时选择candidate[i]元素,把剩下的元素一一加到结果集合中,并且把目标减去加入的元素,然后把剩下元素(由于可以重复使用故包括当前加入的元素)放到下一层递归中解决子问题。若得到的差值小于0,则失败,需要增加该递归中的循环i,循环结束返回上一层函数,此时需要清空已经放入结果集的元素,该层i自增。若最终得到差值为0则存储结果。


     
     
  1. void sift(vector<int>&nums,int low,int high){
  2. int i=low,j= 2*i+ 1;
  3. if(low<high){
  4. int tmp=nums[low];
  5. while(j<=high){
  6. if(j+ 1<=high&&nums[j]<nums[j+ 1])j++;
  7. if(nums[j]>tmp){nums[i]=nums[j];i=j;j= 2*i+ 1;}
  8. else break;
  9. }
  10. nums[i]=tmp;
  11. }
  12. }
  13. void sort_heap(vector<int>&num){
  14. int n=num.size();
  15. for( int i=n/ 2 -1;i>= 0;i--)
  16. sift(num,i,n -1);
  17. for( int i=n -1;i>= 1;i--){ int t=num[ 0];num[ 0]=num[i];num[i]=t;sift(num, 0,i -1);}
  18. //for(int i=0;i<num.size();i++)cout<<num[i]<<" ";
  19. }
  20. //combinationsum
  21. void helper_combination(vector<int> candidates,int start,int target,vector<int> item,vector< vector<int> >&res){
  22. if(target< 0) return;
  23. if(target== 0){res.push_back(item); return;} //达到结束
  24. for( int i=start;i<candidates.size();i++){
  25. if(i -1>= 0&&candidates[i]==candidates[i -1]) continue; //因为可以重复使用,故集合里的重复元素没有什么用
  26. item.push_back(candidates[i]);
  27. helper_combination(candidates,i,target-candidates[i],item,res);
  28. item.pop_back(); //这里需要删除
  29. }
  30. }
  31. vector< vector< int> > combinationSum( vector< int>& candidates, int target){
  32. sort_heap(candidates);
  33. vector< vector< int> >res;
  34. vector< int> item;
  35. helper_combination(candidates, 0,target,item,res);
  36. return res;
  37. }
举例如下:

候选集合[2,3,6,7],target=7

正确结果:


     
     
  1. [
  2. [ 7],
  3. [ 2, 2, 3]
  4. ]

大概思路:

77. Combinations
也类似思路,给出n,从1-n选出k个组成一对,求出所有这样的对

例如

n=4,k=2:


     
     
  1. [
  2. [ 2, 4],
  3. [ 3, 4],
  4. [ 2, 3],
  5. [ 1, 2],
  6. [ 1, 3],
  7. [ 1, 4],
  8. ]
也是循环递归,不合适回溯上一递归。


     
     
  1. / combinations
  2. void helper_combine (int n,int k,int start,vector<int> item,vector< vector<int> >&res){
  3. if(item.size()==k){res.push_back(item); return;}
  4. for( int i=start;i<=n;i++)
  5. {item.push_back(i);
  6. helper_combine(n,k,i+ 1,item,res);
  7. item.pop_back();
  8. }
  9. }
  10. vector< vector< int> >combine( int n, int k){
  11. vector< vector< int> >res;
  12. if(n< 1||k>n) return res;
  13. vector< int> item;
  14. helper_combine(n,k, 1,item,res);
  15. return res;
  16. }
对于46. Permutations 也一人如此

对于只给出数字n:


     
     
  1. //permutation
  2. void helper_Npermutation(int n,int start,vector<int> item){
  3. if(n==item.size()){ for( int i= 0;i<n;i++) cout<<item[i]<< " "; cout<< endl;}
  4. else{
  5. for( int i= 1;i<=n;i++)
  6. { int f= 1;
  7. for( int j= 0;j<start;j++) if(item[j]==i)f= 0;
  8.   if(f){
  9. item.push_back(i);
  10. helper_Npermutation(n,i+ 1,item);
  11. item.pop_back();}
  12. }
  13. }
  14. }
  15. void getNpermutation(int n){
  16. if(n< 1) return;
  17. vector< int>item;
  18. helper_Npermutation(n, 0,item);
  19. }
对于给出一个非重复序列:


     
     
  1. void helper_permutation(vector<int> candidates,int start,vector<int> item,vector< vector<int> >&res){
  2. if(item.size()==candidates.size()){res.push_back(item); return;}
  3. int n=candidates.size();
  4. for( int i= 0;i<n;i++)
  5. { int f= 1;
  6. for( int j= 0;j<item.size();j++) if(item[j]==candidates[i])f= 0;
  7. if(f){
  8. item.push_back(candidates[i]);
  9. helper_permutation(candidates,i+ 1,item,res);
  10. item.pop_back();
  11. }
  12. }
  13. }
  14. vector< vector< int> > permute( vector< int>& nums){
  15. vector< vector< int> >res;
  16. if(nums.size()< 1) return res;
  17. vector< int>item;
  18. helper_permutation(nums, 0,item,res);
  19. return res;
  20. }
对于给出一个重复序列:
主要先要排序:


     
     
  1. void helper_permutation(vector<int> candidates,int start,vector<int> item,vector< vector<int> >&res){
  2. if(item.size()==candidates.size()){res.push_back(item); return;}
  3. int n=candidates.size();
  4. for( int i= 0;i<n;i++)
  5. { if(!i||candidates[i]!=candidates[i -1]){
  6. int f1= 0,f2= 0;
  7. for( int j= 0;j<item.size();j++) if(item[j]==candidates[i])f1++;
  8. for( int j= 0;j<n;j++) if(candidates[j]==candidates[i])f2++;
  9. if(f2>f1){
  10. item.push_back(candidates[i]);
  11. helper_permutation(candidates,i+ 1,item,res);
  12. item.pop_back();
  13. }
  14. }
  15. }
  16. }
  17. vector< vector< int> > permuteUnique( vector< int>& nums){
  18. vector< vector< int> >res;
  19. if(nums.size()< 1) return res;
  20. vector< int>item;
  21. sort(nums.begin(),nums.end()); //include <algorithm>
  22. helper_permutation(nums, 0,item,res);
  23. return res;
  24. }


140. Word Break II


     
     
  1. string vectorcharTostring(vector<char> ss){
  2. string res= "";
  3. for( int i= 0;i<ss.size();i++)
  4. res+=ss[i];
  5. //res+='\0';
  6. return res;
  7. }
  8. bool find_vector(vector<string> ss,string s){
  9. int i= 0;
  10. while(i<ss.size()){ if(s.compare(ss[i])== 0) return 1;i++;}
  11. return 0;
  12. }
  13. //word break2
  14. void helper_wordbreak(string s,vector<string> wordDict,int start,string item,vector<string>& res){
  15. if(start>=s.length()){res.push_back(item);item.clear(); return;}
  16. vector< char> t;
  17. for( int i=start;i<s.length();i++)
  18. {
  19. t.push_back(s[i]);
  20. // cout<<vectorcharTostring(t).size()<<endl;
  21. if(find_vector(wordDict,vectorcharTostring(t)))
  22. {
  23. //item+=(vectorcharTostring(t));
  24. //if(i<s.length()-1)item+=" ";
  25. string newitem=item.length()?(item+ " "+vectorcharTostring(t)):vectorcharTostring(t);
  26. helper_wordbreak(s,wordDict,i+ 1,newitem,res);
  27. }
  28. }
  29. }
  30. vector< string> wordBreak( string s, vector< string>& wordDict){
  31. vector< string> res;
  32. if(wordDict.size()< 1) return res;
  33. string item= "";
  34. helper_wordbreak(s,wordDict, 0,item,res);
  35. return res;
  36. }
这种类似以前NQueen,Permutation做法正确,但是会超时Time Limit Exceeded!故需要完善。结合139. Word Break 题采用的动态规划做法,这里也可以引入,故代码修改如下:


     
     
  1. bool find_vector(vector<string> ss,string s){
  2. int i= 0;
  3. while(i<ss.size()){ if(s.compare(ss[i])== 0) return 1;i++;}
  4. return 0;
  5. }
  6. //word break2
  7. void helper_wordbreak(string s,vector<string> wordDict,int start,string item,vector<string>& res,vector<bool> dp){
  8. string t;
  9. for( int i= 1;i+start<=s.length();i++)
  10. {
  11. if(dp[i+start]&&find_vector(wordDict,s.substr(start,i))){
  12. t=s.substr(start,i);
  13. if(i+start<s.length())helper_wordbreak(s,wordDict,i+start,item+t+ " ",res,dp);
  14. else res.push_back(item+t);
  15. }
  16. }
  17. }
  18. vector< string> wordBreak( string s, vector< string>& wordDict){
  19. vector< string> res;
  20. if(wordDict.size()< 1) return res;
  21. string item= "";
  22. vector< bool> dp(s.length()+ 1, false);
  23. dp[ 0]= true;
  24. int min_len=INT_MAX,max_len= 0;
  25. for( int i= 0;i<wordDict.size();i++){ if(min_len>wordDict[i].size())min_len=wordDict[i].size();
  26. if(max_len<wordDict[i].size())max_len=wordDict[i].size();
  27. }
  28. for( int i= 0;i<s.size();i++){
  29. if(dp[i]){ for( int len=min_len;i+len<=s.size()&&len<=max_len;len++)
  30. if(find_vector(wordDict,s.substr(i,len)))dp[i+len]= 1;
  31. }
  32. }
  33. if(dp[s.length()])
  34. helper_wordbreak(s,wordDict, 0,item,res,dp);
  35. return res;
  36. }
131. Palindrome Partitioning

给定一个字符串,把内部分部,使得每部分子串也是回文串。

首先需要得到字符串所有字串是否是回文,这里采用动态递归法,引入二维数组bool dp[i][j],表示从i到j这一子串是否是回文,下面是递归公式

dp[i][j]=1 when s[i]==s[j]&&j-i==1(即相邻)或者dp[i+1][j-1]=1

dp[i][j]=0;

故初始值为0。


     
     
  1. vector < vector < bool> > getdict( string s){
  2. vector< vector< bool> > res;
  3. for( int i= 0;i<s.length();i++){
  4. vector< bool> aa;
  5. for( int j= 0;j<s.length();j++){aa.push_back( 0);};
  6. res.push_back(aa);
  7. }
  8. if(s.size()< 1) return res;
  9. for( int i=s.length() -1;i>= 0;i--)
  10. for( int j=i;j<s.length();j++)
  11. if(s[i]==s[j]&&((j-i< 2)||res[i+ 1][j -1]))res[i][j]= 1;
  12. return res;
  13. } //判断任意字串之间是不是回文
剩下的很明显也是循环递归,不合适回溯即可:


     
     
  1. //palindrome Partitioning
  2. vector < vector < bool> > getdict( string s){
  3. vector< vector< bool> > res;
  4. for( int i= 0;i<s.length();i++){
  5. vector< bool> aa;
  6. for( int j= 0;j<s.length();j++){aa.push_back( 0);};
  7. res.push_back(aa);
  8. }
  9. if(s.size()< 1) return res;
  10. for( int i=s.length() -1;i>= 0;i--)
  11. for( int j=i;j<s.length();j++)
  12. if(s[i]==s[j]&&((j-i< 2)||res[i+ 1][j -1]))res[i][j]= 1;
  13. return res;
  14. } //判断任意字串之间是不是回文
  15. void helper_partitioning(string s,vector< vector<bool> > dict,int start, vector<string> item,vector< vector<string> > &res){
  16. if(start==s.length()){res.push_back(item); return;}
  17. for( int i=start;i<s.length();i++)
  18. { if(dict[start][i]){item.push_back(s.substr(start,(i-start+ 1)));helper_partitioning(s,dict,i+ 1,item,res);item.pop_back();}
  19. }
  20. }
  21. vector< vector< string> > partition( string s){
  22. vector< vector< string> >res;
  23. if(s.length()< 1) return res;
  24. vector< vector< bool> >dict;
  25. dict=getdict(s);
  26. vector< string> item;
  27. helper_partitioning(s,dict, 0,item,res);
  28. return res;
  29. }

93. Restore IP Addresses

给定一个只含数字的字符串,判断所能得到的 所有IP地址

例如:

s=”25525511135”

则存在[“255.255.11.135”, “255.255.111.35”]

首先判断IP地址要求的格式:

1 一定含有点分开的四段子字段,每段字符数最多3个

2 每个子字段转换成int范围是0-255

3 每个字符串可以是0,但不能是00,01,0XX

同样运用回溯,次取1,2,3长度的子串,判断其为合法的IP地址子字段后,取后面的真个字符串递归判断,通过count从0增加到3来判断是否完成4个子段的判断,将结果加入最终的res中去。


     
     
  1. bool isValidsubstr(string substr1){
  2. if(substr1[ 0]== '0'){ return substr1== "0";
  3. }
  4. int res=atoi(substr1.c_str());
  5. return res>= 0&&res<= 255;
  6. } //判断字串合法
  7. void helper_ipaddress(string s,int start,string item,vector<string>&res){
  8. if(start== 3&&isValidsubstr(s)){res.push_back(item+s); return;}
  9. for( int i= 1;i<= 3&&i<s.size();i++){
  10. string sub=s.substr( 0,i);
  11. if(isValidsubstr(sub)){helper_ipaddress(s.substr(i),start+ 1,item+sub+ ".",res);}
  12. }
  13. }
  14. vector< string> restoreIpAddresses( string s){
  15. vector< string> res;
  16. if(s.size()< 1||s.size()< 4||s.size()> 12) return res;
  17. string item= "";
  18. helper_ipaddress(s, 0,item,res);
  19. return res;
  20. }





转自博文:五大经典算法二 回溯

回溯算法在解决多选择问题时特别有效,一般思路如下:在当前场景下,存在若干种选择去操作,有可能两种结果:一是违反相应条件限制,只能返回(back),另一种是该选择选到最后居然正确并结束。故在回溯时存在三要素,能总结出这样的三要素问题便可以迅速解决:

1 找到选择

2 限制条件,即选择操作在此条件下才进行

3 结束

回溯在迷宫问题等应用广泛,下面的Leetcode22题Generate Parentheses 也是很典型的回溯问题。

Generate Parenthese要求给出n对括号下的所有可能例如

n=3

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]
从题目寻找三要素:

1 选择:

加左括号

加右括号

2 条件

左括号没有用完(才可以加左括号)

右括号数目小于左括号数目(才可以加右括号)

3 结束

左右括号均用完

思路:


  
  
  1. if (左右括号都已用完) {
  2. 加入解集,返回
  3. }
  4. //否则开始试各种选择
  5. if (还有左括号可以用) {
  6. 加一个左括号,继续递归
  7. }
  8. if (右括号小于左括号) {
  9. 加一个右括号,继续递归
  10. }
代码:


  
  
  1. void backtrade(string sublist,vector<string> &res,int left,int right)//left 是左括号剩余
  2. {
  3. if(left== 0&&right== 0){res.push_back(sublist); return;
  4. } //左右括号用完
  5. if(left> 0){backtrade(sublist+ "(",res,left -1,right);} //左括号可以用
  6. if(left<right){backtrade(sublist+ ")",res,left,right -1);}
  7. }
  8. vector< string> generateParenthesis( int n){
  9. vector< string> res;
  10. backtrade( "",res,n,n);
  11. return res;
  12. }

是一个非常典型的套路,有许多题都会用到,基本上大部分NP问题的求解都是用这个方式,比如N-QueensSudoku SolverCombination SumCombinationsPermutationsWord Break IIPalindrome Partitioning等,所以大家只要把这个套路掌握熟练

例如Leetcode 51. N-Queens
当n=4时,4X4棋盘有两种摆法(Q代表皇后,.表示空白):


  
  
  1. [
  2. [ ".Q..", // Solution 1
  3. "...Q",
  4. "Q...",
  5. "..Q."],
  6. [ "..Q.", // Solution 2
  7. "Q...",
  8. "...Q",
  9. ".Q.."]
  10. ]
这类NP问题时间复杂度肯定是指数量级,解题思路是回溯,即通过循环下递归,条件约束回溯,直到满足结束条件。在这里回溯三要素:

一 选择:添加Q还是点

二 条件:满足NQueens布局条件

三 所有行都满足条件时存储棋盘并结束

定义一个数组colForrow[n],colForrow[i]=j代表第i行第j列上是皇后。这是问题的需要耗费的空间复杂度O(N)

问题的首先需要检查加入当前皇后的合法性:

检查行:即当前行row不存在第二个皇后;

检查列:即当前列不存在第二个皇后;

因为我们采用逐行求解,故在当前行布置皇后前该行全部空白自动满足,主要考虑列,即当前行与前面的不冲突。

检查对角线:就是行的差和列的差的绝对值不要相等就可以

总体设计思路:

采用循环递归机制即在每一层递归函数内,用一个循环(循环是针对列的循环)把一个皇后填入对应行的每一列,如果当前加入第j列棋盘合法则递归处理下一行,如果在处理下一行时发现遍历所有列都不能满足棋盘规则,则退出该层递归,回溯返回到上一行。注意此时不用再把添加的元素移除,因为这里用的是一个一维数组去存皇后在对应行的哪一列,当回溯到上一行时该数组自动回到原来状态。在进入上一行后,由于循环将列数j加1继续判断。直到所有行都完成保存结果并结束


  
  
  1. //Queens
  2. string vectorcharTostring(vector<char> ss){
  3. string res= "";
  4. for( int i= 0;i<ss.size();i++)
  5. res+=ss[i];
  6. res+= '\0';
  7. return res;
  8. }
  9. bool checkValidQueen(int row,int colForrow[])
  10. {
  11. for( int i= 0;i<row;i++)
  12. if(colForrow[row]==colForrow[i]|| abs(colForrow[row]-colForrow[i])==row-i) return 0;
  13. return 1;
  14. } //除了刚加的,前面的都是合法,故只需检查当前行和前面
  15. void helper_queen(int n,int row,int colForrow[], vector< vector<string> >& res)
  16. {
  17. if(row==n){
  18. vector< string> item;
  19. for( int i= 0;i<n;i++)
  20. {
  21. vector< char> strRow;
  22. for( int j= 0;j<n;j++)
  23. if(colForrow[i]==j)strRow.push_back( 'Q');
  24. else strRow.push_back( '.');
  25. string tmp=vectorcharTostring(strRow);
  26. item.push_back(tmp);
  27. }
  28. //for(int i=0;i<item.size();i++)cout<<item[i]<<endl;
  29. res.push_back(item); //存储完毕
  30. return;
  31. }
  32. for( int i= 0;i<n;i++)
  33. {
  34. colForrow[row]=i;
  35. if(checkValidQueen(row,colForrow))
  36. helper_queen(n,row+ 1,colForrow,res);
  37. }
  38. } //逐行求解,在每一行尝试每个列位置
  39. vector< vector< string> > solveNQueens( int n){
  40. vector< vector< string> >res;
  41. int * colForrow=( int*) malloc( sizeof( int)*n);
  42. helper_queen(n, 0,colForrow,res);
  43. return res;
  44. }
以n=4来简单描述思路:



 再例如39. Combination Sum
给定一候选序列和一个目标值,要求从候选序列中选出若干数据使得它们之和与目标值相等,这里每个选出的数据可以被重复使用。还是回溯思想:

一 选择:选择一个元素

二 条件:

三 直到和与taget相等

设计思路:

也是循环递归方式,每次递归中某次循环i时选择candidate[i]元素,把剩下的元素一一加到结果集合中,并且把目标减去加入的元素,然后把剩下元素(由于可以重复使用故包括当前加入的元素)放到下一层递归中解决子问题。若得到的差值小于0,则失败,需要增加该递归中的循环i,循环结束返回上一层函数,此时需要清空已经放入结果集的元素,该层i自增。若最终得到差值为0则存储结果。


  
  
  1. void sift(vector<int>&nums,int low,int high){
  2. int i=low,j= 2*i+ 1;
  3. if(low<high){
  4. int tmp=nums[low];
  5. while(j<=high){
  6. if(j+ 1<=high&&nums[j]<nums[j+ 1])j++;
  7. if(nums[j]>tmp){nums[i]=nums[j];i=j;j= 2*i+ 1;}
  8. else break;
  9. }
  10. nums[i]=tmp;
  11. }
  12. }
  13. void sort_heap(vector<int>&num){
  14. int n=num.size();
  15. for( int i=n/ 2 -1;i>= 0;i--)
  16. sift(num,i,n -1);
  17. for( int i=n -1;i>= 1;i--){ int t=num[ 0];num[ 0]=num[i];num[i]=t;sift(num, 0,i -1);}
  18. //for(int i=0;i<num.size();i++)cout<<num[i]<<" ";
  19. }
  20. //combinationsum
  21. void helper_combination(vector<int> candidates,int start,int target,vector<int> item,vector< vector<int> >&res){
  22. if(target< 0) return;
  23. if(target== 0){res.push_back(item); return;} //达到结束
  24. for( int i=start;i<candidates.size();i++){
  25. if(i -1>= 0&&candidates[i]==candidates[i -1]) continue; //因为可以重复使用,故集合里的重复元素没有什么用
  26. item.push_back(candidates[i]);
  27. helper_combination(candidates,i,target-candidates[i],item,res);
  28. item.pop_back(); //这里需要删除
  29. }
  30. }
  31. vector< vector< int> > combinationSum( vector< int>& candidates, int target){
  32. sort_heap(candidates);
  33. vector< vector< int> >res;
  34. vector< int> item;
  35. helper_combination(candidates, 0,target,item,res);
  36. return res;
  37. }
举例如下:

候选集合[2,3,6,7],target=7

正确结果:


  
  
  1. [
  2. [ 7],
  3. [ 2, 2, 3]
  4. ]

大概思路:

77. Combinations
也类似思路,给出n,从1-n选出k个组成一对,求出所有这样的对

例如

n=4,k=2:


  
  
  1. [
  2. [ 2, 4],
  3. [ 3, 4],
  4. [ 2, 3],
  5. [ 1, 2],
  6. [ 1, 3],
  7. [ 1, 4],
  8. ]
也是循环递归,不合适回溯上一递归。


  
  
  1. / combinations
  2. void helper_combine (int n,int k,int start,vector<int> item,vector< vector<int> >&res){
  3. if(item.size()==k){res.push_back(item); return;}
  4. for( int i=start;i<=n;i++)
  5. {item.push_back(i);
  6. helper_combine(n,k,i+ 1,item,res);
  7. item.pop_back();
  8. }
  9. }
  10. vector< vector< int> >combine( int n, int k){
  11. vector< vector< int> >res;
  12. if(n< 1||k>n) return res;
  13. vector< int> item;
  14. helper_combine(n,k, 1,item,res);
  15. return res;
  16. }
对于46. Permutations 也一人如此

对于只给出数字n:


  
  
  1. //permutation
  2. void helper_Npermutation(int n,int start,vector<int> item){
  3. if(n==item.size()){ for( int i= 0;i<n;i++) cout<<item[i]<< " "; cout<< endl;}
  4. else{
  5. for( int i= 1;i<=n;i++)
  6. { int f= 1;
  7. for( int j= 0;j<start;j++) if(item[j]==i)f= 0;
  8.   if(f){
  9. item.push_back(i);
  10. helper_Npermutation(n,i+ 1,item);
  11. item.pop_back();}
  12. }
  13. }
  14. }
  15. void getNpermutation(int n){
  16. if(n< 1) return;
  17. vector< int>item;
  18. helper_Npermutation(n, 0,item);
  19. }
对于给出一个非重复序列:


  
  
  1. void helper_permutation(vector<int> candidates,int start,vector<int> item,vector< vector<int> >&res){
  2. if(item.size()==candidates.size()){res.push_back(item); return;}
  3. int n=candidates.size();
  4. for( int i= 0;i<n;i++)
  5. { int f= 1;
  6. for( int j= 0;j<item.size();j++) if(item[j]==candidates[i])f= 0;
  7. if(f){
  8. item.push_back(candidates[i]);
  9. helper_permutation(candidates,i+ 1,item,res);
  10. item.pop_back();
  11. }
  12. }
  13. }
  14. vector< vector< int> > permute( vector< int>& nums){
  15. vector< vector< int> >res;
  16. if(nums.size()< 1) return res;
  17. vector< int>item;
  18. helper_permutation(nums, 0,item,res);
  19. return res;
  20. }
对于给出一个重复序列:
主要先要排序:


  
  
  1. void helper_permutation(vector<int> candidates,int start,vector<int> item,vector< vector<int> >&res){
  2. if(item.size()==candidates.size()){res.push_back(item); return;}
  3. int n=candidates.size();
  4. for( int i= 0;i<n;i++)
  5. { if(!i||candidates[i]!=candidates[i -1]){
  6. int f1= 0,f2= 0;
  7. for( int j= 0;j<item.size();j++) if(item[j]==candidates[i])f1++;
  8. for( int j= 0;j<n;j++) if(candidates[j]==candidates[i])f2++;
  9. if(f2>f1){
  10. item.push_back(candidates[i]);
  11. helper_permutation(candidates,i+ 1,item,res);
  12. item.pop_back();
  13. }
  14. }
  15. }
  16. }
  17. vector< vector< int> > permuteUnique( vector< int>& nums){
  18. vector< vector< int> >res;
  19. if(nums.size()< 1) return res;
  20. vector< int>item;
  21. sort(nums.begin(),nums.end()); //include <algorithm>
  22. helper_permutation(nums, 0,item,res);
  23. return res;
  24. }


140. Word Break II


  
  
  1. string vectorcharTostring(vector<char> ss){
  2. string res= "";
  3. for( int i= 0;i<ss.size();i++)
  4. res+=ss[i];
  5. //res+='\0';
  6. return res;
  7. }
  8. bool find_vector(vector<string> ss,string s){
  9. int i= 0;
  10. while(i<ss.size()){ if(s.compare(ss[i])== 0) return 1;i++;}
  11. return 0;
  12. }
  13. //word break2
  14. void helper_wordbreak(string s,vector<string> wordDict,int start,string item,vector<string>& res){
  15. if(start>=s.length()){res.push_back(item);item.clear(); return;}
  16. vector< char> t;
  17. for( int i=start;i<s.length();i++)
  18. {
  19. t.push_back(s[i]);
  20. // cout<<vectorcharTostring(t).size()<<endl;
  21. if(find_vector(wordDict,vectorcharTostring(t)))
  22. {
  23. //item+=(vectorcharTostring(t));
  24. //if(i<s.length()-1)item+=" ";
  25. string newitem=item.length()?(item+ " "+vectorcharTostring(t)):vectorcharTostring(t);
  26. helper_wordbreak(s,wordDict,i+ 1,newitem,res);
  27. }
  28. }
  29. }
  30. vector< string> wordBreak( string s, vector< string>& wordDict){
  31. vector< string> res;
  32. if(wordDict.size()< 1) return res;
  33. string item= "";
  34. helper_wordbreak(s,wordDict, 0,item,res);
  35. return res;
  36. }
这种类似以前NQueen,Permutation做法正确,但是会超时Time Limit Exceeded!故需要完善。结合139. Word Break 题采用的动态规划做法,这里也可以引入,故代码修改如下:


  
  
  1. bool find_vector(vector<string> ss,string s){
  2. int i= 0;
  3. while(i<ss.size()){ if(s.compare(ss[i])== 0) return 1;i++;}
  4. return 0;
  5. }
  6. //word break2
  7. void helper_wordbreak(string s,vector<string> wordDict,int start,string item,vector<string>& res,vector<bool> dp){
  8. string t;
  9. for( int i= 1;i+start<=s.length();i++)
  10. {
  11. if(dp[i+start]&&find_vector(wordDict,s.substr(start,i))){
  12. t=s.substr(start,i);
  13. if(i+start<s.length())helper_wordbreak(s,wordDict,i+start,item+t+ " ",res,dp);
  14. else res.push_back(item+t);
  15. }
  16. }
  17. }
  18. vector< string> wordBreak( string s, vector< string>& wordDict){
  19. vector< string> res;
  20. if(wordDict.size()< 1) return res;
  21. string item= "";
  22. vector< bool> dp(s.length()+ 1, false);
  23. dp[ 0]= true;
  24. int min_len=INT_MAX,max_len= 0;
  25. for( int i= 0;i<wordDict.size();i++){ if(min_len>wordDict[i].size())min_len=wordDict[i].size();
  26. if(max_len<wordDict[i].size())max_len=wordDict[i].size();
  27. }
  28. for( int i= 0;i<s.size();i++){
  29. if(dp[i]){ for( int len=min_len;i+len<=s.size()&&len<=max_len;len++)
  30. if(find_vector(wordDict,s.substr(i,len)))dp[i+len]= 1;
  31. }
  32. }
  33. if(dp[s.length()])
  34. helper_wordbreak(s,wordDict, 0,item,res,dp);
  35. return res;
  36. }
131. Palindrome Partitioning

给定一个字符串,把内部分部,使得每部分子串也是回文串。

首先需要得到字符串所有字串是否是回文,这里采用动态递归法,引入二维数组bool dp[i][j],表示从i到j这一子串是否是回文,下面是递归公式

dp[i][j]=1 when s[i]==s[j]&&j-i==1(即相邻)或者dp[i+1][j-1]=1

dp[i][j]=0;

故初始值为0。


  
  
  1. vector < vector < bool> > getdict( string s){
  2. vector< vector< bool> > res;
  3. for( int i= 0;i<s.length();i++){
  4. vector< bool> aa;
  5. for( int j= 0;j<s.length();j++){aa.push_back( 0);};
  6. res.push_back(aa);
  7. }
  8. if(s.size()< 1) return res;
  9. for( int i=s.length() -1;i>= 0;i--)
  10. for( int j=i;j<s.length();j++)
  11. if(s[i]==s[j]&&((j-i< 2)||res[i+ 1][j -1]))res[i][j]= 1;
  12. return res;
  13. } //判断任意字串之间是不是回文
剩下的很明显也是循环递归,不合适回溯即可:


  
  
  1. //palindrome Partitioning
  2. vector < vector < bool> > getdict( string s){
  3. vector< vector< bool> > res;
  4. for( int i= 0;i<s.length();i++){
  5. vector< bool> aa;
  6. for( int j= 0;j<s.length();j++){aa.push_back( 0);};
  7. res.push_back(aa);
  8. }
  9. if(s.size()< 1) return res;
  10. for( int i=s.length() -1;i>= 0;i--)
  11. for( int j=i;j<s.length();j++)
  12. if(s[i]==s[j]&&((j-i< 2)||res[i+ 1][j -1]))res[i][j]= 1;
  13. return res;
  14. } //判断任意字串之间是不是回文
  15. void helper_partitioning(string s,vector< vector<bool> > dict,int start, vector<string> item,vector< vector<string> > &res){
  16. if(start==s.length()){res.push_back(item); return;}
  17. for( int i=start;i<s.length();i++)
  18. { if(dict[start][i]){item.push_back(s.substr(start,(i-start+ 1)));helper_partitioning(s,dict,i+ 1,item,res);item.pop_back();}
  19. }
  20. }
  21. vector< vector< string> > partition( string s){
  22. vector< vector< string> >res;
  23. if(s.length()< 1) return res;
  24. vector< vector< bool> >dict;
  25. dict=getdict(s);
  26. vector< string> item;
  27. helper_partitioning(s,dict, 0,item,res);
  28. return res;
  29. }

93. Restore IP Addresses

给定一个只含数字的字符串,判断所能得到的 所有IP地址

例如:

s=”25525511135”

则存在[“255.255.11.135”, “255.255.111.35”]

首先判断IP地址要求的格式:

1 一定含有点分开的四段子字段,每段字符数最多3个

2 每个子字段转换成int范围是0-255

3 每个字符串可以是0,但不能是00,01,0XX

同样运用回溯,次取1,2,3长度的子串,判断其为合法的IP地址子字段后,取后面的真个字符串递归判断,通过count从0增加到3来判断是否完成4个子段的判断,将结果加入最终的res中去。


  
  
  1. bool isValidsubstr(string substr1){
  2. if(substr1[ 0]== '0'){ return substr1== "0";
  3. }
  4. int res=atoi(substr1.c_str());
  5. return res>= 0&&res<= 255;
  6. } //判断字串合法
  7. void helper_ipaddress(string s,int start,string item,vector<string>&res){
  8. if(start== 3&&isValidsubstr(s)){res.push_back(item+s); return;}
  9. for( int i= 1;i<= 3&&i<s.size();i++){
  10. string sub=s.substr( 0,i);
  11. if(isValidsubstr(sub)){helper_ipaddress(s.substr(i),start+ 1,item+sub+ ".",res);}
  12. }
  13. }
  14. vector< string> restoreIpAddresses( string s){
  15. vector< string> res;
  16. if(s.size()< 1||s.size()< 4||s.size()> 12) return res;
  17. string item= "";
  18. helper_ipaddress(s, 0,item,res);
  19. return res;
  20. }




猜你喜欢

转载自blog.csdn.net/yangxingpa/article/details/81517272