【算法总结】排列组合问题与回溯法
学以致用
DX同学遇到了一个实际的问题,一共90个数找到N个数的和是一个确定的数,使用回溯法解决了这个90个数的组合问题,相当有成就感!感叹变成的力量太强大了! 工程在这里
排列组合问题与回溯法
排列组合问题与回溯法核心
排列组合问题都是枚举类型的问题,这类问题的关键是采取怎样的枚机策略保证
- 完整性:不能遗漏任何一种情况
- 准确性:枚举出的一种情况是正确的
- 效率:一般来说不会有太大的效率差异,因为枚举本身输出情况就很多,比如n!中情况
其中最常用的是回溯法(英文是Backtracking),这篇演算法笔记讲解的比较清晰,基本是依循《算法设计手册》中的回溯法一章整理的。 回溯法的是来解决多维向量的枚举问题,其核心:
- 递归的枚举每个维度的值
- 既然是递归,那就用终止条件。一般的终止条件是找到了满足条件的解,或者已经超出了解的范围,无需再递归下去。
- 找到某一维的候选值。解排列组合的重中之重。常用的技巧是使用标志位来记录某一维是否可用。
- make_move和unmake_move在对每个候选值递归调用前后发生,这里的unmake_move非常的重要,保证了回溯法的正确!
排列算法Permute举例
一.排列不重复的元素
Given a collection of numbers, return all possible permutations.
} vector<vector<int> > permute(vector<int> &num) { vector<int> tmp; vector<vector<int>> retVec; int length = (int)num.size(); vector<bool>isVisited = vector<bool>(length,false); permuteHelper(retVec, num, tmp, length,isVisited); return retVec; }
上述使用backtrack的想法是按位选取,第一位有n个选择,第一位确认后,第二位有(n-1)个选择,依次类推,并且使用了深度优先遍历,正如算法设计手册上说的一样,深度优先遍历能够减少空间的使用,空间的减少带来的问题是我们每次递归后,要做unmake操作,如tmp.pop_back();和isVisited[i] = false;
难点是使用isVisited标志某一个是否使用过了,如果使用过则不再使用
二、排列包含重复元素的数组
Given a collection of numbers that might contain duplicates, return all possible unique permutations.
与上一道题非常相似,两点变化:
- 数组先排序
- 使用lastNumber来记录上一个选择的候选者,如果本候选者和上一个候选者一样,那么跳过本候选者。
- (参考一)给出的解法虽然可以,但是不能做n选k的排列问题,只能处理n选n得排列问题
三、计算下一个数
Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.
使用字典序法:
对给定的字符集中的字符规定一个先后关系,在此基础上按照顺序依次产生每个排列。例如字符集{1,2,3},按字典序生成的全排列是:123,132,213,231,312,321 例如:839647521的下一个排列
- Step1:从最右开始,找到第一个比右边小的数字4(因为4<7, 而7>5>2>1),再从最右开始,找到4右边比4大的数字5(因为4>2>1而4<5)。4、5也即从右往左找到的第一个对严格的升序对。
- Step2:交换4、5。
- Step3:倒置5右边的7421为1247.即得到下一个排列839651247.
该方法支持数据重复,且在C++STL中被采用。因为需要交换的次数较多,字典序法的效率较低。
LeetCode上的相关问题
- Permutations
- Permutations II
- Next Permutation
- Permutation Sequence
- Subsets
- Subsets II
- Combinations
- Combination Sum
- Combination Sum II