排列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题

  

猜你喜欢

转载自www.cnblogs.com/bonelee/p/12589029.html