leetcode之深搜递归回溯类之排列与组合类-----77/39/40/216/317 组合 78/90/368 子排列 22/79/93/131 典型递归回溯 46/47 全排列
2017-10-03 12:19
906 查看
这部分主要关于:递归的深度搜索,回溯剪枝减少不必要递归
递归深搜回溯题:
1、递归路线是什么
2、需要获取的结果是什么
3、根据题意中的规定,可以在什么时候就停止继续递归
1、OJ77 combinations
给定正整数n和k,找出在[1-n]中,k个数的全部组合;
如n=2,k=1,则结果为[[1], [2]];
如n=2,k=2,,结果为[[1,2]];
如n=3,k=2,结果为[[1,2], [1,3], [2,3]]
直观:1和后边的数组成k个数的组合;2和后边的数组成k个数的组合,3和后边的数组成k个数的组合......完全是后向,不需要找相同组合顺序不同的,如[1, 2, 3]是正确的,还需要把[2,1,3]找出来
这类题意的题,特点是:
1、递归路线是:从数组头向尾递归,不需要总从头遍历,收集当前数后,遍历接下来的数直到都遍历完
2、获取结果是:收集的数达到k个,就是一个结果
3、停止条件是:当前收集达到k个数了记录结果,记录后返回上一层;
OJ77代码:
2、OJ39 combination sum
给定一个数组,比如[2,3,6,7],给定一个整数target比如7,找到数组中的可重复组合,和等于target,比如这个例子结果为:[[7], [2,2,3]]
1、递归路线是:从数据头到尾递归,注意因为元素可以重复出现,所以当前层向下递归时,不可以后移索引;另外,需要返回的结果不可以重复,即如一个结果是[2,2,3],不需要它的其他排列组合如[2,3,2]、[3,2,2]这样,所以,当前层向下一层递归时,索引不变即可,不需要每次从头再遍历
2、获取结果是:数据集之和为target的
3、停止条件是:题目提出了给定的是正数(positive),所以意味着如果当前数据集的和已经大于等于target了,那么就不用继续向下递归;基于此,原始数组应该提前排序,这样利于最大化的减少多余的递归情况
OJ39代码:
3、OJ40 combination sum II
给定一个数组如[10, 1, 2, 7, 6, 1, 5],给定一个整数target,求数组中的组合,和为target的;如target = 8,则结果为:[[1, 7], [1 ,1, 6], [1, 2, 5], [2, 6]],要求每个数只能出现一次,比如本例中数组有两个1,都可以和7组合成8,但是结果中只能有一个为[1, 7]的结果;
1、递归路线为:从数据头到尾递归,因为结果集中的数不可重复,且原始数据内有重复数据,所以需要注意两个事情:
1、向下递归时,索引向后移
2、对于数组中重复的数,不能做同样的递归处理,需要界定数组中重复的数,解决方法是:
原数组排好序,向下递归前先看看,是不是当前数,前边已经有了
2、获取结果是:数据集之和为target的;
3、停止条件是:和OJ39一样里边都是正数,所以停止条件也是,当前数据集之和大于等于target时,进而也需要原数据排好序
OJ40代码:
4、OJ216 combination sum III
在[1-9]范围内,给定整数K和n,求K个数的组合,且和为n的;
此题是OJ77变种,OJ77题是要在1-N范围内,找到K个数的全部组合;本题是在1-9的范围内,找到K个数且和为N的全部组合;
1、递归路线是:从数据头到尾递归,不可能重复,所以每次向下递归时索引后移;
2、获取结果是:数据集个数达到K,且和为N的;
3、停止条件是:两个停止条件,符合任意一个都要停止:1、数据集个数达到K;2、原始数据从1-9都是正数,所以数据集之和大于等于N时,也要停止向下递归;
OJ216代码:
5、OJ377 combination sum IV
给定一个数组如[1,2,3],给定整数target如target=4,找出数组中全部的和为target组合,元素可以重复(如组成4可以是4个首元素1),全部的组合顺序(如组成4可以是[1,1,2]和[2,1,1])
元素可以重复:意味着每次向下递归时,索引不可以后移;同时要求全部的组合顺序,说明当前层的数,还要和它前面的数组合,而不是完全向后的递归,也就是每次递归过程都需要是从头到尾;
递归路线是:从数据头到尾递归,每次都从头再遍历;
获取结果是:数据集和为target的;
停止条件是:正数,所以数据集和大于等于target的同样被剪枝;如果是负数则无这个剪枝条件
按这种办法的OJ377代码:
该方法可以得到正确结果,但无法AC会TLE超时,因为每次都从头遍历数组,所以需要更高级的剪枝;
联想跳台阶的优化是什么?
一次可以跳1或2个台阶,N个台阶的走法是F(N) = F(N - 1) + F(N - 2);
优化方法是:
1、已知走1个方法是1种,走2个是两种,那么就知道了走3个是F(1) + F(2)是3种,此时把F(3) = 3记下来;
2、然后计算走4种的方法时,需要计算F(4) = F(3) + F(2),这时不需要递归计算F(3),因为F(3)已经记下来了,直接得到F(4) = 5;
3、计算第5、6、7、.....、N时,每次都可以使用上次结果;
那么对于本题,求组合的方法数,且组合可以重复,而且要求每个数,与包括它自己在内的全部数的组合情况,则本题相当于:
跳target = 4个台阶,每次可以跳[1,2,3]个台阶,有多少种跳法?
那么本题的优化方法是:如对于[1,2,3],则F(target) = F(target - 1) + F(target - 2) + F(target - 3);递归过程中会依次求出F(4)、F(5)、.....、F(target)
经验:需要从头到尾遍历的题,主动思考是不是跳台阶式问题,能否往跳台阶的优化思路去靠;
OJ377优化后可AC代码:
6、OJ78 subsets
子集,给定无重复元素的数组[1,2,3],求全部的子排列,结果为:[[], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3]]
递归路线是:从数据头到尾递归
获取结果是:都要
停止条件是:直到都遍历完
OJ78代码:
7、OJ90 subsets II
和OJ78唯一区别是,原数组内可能有重复元素,而结果集中不可以有相同元素组成的结果,比如[1,1,7]的结果里不可以有两个[1,7]
和OJ40的套路一模一样,原数组先排好序,然后查找当前数前边的数是否和当前数一样,一样就说明已经有结果了,不要再计算出同样的结果
OJ90代码:
8、OJ368 largest divisible subset
如给定[3,4,16,8],找出最长的能够连续被整除的组合,如本例的结果是:[4,8,16]
递归路线是:如16可以整除8,8可以整除4,那么16自然可以整除4,所以向后递归时:1、原数组排好序,保证大数在后边免于向前遍历;2、向后递归时索引要后移
获取结果是:可以整除的组合中最长的;
停止条件是:除递归到头外,还有一个重要的剪枝条件,当前已经计算出的最长组合的长度,如果已经大于“当前组合长度 + 后边剩余的全部元素个数”,如[1,2,4,8],遍历1时已经得出最长组合为[1,2,4,8]长度为4,那么遍历2时会发现即便后边都能整除也就是最长长度也不过是3,那么对于2的遍历立即可以停止;
经验:因数据大小导致需要向前遍历的题,这种情况下必须先排好序,规避掉这种需求向前遍历的情况
OJ368代码:
9、OJ22 generate parentheses
生成括号,给定K,有多少种括号组合形式,要求:不可以出现右括号领头,如给定3,结果为:"((()))"、"(()())"、"(())()"、"()(())"、"()()()"
递归路线是:从0-K;
停止条件是:左括号和右括号个数都达到K时;
本题注意递归生成时的顺序,必须是,左括号要领先于右括号,设置两个数分别在递归过程中表示当前左括号和右括号的个数,判断依据是:
1、当都为0即最开始的时候,必须生成左括号;
2、当左括号和右括号个数都达到K时,结束了;
3、如果左括号个数,大于右括号个数,那么可以生成左括号,还可以生成右括号,两个递归流程;
4、如果左括号和右括号个数相等,必须生成左括号;
5、左括号肯定率先达到K,这时肯定必须生成右括号;
OJ22代码:
10、OJ79 word search
给定一个二维数组,里边有各种英文字符,然后查找一个英文字符串,判断二维数组里是否存在,可以是向上向下向左向右,不可以重复,如:
word =
-> returns
word =
-> returns
word =
-> returns
本题事实上和二叉树里的"子树问题"是一个问题,每一个字符都可以作为起始点做深度遍历,显然需要先判断出和word的首字符相同的再进入深搜;
另外进入深搜后,因为不许重复,所以需要给每个已经走过且判断复合word了的字符做"已使用"的标记,避免重复使用
OJ79代码:
11、OJ46 permutations
无重复元素的全排列
OJ46代码:
12、OJ47 permutation II
带重复元素的全排列。
注意,这道题用OJ40、OJ90的避免去重方法不适用,因为当递归到当前数时,向前发现有相同的数,但怎么界定是第一次执行的还是重复的呢?
带重复的全排列的方法是,原数组排序,保证重复元素相邻,然后为每一个元素标记上"是否已使用",遍历时永远从头遍历到尾,如果当前元素和之前元素相同,但之前元素标记未用过,那么说明本次遍历属于重复的;
如[1,1],当第一次从第一个1遍历到第二个1时,得到结果[1,1]并保存;
当第二次从第二个1入结果集,然后再递归遍历时,会发现第一个1的标志位为"未使用",什么当前是跨过了第1个1
OJ47代码:
另外,关于OJ31(当前全排列组合的下一个,next permutation)和OJ60(第K个全排列,permutation sequence),和递归回溯并无关,而是基于全排列知识的运用。
递归深搜回溯题:
1、递归路线是什么
2、需要获取的结果是什么
3、根据题意中的规定,可以在什么时候就停止继续递归
1、OJ77 combinations
给定正整数n和k,找出在[1-n]中,k个数的全部组合;
如n=2,k=1,则结果为[[1], [2]];
如n=2,k=2,,结果为[[1,2]];
如n=3,k=2,结果为[[1,2], [1,3], [2,3]]
直观:1和后边的数组成k个数的组合;2和后边的数组成k个数的组合,3和后边的数组成k个数的组合......完全是后向,不需要找相同组合顺序不同的,如[1, 2, 3]是正确的,还需要把[2,1,3]找出来
这类题意的题,特点是:
1、递归路线是:从数组头向尾递归,不需要总从头遍历,收集当前数后,遍历接下来的数直到都遍历完
2、获取结果是:收集的数达到k个,就是一个结果
3、停止条件是:当前收集达到k个数了记录结果,记录后返回上一层;
OJ77代码:
class Solution { public: void helper (int cur, const int n, const int k, vector<int> r, vector<vector<int>> &res) { //递归停止条件, 当前已收集到K个数, 收集当前结果 if (r.size() == k) { res.push_back(r); return; } for (int i = cur; i <= n; i++) { //在当前层就判断, 只在当前还未达到K个数时向下递归, 否则直接就停止 if (r.size() < k) { r.push_back(i); //直接往下一个数 helper(i + 1, n, k, r, res); r.pop_back(); } } } vector<vector<int>> combine(int n, int k) { vector<vector<int>> res; if (n <= 0) { return res; } vector<int> r; helper(1, n, k, r, res); return res; } };
2、OJ39 combination sum
给定一个数组,比如[2,3,6,7],给定一个整数target比如7,找到数组中的可重复组合,和等于target,比如这个例子结果为:[[7], [2,2,3]]
1、递归路线是:从数据头到尾递归,注意因为元素可以重复出现,所以当前层向下递归时,不可以后移索引;另外,需要返回的结果不可以重复,即如一个结果是[2,2,3],不需要它的其他排列组合如[2,3,2]、[3,2,2]这样,所以,当前层向下一层递归时,索引不变即可,不需要每次从头再遍历
2、获取结果是:数据集之和为target的
3、停止条件是:题目提出了给定的是正数(positive),所以意味着如果当前数据集的和已经大于等于target了,那么就不用继续向下递归;基于此,原始数组应该提前排序,这样利于最大化的减少多余的递归情况
OJ39代码:
class Solution { public: void helper (int idx, vector<int> cur, const int target, const vector<int>& candidates, vector<vector<int>> &res) { //计算当前和 int sum = 0; for (auto i: cur) { sum += i; } //已知candidates里都是正数, 如果当前和已经大于等于target, 那么后边的递归就不需要(肯定比target大) //所以candidates提前排好序, 能最多的过滤掉大于等于target的case if (sum > target) { return; } else if (sum == target) { res.push_back(cur); return; } for (int i = idx; i < candidates.size(); i++) { cur.push_back(candidates[i]); //数组中元素可以重复出现, 不能往下一个数 helper(i, cur, target, candidates, res); cur.pop_back(); } } vector<vector<int>> combinationSum(vector<int>& candidates, int target) { vector<vector<int>> res; if (candidates.empty()) { return res; } //提前排好序, 有利于最多的减少多余递归 sort(candidates.begin(), candidates.end()); vector<int> cur; helper(0, cur, target, candidates, res); return res; } };
3、OJ40 combination sum II
给定一个数组如[10, 1, 2, 7, 6, 1, 5],给定一个整数target,求数组中的组合,和为target的;如target = 8,则结果为:[[1, 7], [1 ,1, 6], [1, 2, 5], [2, 6]],要求每个数只能出现一次,比如本例中数组有两个1,都可以和7组合成8,但是结果中只能有一个为[1, 7]的结果;
1、递归路线为:从数据头到尾递归,因为结果集中的数不可重复,且原始数据内有重复数据,所以需要注意两个事情:
1、向下递归时,索引向后移
2、对于数组中重复的数,不能做同样的递归处理,需要界定数组中重复的数,解决方法是:
原数组排好序,向下递归前先看看,是不是当前数,前边已经有了
2、获取结果是:数据集之和为target的;
3、停止条件是:和OJ39一样里边都是正数,所以停止条件也是,当前数据集之和大于等于target时,进而也需要原数据排好序
OJ40代码:
class Solution { public: void helper (int idx, const int target, vector<int> cur, const vector<int> &candidates, vector<vector<int>> &res) { int sum = 0; for (auto i: cur) { sum += i; } //因为原数据也都是正数, 所以也是同样的剪枝条件 if (sum > target) { return; } else if (sum == target) { res.push_back(cur); return; } for (int i = idx; i < candidates.size(); i++) { //排序后, 相同数会相邻, 题意要求结果不可以有重复, 所以对于前边已经遍历过的索引对应的数, 不要再遍历 if (i > idx && candidates[i] == candidates[i - 1]) { continue; } else { cur.push_back(candidates[i]); //不可以自重复己加自己, 所以索引后移 helper(i + 1, target, cur, candidates, res); cur.pop_back(); } } } vector<vector<int>> combinationSum2(vector<int>& candidates, int target) { vector<vector<int>> res; if (candidates.empty()) { return res; } sort(candidates.begin(), candidates.end()); vector<int> cur; helper(0, target, cur, candidates, res); return res; } };
4、OJ216 combination sum III
在[1-9]范围内,给定整数K和n,求K个数的组合,且和为n的;
此题是OJ77变种,OJ77题是要在1-N范围内,找到K个数的全部组合;本题是在1-9的范围内,找到K个数且和为N的全部组合;
1、递归路线是:从数据头到尾递归,不可能重复,所以每次向下递归时索引后移;
2、获取结果是:数据集个数达到K,且和为N的;
3、停止条件是:两个停止条件,符合任意一个都要停止:1、数据集个数达到K;2、原始数据从1-9都是正数,所以数据集之和大于等于N时,也要停止向下递归;
OJ216代码:
class Solution { public: void helper (int st, int k, int n, vector<int> cur, vector<vector<int>> &res) { //计算当前和 int sum = 0; for (auto i: cur) { sum += i; } //1-9都是正数, 所以做同样的剪枝 if (sum > n) { return; } else if (sum == n && !k) { res.push_back(cur); return; } for (int i = st; i <= 9; i++) { //向下递归时, 根据数据集当前个数就做剪枝, 确保不会出现大于K个数的数据集向下继续计算 if (k) { cur.push_back(i); helper(i + 1, k - 1, n, cur, res); cur.pop_back(); } } } vector<vector<int>> combinationSum3(int k, int n) { vector<vector<int>> res; vector<int> cur; helper(1, k, n, cur, res); return res; } };
5、OJ377 combination sum IV
给定一个数组如[1,2,3],给定整数target如target=4,找出数组中全部的和为target组合,元素可以重复(如组成4可以是4个首元素1),全部的组合顺序(如组成4可以是[1,1,2]和[2,1,1])
元素可以重复:意味着每次向下递归时,索引不可以后移;同时要求全部的组合顺序,说明当前层的数,还要和它前面的数组合,而不是完全向后的递归,也就是每次递归过程都需要是从头到尾;
递归路线是:从数据头到尾递归,每次都从头再遍历;
获取结果是:数据集和为target的;
停止条件是:正数,所以数据集和大于等于target的同样被剪枝;如果是负数则无这个剪枝条件
按这种办法的OJ377代码:
class Solution { public: void helper (int cur, const vector<int> &nums, const int target, int &res) { if (cur == target) { ++res; return; } for (auto i: nums) { cur += i; //剪枝放在递归前 if (cur <= target) { helper(cur, nums, target, res); } cur -= i; } } int combinationSum4(vector<int>& nums, int target) { int res = 0; if (nums.empty()) { return res; } sort(nums.begin(), nums.end()); helper(0, nums, target, res); return res; } };
该方法可以得到正确结果,但无法AC会TLE超时,因为每次都从头遍历数组,所以需要更高级的剪枝;
联想跳台阶的优化是什么?
一次可以跳1或2个台阶,N个台阶的走法是F(N) = F(N - 1) + F(N - 2);
优化方法是:
1、已知走1个方法是1种,走2个是两种,那么就知道了走3个是F(1) + F(2)是3种,此时把F(3) = 3记下来;
2、然后计算走4种的方法时,需要计算F(4) = F(3) + F(2),这时不需要递归计算F(3),因为F(3)已经记下来了,直接得到F(4) = 5;
3、计算第5、6、7、.....、N时,每次都可以使用上次结果;
那么对于本题,求组合的方法数,且组合可以重复,而且要求每个数,与包括它自己在内的全部数的组合情况,则本题相当于:
跳target = 4个台阶,每次可以跳[1,2,3]个台阶,有多少种跳法?
那么本题的优化方法是:如对于[1,2,3],则F(target) = F(target - 1) + F(target - 2) + F(target - 3);递归过程中会依次求出F(4)、F(5)、.....、F(target)
经验:需要从头到尾遍历的题,主动思考是不是跳台阶式问题,能否往跳台阶的优化思路去靠;
OJ377优化后可AC代码:
class Solution { public: int helper (int target, const vector<int> &nums, unordered_map<int, int> &hmap) { if (!target) { return 1; } else if (target > 0) { //直接使用已经跑过的更深层的结果 if (hmap.find(target) != hmap.end()) { return hmap[target]; } } else { return 0; } //求F(target) = sum{F(target - nums[i])}, 相当于跳台阶的F(N) = F(N - 1) + F(N - 2) int count = 0; for (auto i: nums) { count += helper(target - i, nums, hmap); } //计算完当前层的target的方法后记录下来, 后面就可以直接用 hmap[target] = count; return count; } int combinationSum4(vector<int>& nums, int target) { if (nums.empty()) { return 0; } unordered_map<int, int> hmap; return helper(target, nums, hmap); } };
6、OJ78 subsets
子集,给定无重复元素的数组[1,2,3],求全部的子排列,结果为:[[], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3]]
递归路线是:从数据头到尾递归
获取结果是:都要
停止条件是:直到都遍历完
OJ78代码:
class Solution { public: void helper (int idx, vector<int> cur, const vector<int> &nums, vector<vector<int>> &res) { if (idx <= nums.size()) { res.push_back(cur); for (int i = idx; i < nums.size(); i++) { cur.push_back(nums[i]); helper(i + 1, cur, nums, res); cur.pop_back(); } } } vector<vector<int>> subsets(vector<int>& nums) { vector<vector<int>> res = {}; if (nums.empty()) { return res; } vector<int> cur; helper(0, cur, nums, res); return res; } };
7、OJ90 subsets II
和OJ78唯一区别是,原数组内可能有重复元素,而结果集中不可以有相同元素组成的结果,比如[1,1,7]的结果里不可以有两个[1,7]
和OJ40的套路一模一样,原数组先排好序,然后查找当前数前边的数是否和当前数一样,一样就说明已经有结果了,不要再计算出同样的结果
OJ90代码:
class Solution { public: void helper (int idx, vector<int> cur, const vector<int> &nums, vector<vector<int>> &res) { if (idx <= nums.size()) { res.push_back(cur); for (int i = idx; i < nums.size(); i++) { if (i > idx && nums[i] == nums[i - 1]) { continue; } else { cur.push_back(nums[i]); helper(i + 1, cur, nums, res); cur.pop_back(); } } } } vector<vector<int>> subsetsWithDup(vector<int>& nums) { vector<vector<int>> res = {}; if (nums.empty()) { return res; } sort(nums.begin(), nums.end()); vector<int> cur; helper(0, cur, nums, res); return res; } };
8、OJ368 largest divisible subset
如给定[3,4,16,8],找出最长的能够连续被整除的组合,如本例的结果是:[4,8,16]
递归路线是:如16可以整除8,8可以整除4,那么16自然可以整除4,所以向后递归时:1、原数组排好序,保证大数在后边免于向前遍历;2、向后递归时索引要后移
获取结果是:可以整除的组合中最长的;
停止条件是:除递归到头外,还有一个重要的剪枝条件,当前已经计算出的最长组合的长度,如果已经大于“当前组合长度 + 后边剩余的全部元素个数”,如[1,2,4,8],遍历1时已经得出最长组合为[1,2,4,8]长度为4,那么遍历2时会发现即便后边都能整除也就是最长长度也不过是3,那么对于2的遍历立即可以停止;
经验:因数据大小导致需要向前遍历的题,这种情况下必须先排好序,规避掉这种需求向前遍历的情况
OJ368代码:
class Solution { public: void helper (int idx, vector<int> cur, const vector<int> &nums, vector<int> &res) { if (idx <= nums.size()) { //已经算出的最长长度, 已经大于现在正在计算的流程中的最大可能长度, 那么就不用继续计算了(如[1,2,4,8], 已经算出[1,2,4,8]时, 后面的都不会更长) if (res.size() >= (nums.size() - idx + cur.size())) { return; } //最长长度超过已经计算的最大值就更新 if (cur.size() > res.size()) { res = cur; } for (int i = idx; i < nums.size(); i++) { if (!cur.empty()) { if (nums[i] % cur[cur.size() - 1] == 0) { cur.push_back(nums[i]); helper(i + 1, cur, nums, res); cur.pop_back(); } } else { cur.push_back(nums[i]); helper(i + 1, cur, nums, res); cur.pop_back(); } } } } vector<int> largestDivisibleSubset(vector<int>& nums) { vector<int> res; if (nums.empty()) { return res; } sort(nums.begin(), nums.end()); vector<int> cur; helper(0, cur, nums, res); return res; } };
9、OJ22 generate parentheses
生成括号,给定K,有多少种括号组合形式,要求:不可以出现右括号领头,如给定3,结果为:"((()))"、"(()())"、"(())()"、"()(())"、"()()()"
递归路线是:从0-K;
停止条件是:左括号和右括号个数都达到K时;
本题注意递归生成时的顺序,必须是,左括号要领先于右括号,设置两个数分别在递归过程中表示当前左括号和右括号的个数,判断依据是:
1、当都为0即最开始的时候,必须生成左括号;
2、当左括号和右括号个数都达到K时,结束了;
3、如果左括号个数,大于右括号个数,那么可以生成左括号,还可以生成右括号,两个递归流程;
4、如果左括号和右括号个数相等,必须生成左括号;
5、左括号肯定率先达到K,这时肯定必须生成右括号;
OJ22代码:
class Solution { public: void helper (int cur1, int cur2, const int n, string cur, vector<string> &res) { if (cur1 == n && cur2 == n) { res.push_back(cur); return; } if (!cur1 && !cur2) { cur += "("; helper(cur1 + 1, cur2, n, cur, res); } else if (cur1 < n && cur1 > cur2) { cur += "("; helper(cur1 + 1, cur2, n, cur, res); cur[cur.length() - 1] = ')'; helper(cur1, cur2 + 1, n, cur, res); } else if (cur1 < n && cur1 == cur2) { cur += "("; helper(cur1 + 1, cur2, n, cur, res); } else { cur += ")"; helper(cur1, cur2 + 1, n, cur, res); } } vector<string> generateParenthesis(int n) { vector<string> res; if (!n) { return res; } string cur = ""; helper(0, 0, n, cur, res); return res; } };
10、OJ79 word search
给定一个二维数组,里边有各种英文字符,然后查找一个英文字符串,判断二维数组里是否存在,可以是向上向下向左向右,不可以重复,如:
[ ['A','B','C','E'], ['S','F','C','S'], ['A','D','E','E'] ]
word =
"ABCCED",
-> returns
true,
word =
"SEE",
-> returns
true,
word =
"ABCB",
-> returns
false.
本题事实上和二叉树里的"子树问题"是一个问题,每一个字符都可以作为起始点做深度遍历,显然需要先判断出和word的首字符相同的再进入深搜;
另外进入深搜后,因为不许重复,所以需要给每个已经走过且判断复合word了的字符做"已使用"的标记,避免重复使用
OJ79代码:
class Solution { public: bool helper (const vector<vector<char>> &board, int x, int y, const int m, const int n, string word, vector<vector<bool>> &visit) { if (word.empty()) { return true; } else { if (x >= 0 && y >= 0 && x < m && y < n && word[0] == board[x][y] && visit[x][y] == false) { string relate = (word.length() > 1)?word.substr(1):""; visit[x][y] = true; if (relate.empty()) { return true; } //4个方向都做深搜, 任何一个返回成功什么已成功找到 bool l1 = helper(board, x - 1, y, m, n, relate, visit); if (l1) { return true; } bool l2 = helper(board, x + 1, y, m, n, relate, visit); if (l2) { return true; } bool l3 = helper(board, x, y - 1, m, n, relate, visit); if (l3) { return true; } bool l4 = helper(board, x, y + 1, m, n, relate, visit); if (l4) { return true; } //都没有找到, 复位该字符标志位 visit[x][y] = false; return false; } else { return false; } } } bool exist(vector<vector<char>>& board, string word) { if (board.empty() && word.empty()) { return false; } else if (board.empty() || word.empty()) { return false; } int m = board.size(), n = board[0].size(); for (int i = 0; i < board.size(); i++) { for (int j = 0; j < board[i].size(); j++) { if (board[i][j] == word[0]) { //visit用于标识每一次进入深搜后, 标识已经走过且确实被用到的字符 vector<vector<bool>> visit(board.size(), vector<bool>(board[i].size(), false)); //找到word首字符后再去深搜, if (helper(board, i, j, m, n, word, visit)) { return true; } } } } return false; } };
11、OJ46 permutations
无重复元素的全排列
OJ46代码:
class Solution { public: void helper (int st, vector<int> nums, vector<vector<int>> &res) { if (st == nums.size()) { res.push_back(nums); return; } for (int i = st; i < nums.size(); i++) { if (i > st) { int t = nums[st]; nums[st] = nums[i]; nums[i] = t; } helper(st + 1, nums, res); if (i > st) { int t = nums[st]; nums[st] = nums[i]; nums[i] = t; } } } vector<vector<int>> permute(vector<int>& nums) { vector<vector<int>> res; if (nums.empty()) { return res; } helper(0, nums, res); return res; } };
12、OJ47 permutation II
带重复元素的全排列。
注意,这道题用OJ40、OJ90的避免去重方法不适用,因为当递归到当前数时,向前发现有相同的数,但怎么界定是第一次执行的还是重复的呢?
带重复的全排列的方法是,原数组排序,保证重复元素相邻,然后为每一个元素标记上"是否已使用",遍历时永远从头遍历到尾,如果当前元素和之前元素相同,但之前元素标记未用过,那么说明本次遍历属于重复的;
如[1,1],当第一次从第一个1遍历到第二个1时,得到结果[1,1]并保存;
当第二次从第二个1入结果集,然后再递归遍历时,会发现第一个1的标志位为"未使用",什么当前是跨过了第1个1
OJ47代码:
class Solution { public: void helper (int idx, vector<int> nums, vector<int> cur, vector<vector<int>> &res, vector<bool> &visit) { if (idx == nums.size()) { res.push_back(cur); return; } //查看当前数的前面的数, 如果相等且未使用, 则不能再继续执行(如[1,1], 从第一个1到第二个1执行生成[1,1]结果后, 索引后移到第二个1并加入cur, 此时准备加入第一个1时, 发现其状态为false返回, 即避免了生成重复结果) for (int i = 0; i < nums.size(); i++) { if (visit[i] || (i > 0 && nums[i] == nums[i - 1] && visit[i - 1] == false)) { continue; } cur.push_back(nums[i]); visit[i] = true; helper(idx + 1, nums, cur, res, visit); visit[i] = false; cur.pop_back(); } } vector<vector<int>> permuteUnique(vector<int>& nums) { vector<vector<int>> res; if (nums.empty()) { return res; } //必须要排序 vector<int> cur; vector<bool> visit(nums.size(), false); sort(nums.begin(), nums.end()); helper(0, nums, cur, res, visit); return res; } };
另外,关于OJ31(当前全排列组合的下一个,next permutation)和OJ60(第K个全排列,permutation sequence),和递归回溯并无关,而是基于全排列知识的运用。
相关文章推荐
- LeetCode39/40/22/77/17/401/78/51/46/47/79 11道回溯题(Backtracking)
- LeetCode39/40/22/77/17/78/51/46/47/79 10道 Backtracking**
- LeetCode 39,40,46,47,78,90 回溯法专题
- leetcode-46、47 Permutations/II 数字的排列组合
- Leetcode 22, 77: 回溯问题
- leetcode78、90-Subsets I/II(组合数/子集数目)
- LeetCode 2015.8.7 173,75,39,78,82,90,103
- 【题解】【排列组合】【回溯】【Leetcode】Generate Parentheses
- [Leetcode] 93, 39, 40
- Leetcode 39 Combination Sum & 40 Combination Sum II & 216 Combination Sum III & 377 Combination V
- leetcode 39|40|216|377. Combination Sum 1|2|3|4
- Leetcode 78&90. Subsets I & II 【排列与组合的生成总结】
- leetcode 47. Permutations II 全排列问题(去掉重复元素)+递归
- Leetcode 39 40 216 Combination Sum I II III
- leetcode_77_combiantion_78_subsets_90_subsets2
- leetcode【39+40+216+377 Combination Sum 相关】【python】
- LeetCode 46 47....排列问题
- [leetcode 46] Permutations------数组中元素的所有排列组合集合
- 【题解】【排列组合】【回溯】【Leetcode】Gray Code
- [LeetCode] Subsets I (78) & II (90) 解题思路,即全组合算法