算法 64式 4、回溯算法整理__第2部分_14到25题

1算法思想

回溯

 

1.1含义

以深度优先方式搜索问题解的算法称为回溯法。

 

1.2思想

按照深度优先搜索策略,从根节点出发搜索解空间树,如果某结点不包含问题解,则逐层向祖先结点回溯;否则进入子树。

 

1.3特点

深度优先搜索+递归前设置变量,递归后清除变量设置

 

1.4适用

适合求取问题所有解类型的题目。例如求出问题的多少种组合。

 

1.5通用解法

回溯算法:

        如果当前是问题的解,打印解;

        设置变量;

        递归;

        清除变量设置。

 

1.6经典例题讲解

n皇后问题

在n×n格的国际象棋上摆放n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

代码如下:

       void place(int pos , vector<int>& result , vector< vector<int> >& results , int n)

       {

              if(pos == n)     //如果当前是问题的解,打印解

              {

                     results.push_back(result);

                     return;

              }

              //从中选出任意一个i值作为result[pos]摆放,作为第pos行的列

              for(int i = 0 ; i < n ; i++)

              {

                     bool isOk = true;

                     for(int j = 0 ; j < pos ; j++)

                     {

                            //如果在同一列,说明不符合,

                            if(result.at(j) == i)

                            {

                                   isOk = false;

                                   break;

                            }

                            //如果在同一对角线

                            if( abs( result.at(j) - i ) == abs(j - pos) )

                            {

                                   isOk = false;

                                   break;

                            }

                     }

                     if(isOk)

                     {

                           

                            result.push_back(i);                 //设置变量

                            place(pos + 1 , result , results , n);    //递归

                            result.pop_back();                 //清除变量设置

                     }

              }

       }

 

1.7回溯与递归的区别

回溯通常也用到递归,递归往往求取的是一个解,回溯通常求取的是多个解。回溯的过程中需要对标记量进行设置,递归完成后,需要对标记量清除设置。

 

 

2回溯系列

 

类别-编号

题目

遁去的1

14

Combination Sum II

Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

Each number in C may only be used once in the combination.

Note:

All numbers (including target) will be positive integers.

The solution set must not contain duplicate combinations.

For example, given candidate set [10, 1, 2, 7, 6, 1, 5] and target 8,

A solution set is:

[

  [1, 7],

  [1, 2, 5],

  [2, 6],

  [1, 1, 6]

]

分析:

此题在递归求解的过程中必须确保每个数字出现的次数不能超过实际出现的次数

输入:

7(数组元素个数) 8(目标值)

10 1 2 7 6 1 5

输出:

1 7,1 2 5,2 6,1 1 6

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/54935890

关键:

1 用回溯来做,如果目标值为0,就将结果压入结果集。回溯适用于求解含有多个结果的集合

回溯基本格式:

尝试某一数值

递归

清空尝试的数值

与可重复累加和的区别在于:

可重复累加和一直尝试当前值,直到目标值 < 当前候选值,则选取下一个候选值。

  如果目标值>0,继续尝试下一个值,尝试过后,弹出之前压入的值

       //如果目标变成0,说明得到结果,将结果加入结果集

       if(0 == target)

       {

              results.push_back(path);

              return;

       }

             

       //此种情况不可能

       if(target < 0)

       {

              return;

       }

       int size = candidates.size();

       for(int i = cur ; i < size ; i++)

       {

              //防止添加重复元素,如果当前元素和之前元素重复,就跳过

              if(i > cur && candidates.at(i) == candidates.at(i-1))

              {

                     continue;

              }

              path.push_back(candidates.at(i));

              //尝试下一个候选值

              combineSum(candidates , target - candidates.at(i) , i + 1 , path , results);

              //回溯

              path.pop_back();

       }

2 求组合值为sum的解法

 void combinationSum(std::vector<int> &candidates, int target, std::vector<std::vector<int> > &res, std::vector<int> &combination, int begin) {

              if  (!target) {

                     res.push_back(combination);

                     return;

              }

        for (int i = begin; i != candidates.size() && target >= candidates[i]; ++i) {

            combination.push_back(candidates[i]);

            combinationSum(candidates, target - candidates[i], res, combination, i);

            combination.pop_back();

        }

       }

 

代码:

class Solution {

public:

       //尝试用摆放来做,问题转化为递归问题

       void combineSum(vector<int>& candidates, int target , int cur , vector<int> path , vector< vector<int> >& results)

       {

              vector< vector<int> > totalResults;

              if(candidates.empty())

              {

                     return ;

              }

              //如果目标变成0,说明得到结果,将结果加入结果集

              if(0 == target)

              {

                     results.push_back(path);

                     return;

              }

             

              //此种情况不可能

              if(target < 0)

              {

                     return;

              }

              int size = candidates.size();

              for(int i = cur ; i < size ; i++)

              {

                     //防止添加重复元素,如果当前元素和之前元素重复,就跳过

                     if(i > cur && candidates.at(i) == candidates.at(i-1))

                     {

                            continue;

                     }

                     path.push_back(candidates.at(i));

                     //尝试下一个候选值

                     combineSum(candidates , target - candidates.at(i) , i + 1 , path , results);

                     //回溯

                     path.pop_back();

              }

       }

 

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {

              vector<vector<int>> results;

              if(candidates.empty())

              {

                     return results;

              }

        //必须确保候选值从大到小排序

              sort(candidates.begin() , candidates.end());

              vector<int> path;

              combineSum(candidates, target , 0 , path , results);

              return results;

    }

};

15

N-Queens

The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no two queens attack each other.

Given an integer n, return all distinct solutions to the n-queens puzzle.

Each solution contains a distinct board configuration of the n-queens' placement, where 'Q' and '.' both indicate a queen and an empty space respectively.

For example,

There exist two distinct solutions to the 4-queens puzzle:

[

 [".Q..",  // Solution 1

  "...Q",

  "Q...",

  "..Q."],

 ["..Q.",  // Solution 2

  "Q...",

  "...Q",

  ".Q.."]

]

分析:这是n皇后问题。

 

输入:

8

输出:

 [".Q..",  // Solution 1

  "...Q",

  "Q...",

  "..Q."],

 ["..Q.",  // Solution 2

  "Q...",

  "...Q",

  ".Q.."]

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/54970129

关键:

1 设一个数组A,这里可以用:i表示横坐标,用A[i]表示总国标。

我们使得横坐标确保都不相同的情况下,已经符合不在同一行的条件;

只需要再满足:

1 不在同一列的条件,即对于任意A[i]与A[j],A[i] != A[j]

2 不在同一对角线的条件:

由于同一对角线上必然满足: 斜率为1或-1: 即|A[]i - A[j]| != |i - j|

最终的结果就是数组A

A[i]表示第i行第A[i]列上放置皇后

 

代码:

class Solution {

public:

       void place(int pos , vector<int>& result , vector< vector<int> >& results , int n)

       {

              //输出结果

              if(pos == n)

              {

                     results.push_back(result);

                     return;

              }

              //从中选出任意一个i值作为result[pos]摆放,作为第pos行的列

              for(int i = 0 ; i < n ; i++)

              {

                     bool isOk = true;

                     for(int j = 0 ; j < pos ; j++)

                     {

                            //如果在同一列,说明不符合,

                            if(result.at(j) == i)

                            {

                                   isOk = false;

                                   break;

                            }

                            //如果在同一对角线

                            if( abs( result.at(j) - i ) == abs(j - pos) )

                            {

                                   isOk = false;

                                   break;

                            }

                     }

                     if(isOk)

                     {

                            //说明符合摆放

                            result.push_back(i);

                            place(pos + 1 , result , results , n);

                            //回溯

                            result.pop_back();

                     }

              }

       }

};

16

N-Queens II

Follow up for N-Queens problem.

Now, instead outputting board configurations, return the total number of distinct solutions.

题目不需要输出棋盘摆放结果,而是输出总共有多少种解

输入:

4

输出:

2

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/54970313

class Solution {

public:

       void place(int pos , vector<int>& result , int& totalResult , int n)

       {

              //输出结果

              if(pos == n)

              {

                     totalResult++;

                     return;

              }

              //从中选出任意一个i值作为result[pos]摆放,作为第pos行的列

              for(int i = 0 ; i < n ; i++)

              {

                     bool isOk = true;

                     for(int j = 0 ; j < pos ; j++)

                     {

                            //如果在同一列,说明不符合,

                            if(result.at(j) == i)

                            {

                                   isOk = false;

                                   break;

                            }

                            //如果在同一对角线

                            if( abs( result.at(j) - i ) == abs(j - pos) )

                            {

                                   isOk = false;

                                   break;

                            }

                     }

                     if(isOk)

                     {

                            //说明符合摆放

                            result.push_back(i);

                            place(pos + 1 , result , totalResult , n);

                            //回溯

                            result.pop_back();

                     }

              }

       }

};

17

Word Ladder II

Given two words (beginWord and endWord), and a dictionary's word list, find all shortest transformation sequence(s) from beginWord to endWord, such that:

Only one letter can be changed at a time

Each transformed word must exist in the word list. Note that beginWord is not a transformed word.

For example,

Given:

beginWord = "hit"

endWord = "cog"

wordList = ["hot","dot","dog","lot","log","cog"]

Return

  [

    ["hit","hot","dot","dog","cog"],

    ["hit","hot","lot","log","cog"]

  ]

Note:

Return an empty list if there is no such transformation sequence.

All words have the same length.

All words contain only lowercase alphabetic characters.

You may assume no duplicates in the word list.

You may assume beginWord and endWord are non-empty and are not the same.

UPDATE (2017/1/20):

The wordList parameter had been changed to a list of strings (instead of a set of strings). Please reload the code definition to get the latest changes.

分析:这是程序员面试金典的一道题目。

给定两个字符串和一个单词列表,每次只能变换一个字符,求从初始字符串变换到结束字符串变换次数最少的

序列,如果序列有多个,请全部返回。如果不存在这样的变换序列,返回空列表。

 

输入:

hit(起始单词) cog(结束单词) 6(单词数组元素个数)

hot dot dog lot log cog

hit cog 5

hot dot dog lot log

hot dog 3

hot dog dot

输出:

hit hot dot dog cog, hit hot lot log cog

no result

hot dot dog

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/55271678

关键:

1 参考这位的解法:https://leetcode.com/problems/word-ladder-ii/?tab=Solutions

另外一种解法是找到一条最短的距离(通过bfs)minDis,并且在遍历过程中存储任意字符串到初始字符串的距离。

那么dfs搜索的时候采用当前结点的下一个结点遍历的时候需要满足下一个结点距离初始结点的距离=当前结点距离初始结点的距离+1

否则,就剪枝,不递归。

 

代码:

class Solution {

public:

       //获取某个单词的所有变位词

       vector<string> getOneEditWords(string& beginWord , vector<string>& wordList, unordered_map<string ,int>& words,unordered_map<string , vector<string> >& wordToNextWords)

       {

              //如果已经计算过了,就直接返回

              if(1 == wordToNextWords.count(beginWord))

              {

                     return  wordToNextWords[beginWord];

              }

              //求出开始单词的可经过一次变换的所有字符串

              vector<string> begNextWords;

              char temp;

              int len;

              if(!beginWord.empty())

              {

                     len = beginWord.length();

                     for(int i = 0; i < len ; i++)

                     {

                            temp = beginWord.at(i);

                            for(char ch = 'a' ;  ch <= 'z' ; ch++)

                            {

                                   if(temp == ch)

                                   {

                                          continue;

                                   }

                                   beginWord.at(i) = ch;

                                   //如果新的单词,在单词列表中存在,就加入结果集

                                   if(1 == words.count(beginWord))

                                   {

                                          begNextWords.push_back(beginWord);

                                   }

                                   beginWord.at(i) = temp;

                            }

                     }

                     wordToNextWords[beginWord] = begNextWords;

              }

              return begNextWords;

       }

 

       //获取所有字符串距离初始字符串的距离映射,以及最少变换次数

       int bfs(string& beginWord, string& endWord, vector<string>& wordList,

              unordered_map<string , vector<string>>& wordToNextWords ,

              unordered_map<string,int>& words, unordered_map<string,int>& strToDistance)

       {

              queue<string> queueWords;

              queueWords.push(beginWord);

              strToDistance[beginWord] = 0;//初始字符串到自身距离为0

              string word;

              string nextWord;

              vector<string> nextWords;

              int curDis = 0;

              int size;

              unordered_map<string ,int> visited;

              bool isFound = false;

              while(!queueWords.empty())

              {

                     word = queueWords.front();

                     queueWords.pop();

                     nextWords = getOneEditWords(word , wordList, words , wordToNextWords);

                     if(nextWords.empty())

                     {

                            continue;

                     }

                     //当前距离是从映射中获取的

                     curDis = -1;

                     if(strToDistance.find(word) != strToDistance.end())

                     {

                            curDis = strToDistance[word];

                     }

                     size = nextWords.size();

                     for(int i = 0 ; i < size ; i++)

                     {

                            nextWord = nextWords.at(i);

                            //当前结点必须是未访问过的,不行有些结点需要用到之前访问过的

                            //设置当前结点距离初始结点的距离

                            if(strToDistance.find(nextWord) == strToDistance.end())

                            {

                                   strToDistance[ nextWord] = curDis + 1;

                                   //判断当前字符串是否等于结束字符串,就直接退出

                                   if(nextWord == endWord)

                                   {

                                          isFound = true;

                                          //break;//不能直接退出,当前层的其他元素需要设置该距离

                                   }

                                   //将当前字符串压入到队列中

                                   queueWords.push(nextWord);

                            }

                     }

                     if(isFound)

                     {

                            break;

                     }

              }

              if(isFound)

              {

                     return curDis;//这个距离是倒数第二个结点距离初始结点的距离,实际距离=curDis+1

              }

              else

              {

                     return -1;

              }

       }

 

       void dfs(string& beginWord, string& endWord,unordered_map<string,int>& words, vector<string> result,

              vector< vector<string> >& results,unordered_map<string , vector<string>>& wordToNextWords ,

              vector<string>& wordList, unordered_map<string,int>& strToDistance)

       {

              result.push_back(beginWord);

              //找到了,返回

              if(beginWord == endWord)

              {

                     results.push_back(result);

                     return;

              }

              vector<string> nextWords = getOneEditWords(beginWord , wordList  , words , wordToNextWords);

              if(nextWords.empty())

              {

                     return ;

              }

              int size = nextWords.size();

              string nextWord;

              for(int i = 0 ; i < size ; i++)

              {

                     nextWord = nextWords.at(i);

                     //牛逼,剪枝,将下一个结点集合中所有与当前结点到初始结点距离+1 不等的全部过滤

                     if(strToDistance[nextWord] == strToDistance[beginWord] + 1)

                     {

                            dfs(nextWord, endWord, words , result , results, wordToNextWords, wordList , strToDistance);

                     }

              }

       }

 

    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {

        vector<vector<string>> results;

              if(beginWord.empty() || endWord.empty() || wordList.empty())

              {

                     return results;

              }

              vector<string> result;

              int size = wordList.size();

              unordered_map<string ,int> words;

              for(int i =  0; i < size ; i++)

              {

                     words[ wordList.at(i) ] = 1;

              }

              int minSequence = INT_MAX;

              unordered_map<string ,int> visited;

              unordered_map<string , vector<string>> wordToNextWords; //= getOneEditMap(beginWord , wordList , words);

              unordered_map<string , int> strToDistance;

              bfs(beginWord , endWord, wordList , wordToNextWords, words ,strToDistance);

              dfs(beginWord, endWord, words, result , results , wordToNextWords , wordList , strToDistance);

              return results;

    }

};

18

Palindrome Partitioning

Given a string s, partition s such that every substring of the partition is a palindrome.

Return all possible palindrome partitioning of s.

For example, given s = "aab",

Return

[

  ["aa","b"],

  ["a","a","b"]

]

分析:此题给定一个字符串,对该字符串进行划分,使得划分后的每个字符串都是一个回文字符串。

返回所有可能的划分。

输入:

aab

ab

aabb

输出:

aa b,a a b

a b

aabb, aa bb, a a bb, aa b b, a a b b

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/55509209

关键:

1

leecode解法:https://leetcode.com/problems/palindrome-partitioning/?tab=Solutions

采用回溯:采用一个循环,先对S[0..i]判断是否是回文串,如果是回文串,将该回文串压入结果;

并且对S[i+1...n]递归判断是否是回文串,当前S[0..i]下的递归对S[i+1..n]处理完毕之后,将

原先S[0...i]从结果集中删除,形成回溯。

这样通过递归当当前位置等于字符串长度时,得到一个划分结果压入结果集

 

代码:

class Solution {

public:

       //判断一个字符串是否是回文串

       bool isPalindrome(string& s ,int beg, int end)

       {

              //空字符串是回文的

              if(s.empty() )

              {

                     return true;

              }

              if(beg < 0 || end >= s.length() || beg > end)

              {

                     return false;

              }

              int low = beg;

              int high = end;

              bool isOk = true;

              while(low < high)

              {

                     if(s.at(low) != s.at(high))

                     {

                            isOk = false;

                            break;

                     }

                     low++;

                     high--;

              }

              //如果当前字符串是回文串,存入结果集

              return isOk;

       }

 

       void backTrace(int begin , string& s ,vector<string>& result , vector< vector<string> >& results)

       {

              if(begin < 0 || s.empty())

              {

                     return;

              }

              if(begin == s.length())

              {

                     results.push_back(result);

                     return;

              }

              int len = s.length();

              for(int i = begin ; i < len ;i++ )

              {

                     //判断当前是否是回文串,如果是,才继续递归对右边部分字符串进行递归处理

                     if(isPalindrome(s , begin , i))

                     {

                            result.push_back( s.substr(begin , i - begin + 1 ));

                            backTrace(i + 1 , s , result , results);

                            //弹出当前回文串,供生成其他有效的回文串划分

                            result.pop_back();

                     }

              }

       }

 

    vector<vector<string>> partition(string s) {

              vector<vector<string> > results;

              if(s.empty())

              {

                     return results;

              }

              vector<string> result;

              backTrace(0 , s ,result , results);

              return results;

    }

};

19

Palindrome Partitioning II

Given a string s, partition s such that every substring of the partition is a palindrome.

Return the minimum cuts needed for a palindrome partitioning of s.

For example, given s = "aab",

Return 1 since the palindrome partitioning ["aa","b"] could be produced using 1 cut.

输入:

aab

aabb

abcd

a

ababab

输出:

1

2

3

0

2

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/55511236

关键:

leecode解法:https://discuss.leetcode.com/topic/2048/my-dp-solution-explanation-and-code/4

采用动态规划

设定一个数组pal[i][j]表示s[i..j]是否是回文字符串

设d[i]表示s[i...n-1]的最小划分次数。

那么从后向前遍历,如果

s[i] == s[j] && (j - i < 2 || pal[i+1][j-1])

即两边字符相等,中间部分又是回文的,更新pal[i][j]=true,说明其实整个s[i...j]是回文的,剩余s[j+1...n-1]的最小次数

为d[j+1],总的划分次数为先划分s[i...j](肯定回文,划分占据一次),总的划分次数s[i..n-1]=min(d[i] , 1 + d[j+1])

牛逼。所求结果为d[0]

 

代码:

class Solution {

public:

       int minCut(string s) {

              if(s.empty())

              {

                     return 0;

              }

              int len = s.length();

              vector< vector<bool> > pal( len , vector<bool>(len , false) );

              vector<int> dp(len , 0);

              //从后向前遍历,因为求dp[i]依赖于于前面的dp[i+1],dp[i+2],...,dp[n-1]

              for(int i = len -1 ; i >= 0 ; i--)

              {

                     dp.at(i) = len - 1 - i;

                     for(int j = i ; j < len; j++)

                     {

                            //如果s[i...j]是回文串,开始计算s[i...n-1]的最小划分次数

                            if(s[i] == s[j] && (j - i < 2 || pal[i+1][j-1]))

                            {

                                   //需要设定当前pal[i][j]为true

                                   pal[i][j] = true;

                                   //如果s[i...n-1]是回文串,那么dp[i] = 0

                                   if(j == len-1)

                                   {

                                          dp[i] = 0;

                                   }

                                   else

                                   {

                                          //s[i...n-1]最小划分次数=先划分为s[i...j]的次数1次 + s[j+1...n-1]的划分次数dp[j+1] ,j 必须小于n

                                          dp.at(i) = min( dp.at(i) , dp.at(j+1) + 1 );

                                   }

                            }

                     }

              }

              return dp.at(0);

       }

};

20

Combination Sum III

Find all possible combinations of k numbers that add up to a number n, 

given that only numbers from 1 to 9 can be used and each combination should be a unique set of numbers.

Example 1:

Input: k = 3, n = 7

Output:

[[1,2,4]]

Example 2:

Input: k = 3, n = 9

Output:

[[1,2,6], [1,3,5], [2,3,4]]

分析:比较明显的回溯问题,需要从1到9中选择k个数字,使得累加和等于指定值,

其中每个数字只能使用一次

输入:

3 7

3 9

1 1

2 1

2 20

输出:

1 2 4

1 2 6,1 3 5,2 3 4

1

no result

no result

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/56494868

关键:

1 回溯算法的一般形式为

  backTrace(起始位置,...)

  {

    //判定找到正确答案,并保存

       if(找到答案)

       {

          保存结果

       }

       i从起始位置开始,遍历i

    if(i满足题目条件)

    {

       向结果中保存当前值

       backTrace(i+1,...)//回溯,并从下一个地方开始

       从结果中删除刚才保存的值

    }

  }

 

代码:

class Solution {

public:

       void backTrace(int beg ,int k ,int n, int currentSum, vector<int>& result , vector< vector<int> >& results )

       {

              //已经找到k个数字

              if((!result.empty()) && result.size() == k && currentSum == n)

              {

                     results.push_back(result);

                     return;

              }

              for(int i = beg ; i <= 9 ; i++ )

              {

                     //当前数加上累加和小于

                     if(i + currentSum <= n)

                     {

                            result.push_back(i);

                            backTrace(i+1 , k , n , i + currentSum , result , results);

                            result.pop_back();

                     }

              }

       }

 

    vector<vector<int>> combinationSum3(int k, int n) {

              vector< vector<int> > results;

        vector<int> result;

              backTrace(1 , k , n , 0 , result , results);

              return results;

    }

};

21

Perfect Squares

Given a positive integer n, find the least number of perfect square numbers

(for example, 1, 4, 9, 16, ...) which sum to n.

For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.

分析:给定一个整数,找出该整数可以用最少几个平方数累加得到,返回最少用的个数。

 

输入:

12

13

1

输出:

3

2

1

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/57406709

关键:

1 回溯问题,回溯。找到一个解就退出.不行,发现从最大开始找,找到的解不一定是最优解,则回溯需要把所有解计算出来

寻找最少的

 

代码:

class Solution {

public:

       //这样,只要n变成0,就算找到了

       void backTrace(int n , int currentTimes  , int& minTimes)

       {

              //剪枝,如果当前次数 >= 最小次数,直接不做处理

              if(currentTimes >= minTimes)

              {

                     return;

              }

              //如果当前选中的平方数等于剩余数值,说明找到

              if(0 == n)

              {

                     //当前需要的次数小于最少次数,则更新

                     if(currentTimes < minTimes)

                     {

                            minTimes = currentTimes;

                     }

                     return;

              }

              //对于n挑选剩余可选择的平方数

              for(int i = (int)sqrt(n) ; i >= 1 ; i-- )

              {

                     //继续尝试

                     backTrace(n - (i * i) , currentTimes + 1 , minTimes);

                     //如果尝试成功,不能直接返回,需要继续回溯处理

              }

       }

 

    int numSquares(int n) {

              int currentTimes = 0;

              int minTimes = INT_MAX;

              backTrace( n , currentTimes , minTimes);

              return minTimes;

    }

};

22

如何求正整数n所有可能的整数组合

给定一个正整数n,求解出所有和为n的整数组合,要求组合按照递增方式展示,而且唯一。

例如:

4=1+1+1+1、1+1+2、1+3、2+2、4(4+0)

Python程序员面试算法宝典

https://blog.csdn.net/qingyuanluofeng/article/details/96827812

关键:

1 书上解法

可以用

comb(sums, result, count)

来做递归,sums表示剩余待表达的整数,result是结果列表,count

    # 确定组合中下一个取值的起始范围,确保组合中下一个数字一定不会小于前一个数字

    i = 1 if 0 == count else result[count-1]

    # i最多可以取到sums

    while i <= sums:

        # 设置变量

        result[count] = i

        count += 1

        # 递归

        comb(sums - i, result, count)

        # 清除变量

        count -= 1

        # 找下一个数字做为组合中的数字

        i += 1

2 没有想到

回溯的一种算法是采用累减的方式,减为0,则表示成功。

另外的确需要尝试在一个位置上摆放多个元素,

每次摆放完成,计算下一次递归结果,然后清除当前摆放

然后选择下一次摆放的元素

 

代码:

'''

sums:求和为sums的所有组合

result:结果数组

count:组合中数字的个数

'''

def comb(sums, result, count):

    # 如果成功,就打印;这里成功是指sums为0

    if sums < 0:

        return

    if 0 == sums:

        # 这里只有从0到count-1位才是结果

        print "满足条件组合"

        printResult(result, count)

        # 找到结果需要返回

        return

 

    # print "当前组合"

    # if count >= 1:

    #     printResult(result, count)

 

    # 确定组合中下一个取值的起始范围,确保组合中下一个数字一定不会小于前一个数字

    i = 1 if 0 == count else result[count-1]

    print "################ i={i} count={count}".format(

        i=i,

        count=count

    )

 

    # i最多可以取到sums

    while i <= sums:

        # 设置变量

        result[count] = i

        count += 1

        # 递归

        comb(sums - i, result, count)

        # 清除变量

        count -= 1

        # 找下一个数字做为组合中的数字

        i += 1

 

def process():

    sums = 4

    result = [None for i in range(sums)]

    count = 0

    result = comb(sums, result, count)

    print result

23

回溯法

排列生成和子集枚举的两种方法:1递归,2遍历

遍历:优点:简单,缺点:增大枚举量,检验所有解

回溯法:含义 :递归时,将生成+检查过程结合

        适合:问题分成步骤,步骤采用不太多选择

回溯算法=递归枚举算法:分成若干步骤递归,若某一步无解,返回上一级调用,称为回溯。

八皇后问题:

在棋盘上放置8个皇后,使得她们互不攻击,此时每个皇后的攻击范围为同行同列和同对角线,要求找出所有解。

一个可行解

Q                                                    

                            Q                        

                                                 Q

                                   Q                 

              Q                                      

                                          Q          

       Q                                             

                     Q                               

算法竞赛入门经典

https://blog.csdn.net/qingyuanluofeng/article/details/47746897

八皇后问题,参见题目15 N-Queens

 

24

困难的串

如果一个字符串包含两个相邻的重复子串,则称它是“容易的串”,其他串成为“困难的串”。例如:BB,ABCDACABCAB,ABCDABCD都是容易的,而D、DC、ABDAB、CBABCBA

都是困难的。

输入正整数n和L,输出由前L个字符组成的、字典序第n个小的困难的串。例如,当L=3时,前7个困难的串分别为:A、AB、ABA、ABAC、ABACA、ABACAB、ABACABA。

输入保证答案不超过80个字符

输入:

7 3

30 3

输出:

ABACA BA

ABACA BCACB ABCAB ACABC ACBAC ABA

算法竞赛入门经典

https://blog.csdn.net/qingyuanluofeng/article/details/47746907

关键:

1 if(cnt++ == n)//用cnt累计字典序第n个困难串

2 printf("%c",iArr[i] + 'A');//由于iArr[i]是整数,加上字符'A'之后,确保字符是从A开始的

3 return 0;//这里使用返回值,是为后面判断是否已经达到跳出条件做准备

4 for(int m = 0; m < L; m++)//L表示困难串的长度

5 for(int j = 1; 2*j <= pos+1; j++)//这里是对2*j长度的当前串的后缀进行判断

6                         for(int k = 0 ; k < j ; k++)//确保连续的能被判断到

                            {

                                   if(iArr[pos-k] != iArr[pos-k-j])

                                   {

                                          isEqual = false;//不是重复相等的情况

                                          break;

                                   }

                            }

7                  if(isDiff)//如果是困难的串,递归调用下一趟

                     {

                            if(!dfs(pos+1,n,L,iArr))//如果已经找到字典序第n小的困难串,输出

                            {

                                   return 0;

                            }

                     }

 

代码:

int dfs(int pos,int n,int L,int* iArr)

{

       if(cnt++ == n)//用cnt累计字典序第n个困难串

       {

              for(int i = 0 ; i < pos ; i++)

              {

                     printf("%c",iArr[i] + 'A');//由于iArr[i]是整数,加上字符'A'之后,确保字符是从A开始的

              }

              putchar('\n');

              return 0;//这里使用返回值,是为后面判断是否已经达到跳出条件做准备

       }

       else

       {

              for(int m = 0; m < L; m++)//L表示困难串的长度

              {

                     iArr[pos] = m;

                     bool isDiff = true;

                     for(int j = 1; 2*j <= pos+1; j++)//这里是对2*j长度的当前串的后缀进行判断

                     {

                            bool isEqual = true;

                            for(int k = 0 ; k < j ; k++)//确保连续的能被判断到

                            {

                                   if(iArr[pos-k] != iArr[pos-k-j])

                                   {

                                          isEqual = false;//不是重复相等的情况

                                          break;

                                   }

                            }

                            if(isEqual)//如果相等,说明是重复的,说明是简单串,不符合要求

                            {

                                   isDiff = false;

                                   break;

                            }

                     }

                     if(isDiff)//如果是困难的串,递归调用下一趟

                     {

                            if(!dfs(pos+1,n,L,iArr))//如果已经找到字典序第n小的困难串,输出

                            {

                                   return 0;

                            }

                     }

              }

       }

       return 1;//如果没有找到就输出

}

25

埃及分数

使用单位分数的和(如1/a,a是自然数)表示一切有理数。例如2/3 = 1/2 + 1/6,但不允许2/3 = 1/3 + 1/3,因为在加数中不允许有相同的。

对于一个分数a/b,表示方法有很多种,其中加数少的比加数多的好,如果加数个数相同,则最小的分数越大越好。例如,19/45 = 1/5 + 1/6 + 1/18是最优方案。

输入整数a,b(0<a<b<1000),试编程计算最佳表达式。

输入:

19 45

输出:

19/45 = 1/5 + 1/6 + 1/18

算法竞赛入门经典

https://blog.csdn.net/qingyuanluofeng/article/details/47746939

关键:

1 采用迭代加深搜索:从小到大枚举深度上线d,每次只考虑深度不超过d的节点。只要解的深度有限,就能枚举到。

 

代码:

stack<int> s; 

stack<int> sbest; //存放最优解 

int flag;   //标志当前是否得到可行解 

 

int gcd(int m,int n)    //求最大公约数 

    if(!n) return m; 

    else return gcd(n,m%n); 

void minus(int &a,int &b,int c,int d)   //分数相减,结果为a/b 

    int m,n,k; 

    m = a*d - b*c; 

    n = b*d; 

    k = gcd(m,n);//这个起到的作用是化简a/b,消去最大公约数 

    a = m/k;  

    b = n/k; 

 

int eq(int a,int b,int c,int d)     //判断两个分数是否相等 

    int m = a*d - b*c; 

    if(m > 0) 

        return 1; 

    else if( m == 0) 

        return 0; 

    else return -1; 

 

void stackCopy(stack<int>& s1,stack<int> s2)    //将s2拷贝到s1中 

    stack<int> buf;       //中转站buf 

    while(!s1.empty())  //将s1清空 

        s1.pop(); 

    while(!s2.empty())   

    { 

        buf.push(s2.top()); 

        s2.pop(); 

    } 

    while(!buf.empty()) 

    { 

        s1.push(buf.top()); 

        buf.pop(); 

    } 

 

void  dfs(int a,int b,int start ,int depth)  

    int n; 

    int c,d;

       int iEqual2,iEqual3;

    if(depth == 0) 

        return ; 

    else 

    { 

        for(n = start;;n++) 

        { 

                     int iEqual = eq(a,b,1,n);

            if(iEqual == 0) 

            { 

                s.push(n); 

                if(!flag ||sbest.top() > s.top())    //如果当前没有解或者有更好的解 

                    stackCopy(sbest,s); 

                flag = 1; 

                s.pop(); 

                return ; 

            } 

            else if(iEqual > 0) 

            { 

                if((iEqual3 = eq(a,b,depth,n)) >= 0)     //  a/b > 1/n * depth  ?确保能够更新depth层数

                    return ; 

                s.push(n);//?  说明走到这里的分支是1/n< a/b < 1/n*depth ,这里可以加n放入进去,找到了一个范围

                c=a;d=b; 

                minus(c,d,1,n);//a/b-1/n  消去前面已经加上的

                dfs(c,d,n+1,depth-1);//这里由于做了减法,使得a/b减小了,在第一层继续寻找答案

                s.pop(); //为什么要弹出

            } 

            else 

                continue; 

        } 

    } 

 

int main() 

    int a,b; 

    stack<int> buf; 

 

    scanf("%d%d",&a,&b); 

    for(int depth = 1;;depth++) 

    { 

        flag = 0; 

        while(!sbest.empty()) 

            sbest.pop(); 

        dfs(a,b,2,depth); 

        if(flag) 

            break; 

    } 

    while(!sbest.empty()) 

    { 

        buf.push(sbest.top()); 

        sbest.pop(); 

    } 

    while(!buf.empty()) 

    { 

        printf("%d ",buf.top()); 

        buf.pop(); 

    } 

    scanf("%d",&a); 

    return 0; 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

参考:
[1]计算机考研--机试指南,王道论坛 组编
[2]剑指offer
[3]算法设计与分析
[4]编程之美
[5]程序员面试金典
[6]leecode
[7]Python
程序员面试算法宝典
[8]刘汝佳算法竞赛入门经典
[9]算法导论
[10]编程珠玑

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值