zoukankan      html  css  js  c++  java
  • 排列dfs模板

    基于排列的深度优先搜索
    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题
    

      

  • 相关阅读:
    mysqldump详解
    mysql忽略表中的某个字段不查询
    mysqldumpslow基本使用
    xtrabakcup基本用法 安装、全量备份恢复、增量备份恢复
    Ubuntu--磁盘统计
    Ubuntu--硬盘的挂载与卸载
    Ubuntu--文件属性权限管理(command: chmod, chown)
    Ubuntu--useradd指令使用
    Ubuntu--安装sshd开启远程登陆服务
    Ubuntu--虚拟机中Ubuntu系统时间与windows不同步
  • 原文地址:https://www.cnblogs.com/bonelee/p/12589029.html
Copyright © 2011-2022 走看看