回溯算法与深度优先遍历
回溯法(backtracking)采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:
- 找到一个可能存在的正确的答案;
- 在尝试了所有可能的分步方法后宣告该问题没有答案。
深度优先搜索算法(Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。这个算法会尽可能深的搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。
全排列问题(leetcode 46题)
题目要求
给定一个 没有重复 数字的序列,返回其所有可能的全排列。(链接:leetcode 46题)
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
算法和思路
这个问题可以看作有n个元素,从左到右依次填入题目中给定的n个数字,每个数字只能使用一次。
首先想到的是递归。遍历所有元素,逐个作为开头的元素,然后对n - 1个元素再次递归进行全排列,直到最后只剩1个元素。
定义递归函数dfs(nums, depth, used, path, res) 表示从对nums数组进行全排列,左往右填已经填了 depth 个元素,元素的使用情况记录在used数组,当前排列为 path,封装的结果集为res。 那么整个递归函数分为两个情况:
- 如果 depth == n,说明我们已经填完了所有位置,找到了一个可行的解,将path封装进res结果集,递归结束。
- 如果depth < n,我们要考虑这path的末端要填入哪个数字。根据题目要求我们肯定不能填已经填过的数,因此定义一个标记数组used 来标记已经填过的数,那么在填第 depth 个数的时候我们遍历题目给定的 n 个数,如果这个数没有被标记过,我们就尝试填入,并将其标记,继续尝试填下一个位置,即调用函数 dfs(nums, depth + 1, used, path, res)。搜索回溯的时候要撤销这一个位置填的数以及标记,并继续尝试其他没被标记过的数。
实现代码
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
if (nums.length == 0) {
return res;
}
// Stack源码中推荐用Deque实现该数据结构
// path记录当前排列
Deque<Integer> path = new ArrayDeque<Integer>();
// used记录已使用过的元素
boolean[] used = new boolean[nums.length];
dfs(nums, 0, used, path, res);
return res;
}
private void dfs(int[] nums, int depth, boolean[] used, Deque<Integer> path, List<List<Integer>> res) {
if (depth == nums.length) {
res.add(new ArrayList<>(path));
}
for (int i = 0; i < nums.length; i++) {
if (used[i]){
continue;
}
path.addLast(nums[i]);
used[i] = true;
dfs(nums, depth + 1, used, path, res);
path.removeLast();
used[i] = false;
}
}
}
在Stack结构体源码中推荐用Deque实现Stack
/**
* <p>A more complete and consistent set of LIFO stack operations is
* provided by the {@link Deque} interface and its implementations, which
* should be used in preference to this class. For example:
* <pre> {@code
* Deque<Integer> stack = new ArrayDeque<Integer>();}</pre>
**/