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