基于排列的深度优先搜索 https://blog.csdn.net/qq_19446965/article/details/102463792 问题模型:求出所有满足条件的“排列”。 判断条件:组合中的元素是顺序“相关”的。 时间复杂度:与 n! 相关。 //全排列-A(n,m) def recursive_a(m, path_list, original_list, result_list): if m == 0: result_list.append(list(path_list)) return for i in range(len(original_list)): path_list.append(original_list[i]) temp_list = deepcopy(original_list) temp_list.pop(i) recursive_a(m - 1, path_list, temp_list, result_list) path_list.pop() 求A(4,3) original_list = [1, 2, 3, 4] result_list = [[1, 2, 3], [1, 2, 4], [1, 3, 2], [1, 3, 4], [1, 4, 2], [1, 4, 3], [2, 1, 3], [2, 1, 4], [2, 3, 1], [2, 3, 4], [2, 4, 1], [2, 4, 3], [3, 1, 2], [3, 1, 4], [3, 2, 1], [3, 2, 4], [3, 4, 1], [3, 4, 2], [4, 1, 2], [4, 1, 3], [4, 2, 1], [4, 2, 3], [4, 3, 1], [4, 3, 2]] 共24种 https://blog.csdn.net/qq_19446965/article/details/102463792 给定一个可包含重复数字的序列,返回所有不重复的全排列。 示例: 输入: [1,1,2] 输出: [ [1,1,2], [1,2,1], [2,1,1] ] 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/permutations-ii 先生成再去重 def helper(self, nums, res, path): if not nums and path not in res: res.append(path) else: for i in range(len(nums)): self.helper(nums[:i] + nums[i+1:], res, path + [nums[i]]) 耗时:1356 ms 这样的做法是在每个path生成之后才去做的判断,因此效率一点都不高。 深度拷贝: def helper(self, nums, res, path): if not nums and path not in res: res.append(list(path)) return for i in range(len(nums)): path.append(nums[i]) temp_list = deepcopy(nums) temp_list.pop(i) self.helper(temp_list, res, path) path.pop() 耗时:超时 回溯法 下面这个做法是标准的回溯法,需要用到visited来表示哪些位置已经被添加到path中了。 为什么有重复? 在这个例子中,我们在第一个1开始的排列中已经取了第二个1的情况;如果在第二个1开始的排列中仍然取第一个1,就有重复了。 如何去重呢? 1)排序 2)不是第一个数字,并且现在的数字和前面的数字相等,同时前面的数字还没有访问过,我们是不能搜索的,需要直接返回。 原因是,这种情况下,必须是由前面搜索到现在的这个位置,而不能是由现在的位置向前面搜索。 def helper(self, nums, res, path, visit): if len(path) == len(nums) : res.append(path) for i in range(len(nums)): if i > 0 and visit[i - 1] and nums[i - 1] == nums[i]: continue if not visit[i]: visit[i] = True self.helper(nums, res, path+[nums[i]], visit) visit[i] = False 耗时:56 ms 交换思想: def swap(nums, i, cur): nums[cur], nums[i] = nums[i], nums[cur] def helper_1(cur, nums, res): if cur == len(nums): res.append(list(nums)) for i in range(cur, len(nums)): if nums[i] not in nums[i+1:]: swap(nums, i, cur) helper(cur + 1, nums, res) swap(nums, i, cur) 耗时:44 ms 题目描述 给定一个"HH:MM"格式的时间,重复使用这些数字,返回下一个最近的时间。每个数字可以被重复使用任意次。 保证输入的时间都是有效的。例如,"01:34","12:09" 都是有效的,而"1:34","12:9"都不是有效的时间。
//排列组合 //定义:https://baike.baidu.com/item/排列组合/706498 https://blog.csdn.net/qq_19446965/article/details/102463792 # coding: utf-8 from copy import deepcopy //组合 def recursive_c(cur, m, cur_list, original_list, result_list): if m == 0: result_list.append(list(cur_list)) return for i in range(cur, len(original_list)): cur_list.append(original_list[i]) recursive_c(i + 1, m - 1, cur_list, original_list, result_list) cur_list.pop() original_list = [1, 2, 3, 4] result_list =[[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]] 共4种 //全排列 def recursive_a(m, cur_list, original_list, result_list): if m == 0: result_list.append(list(cur_list)) return for i in range(len(original_list)): cur_list.append(original_list[i]) temp_list = deepcopy(original_list) temp_list.pop(i) recursive_a(m - 1, cur_list, temp_list, result_list) cur_list.pop() original_list = [1, 2, 3, 4] result_list = [[1, 2, 3], [1, 2, 4], [1, 3, 2], [1, 3, 4], [1, 4, 2], [1, 4, 3], [2, 1, 3], [2, 1, 4], [2, 3, 1], [2, 3, 4], [2, 4, 1], [2, 4, 3], [3, 1, 2], [3, 1, 4], [3, 2, 1], [3, 2, 4], [3, 4, 1], [3, 4, 2], [4, 1, 2], [4, 1, 3], [4, 2, 1], [4, 2, 3], [4, 3, 1], [4, 3, 2]] 共24种 https://blog.csdn.net/qq_19446965/article/details/102463792
组合: https://www.cnblogs.com/bonelee/p/11667428.html 看下面的图,以【1,2,3】为例: root (path=[]) | ------------------------------------------------------------------------ | | | 1 2 3 / | 2 3 3 / 3 在遍历上述树的过程中将path全部记录下来(path不用走到叶子节点) 代码如下: class Solution: """ @param nums: A set of numbers @return: A list of lists """ def subsets(self, nums): # write your code here nums = sorted(nums) path, result = [], [] self.dfs(nums, 0, path, result) return result def dfs(self, nums, start_index, path, result): result.append(list(path)) if start_index == len(nums): return for i in range(start_index, len(nums)): path.append(nums[i]) self.dfs(nums, i+1, path, result) path.pop() 含有重复元素的子集问题,例如【1,2,2,3】 root (path=[]) | ------------------------------------------------------------------------- | | | | 1 2 2(重复) 3 / / | 2 3 2 3 3 / | 2 3 3 | 3 又例如【1,1,2,3】 root (path=[]) | --------------------------------------------------------------------------- | 1(重复) | | 1 / 2 3 / | 2 3 | 1 2 3 | 3 / | 3 2 3 3 | 3 class Solution: """ @param nums: A set of numbers. @return: A list of lists. All valid subsets. """ def subsetsWithDup(self, nums): # write your code here nums = sorted(nums) path, result = [], [] self.dfs(nums, 0, path, result) return result def dfs(self, nums, index, path, result): result.append(list(path)) for i in range(index, len(nums)): if i > 0 and nums[i] == nums[i-1] and i > index: # 和上面代码相比,就多了这个判断而已 continue path.append(nums[i]) self.dfs(nums, i+1, path, result) path.pop() 排列: https://www.cnblogs.com/bonelee/p/11668685.html 举例说明,【1,2,3】: root / | 1 2 3 / / / 2 3 1 3 1 2 / ... .... 3 3 也就是说:permutate([1,2,3]) = {[1]+permutate([2,3]), [2]+permutate([1,3]), [3]+permutate([1,2])} 使用交换思路后编码如下: class Solution: """ @param: : A list of integers @return: A list of unique permutations """ def permute(self, nums): # write your code here result = [] self.find_permutations(nums, 0, result) return result def find_permutations(self, nums, start_index, result): if start_index >= len(nums): result.append(list(nums)) return for i in range(start_index, len(nums)): nums[start_index], nums[i] = nums[i], nums[start_index] self.find_permutations(nums, start_index + 1, result) nums[start_index], nums[i] = nums[i], nums[start_index] 含有重复元素的全排列,举例说明,【1,1,2】: root / | 1 1(重复) 2 / / / 1 2 2 1 1 1(重复) / | | | | 2 1 1 2 1 1 代码如下:就是用比较无脑的交换做法,当前交换的元素nums[i], 只要在nums[start_index:i]有重复元素存在就说明之前的路径已经走过。 class Solution: """ @param: : A list of integers @return: A list of unique permutations """ def permuteUnique(self, nums): # write your code here result = [] self.find_permutations(nums, 0, result) return result def find_permutations(self, nums, start_index, result): if start_index >= len(nums): result.append(list(nums)) return for i in range(start_index, len(nums)): if nums[i] in nums[start_index:i]: # 重复的排列就多了这个判断而已 continue nums[start_index], nums[i] = nums[i], nums[start_index] self.find_permutations(nums, start_index + 1, result) nums[start_index], nums[i] = nums[i], nums[start_index] 例子: https://www.cnblogs.com/bonelee/p/11668915.html DFS总结: 1、第一次讲的dfs模板一定要记住。 2、二叉树的遍历,https://www.cnblogs.com/rnanprince/p/11595380.html,先序中序的递归和迭代写法必须掌握,像算法模板一样记住。后序遍历只掌握递归写法。 3、遍历过程中需要记住上次遍历节点才能得到结果的,记住模板。 last_node = None def dfs (root): if last_node is None: last_node = root else: compare(last_node, root).... last_node = root dfs(root.left) dfs(root.right) 4、BST的搜索代码要会,要记住。 5、排列组合类题目: 组合类算法,都使用分治+递归的思路去写,重复元素,先排序,无非多了一个判断。 排列类算法,用交换思路,都使用分治+递归的思路去写,重复元素,无非多了一个判断。 6、隐式图搜索:八皇后,正则表达式匹配,word拼图 i j | | abc ==> abc dfs(i+1, j+1) a*bc ==> aaabc dfs(i+2, j) or dfs(i, j+1) a.bc ==> adbc dfs(i+1, j+1) a b c g a n a x x i x x n x x dfs(左边走) dfs(右边走) dfs(上边走) dfs(下边走) 走的过程中将路径记下来 7、常见问题: 超时的处理:剪枝(cache、trie去剪枝),修改算法bfs,用dp 测试用例过不完:自己debug,放到ide去调试
基于组合的深度优先搜索 https://blog.csdn.net/qq_19446965/article/details/102463792 问题模型:求出所有满足条件的“组合”。 判断条件:组合中的元素是顺序无关的。 时间复杂度:与 2^n 相关。 DFS的缺点以及适用情况 DFS的优点 空间优劣上,DFS是有优势的,DFS不需要保存搜索过程中的状态,而BFS在搜索过程中需要保存搜索过的状态,而且一般情况需要一个队列来记录。 因为根据栈的思想,DFS在搜索一个点以后,会弹出该点,就不需要保存已经搜索过的点。而BFS是必定保存搜索过的点的。 DFS的缺点 因为DFS含有栈的思想,因此经常用递归解决问题,但是如果不给递归限制深度,往往会超过时间与空间复杂度的。 二维数组的题目,N小于20的,适用DFS。而一般 N<= 200,N<=1000这种,一定不可能用DFS去做。而且并不只是整个题目不能用DFS,其中的每一步也不能使用DFS。 N指的应该是递归深度**。 比如LeetCode中的Unique Paths,若是用大集合,会超时,该题适合用DP解。 DFS的适用情况: DFS适合搜索全部的解,因为要搜索全部的解,那么BFS搜索过程中,遇到离根最近的解,并没有什么用,所以搜素全部解的问题,DFS显然更加合适。 当求解目标,必须要走到最深(例如树,走到叶子节点)才能得到一个解,这种情况适合用深搜。 https://blog.csdn.net/qq_19446965/article/details/102472524 搜索,二叉树的时间复杂度计算通用公式 搜索的时间复杂度:O(答案总数 * 构造每个答案的时间) 举例:Subsets问题,求所有的子集。子集个数一共 2^n,每个集合的平均长度是 O(n) 的,所以时间复杂度为 O(n * 2^n),同理 Permutations 问题的时间复杂度为:O(n * n!) 用分治法解决二叉树问题的时间复杂度:O(二叉树节点个数 * 每个节点的计算时间) 举例:二叉树最大深度。二叉树节点个数为 N,每个节点上的计算时间为 O(1)。总的时间复杂度为 O(N) https://blog.csdn.net/qq_19446965/article/details/102472371 递归三要素: 1、递归的定义 2、递归的拆解 3、递归的出口 定义是我们要声明这个函数,要考虑这个函数的返回值以及需要的参数 拆解是我们怎么来实现这个函数,也就是说,我们这个函数是干什么的 出口结束递归,剪枝相当于提前设置了一些递归出口 题目描述:给定一个数组 num (整数)和一个整数 target. 找到 num 中所有的数字之和为 target 的组合. //查找和为target的所有组合,DFS法 def dfs(cur, sum, target, path_list, original_list, result_list): if sum == target: result_list.append(list(path_list)) return if sum > target: return for i in range(cur, len(original_list)): path_list.append(original_list[i]) dfs(i + 1, sum + original_list[i], target, path_list, original_list, result_list) path_list.pop() 原文链接:https://blog.csdn.net/qq_19446965/article/details/81775702 题目描述:给定一个数组 num 和一个整数 target. 找到 num 中所有的数字(不重复)之和为 target 的组合. def dfs(cur, sum, target, path_list, original_list, result_list): if target == sum: result_list.append(list(path_list)) return for i in range(cur, len(original_list)): if i > 0 and original_list[i] == original_list[i-1]: continue path_list.append(original_list[i]) dfs(i + 1, sum + original_list[i], target, path_list, original_list, result_list) path_list.pop() >> original_list = [1, 2, 1, 3, 1] >> target=3 >> [[1, 2], [3]] 题目描述:给定一个数组 num 和一个整数 target. 找到 num 中所有的数字之和为 target 的组合(不重复). https://www.jiuzhang.com/solutions/combination-sum-ii/ def dfs(cur, sum, target, path_list, original_list, result_list): if target == sum: if path_list not in result_list: result_list.append(list(path_list)) return for i in range(cur, len(original_list)): path_list.append(original_list[i]) dfs(i + 1, sum + original_list[i], target, path_list, original_list, result_list) path_list.pop() >> original_list = [1, 2, 1, 3, 1] >> target=3 >> [[1, 1, 1], [1, 2], [3]] 题目描述: 给定一个候选数字的集合 candidates 和一个目标值 target. 找到 candidates 中所有的和为 target 的组合. 在同一个组合中, candidates 中的某个数字不限次数地出现. https://www.jiuzhang.com/solutions/combination-sum/ 分析: Combination Sum 一个数可以选很多次,搜索时从 index 开始而不是从 index + 1 class Solution: def combinationSum(self, candidates, target): candidates = sorted(list(set(candidates))) results = [] self.dfs(candidates, target, 0, [], results) return results # 递归的定义:在candidates[start ... n-1] 中找到所有的组合,他们的和为 target # 和前半部分的 combination 拼起来放到 results 里 # (找到所有以 combination 开头的满足条件的组合,放到 results) def dfs(self, candidates, target, start, combination, results): # 递归的出口:target <= 0 if target < 0: return if target == 0: # deepcooy return results.append(list(combination)) # 递归的拆解:挑一个数放到 combination 里 for i in range(start, len(candidates)): # [2] => [2,2] combination.append(candidates[i]) self.dfs(candidates, target - candidates[i], i, combination, results) # [2,2] => [2] combination.pop() # backtracking 题目描述 给定n个不同的正整数,整数k(1<= k <= n)以及一个目标数字。 在这n个数里面找出K个数,使得这K个数的和等于目标数字,你需要找出所有满足要求的方案。 分析:多了个限制条件k public class Solution { List<List<Integer> > ans; public List<List<Integer>> kSumII(int A[], int K, int target) { ans = new ArrayList<List<Integer>>(); List<Integer> tans = new ArrayList<Integer>(); dfs(A, K, target, A.length - 1, tans); return ans; } public void dfs(int A[], int K, int target, int index, List<Integer> tans) { if(K == 0 && target == 0) { ans.add(new ArrayList<Integer>(tans)); return ; } if(K < 0 || target < 0 || index < 0) return ; dfs(A, K, target, index - 1, tans); tans.add(A[index]); dfs(A, K - 1, target - A[index], index-1, tans); tans.remove(tans.size() - 1); } public void dfs_b(int A[], int K, int target, int index, List<Integer> tans) { if(K == 0 && target == 0) { ans.add(new ArrayList<Integer>(tans)); return ; } if(K < 0 || target < 0 || index < 0) return ; for (int i = A.length-1; i >= index; i++) { tans.add(A[i]); dfs(A, K - 1, target - A[index], index-1, tans); tans.remove(tans.size() - 1); } } 题目描述 给定字符串 s, 需要将它分割成一些子串, 使得每个子串都是回文串. 返回所有可能的分割方案. https://blog.csdn.net/qq_19446965/article/details/81513591 第5题