回溯算法
问题描述
回溯算法是穷举的一种,通过“剪枝”策略,来减少遍历的次数,整体的时间复杂度还是O(n!),但是比纯暴力破解法还是效率高一些。
回溯算法是一种经典的搜索算法,虽然执行效率低,但是能有思路解决问题,已经比无解要好。
属于递归算法的一种,深度优先遍历算法(DFS)就是回溯算法的应用。
回溯法的思路就是在“分叉路口”逐一路径进行试验,发现走错路了,再往回退,只到上一个“正确的路口”,再尝试其他路径。
解题步骤
解决一个回溯问题,实际上就是一个决策树的遍历过程。
你只需要思考 3 个问题:
1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。
选择问题的决策树示意图:
回溯算法可以简单归结为:
def backTrack(路径,选择列表[维护的状态]){
if 满足条件:
result.add(路径);
return;
for 选择 in 选择列表:
if 已存在:
continue;
add_item (做选择,添加到选择列表中);
backTrack (路径,选择列表);
remove_item (撤销选择,回溯);
}
回溯算法的经典问题
N皇后问题
/****************************************
* 八皇后问题
* 可以使用回溯法和动态规划法
* 引申处理N皇后问题,返回可以实现的结果个数
*/
public int calNQueens(int n){
// 计算n皇后问题
int res = 0;
int[] queensRes = new int[n]; // 八皇后问题的最终结果,记录每行元素放置在哪一列,使用全局变量
res = calQueens(n,queensRes,0,res);
return res;
}
/***********************
*
*/
public int calQueens(int N,int[] queens,int row,int res){
// for(int i = 0; i< queuesRes.length;i ++){
// System.out.print(queuesRes[i] + ",");
// }
// System.out.println();
// row是行号,每次判断一行的可行值
if(row == N){
// 8行已经执行完毕,打印输出
printQueues(queens);
res += 1;
return res;
}
for(int col = 0; col < N ; ++ col ){
if(is_ok(queens,row,col)){
queens[row] = col;
res = calQueens(N,queens,row + 1,res);
}
}
return res;
}
private boolean is_ok(int[] queens,int row,int col){
// 判断当前列位置是否ok,N皇后问题的规则是斜着,横竖都不能有重复值
int left = col - 1, right = col + 1; // 判断左右斜面是否可行
for(int i = row - 1;i >= 0; i --){
if (left == queens[i] && left >= 0){
return false;
}
if (right == queens[i] && right < 8){
return false;
}
if (queens[i] == col){
return false;
}
-- left;
++ right;
}
return true;
}
private void printQueues(int[] res){
System.out.println("==========================================");
for(int i = 0; i < res.length; i ++){
for (int j = 0; j < res.length ;j ++ ){
if (j == res[i]){
System.out.print("@ ");
} else {
System.out.print("* ");
}
}
System.out.println();
}
}
全排列问题
子问题:字符串排列
<待填坑>
和数组一样,都属于全排列问题
// 全排列问题,可以扩展为二维数组的排列
子问题:数组全排列
LeetCode 46. 全排列
题目描述:
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 示例 2:
输入:nums = [0,1] 输出:[[0,1],[1,0]]s
public static List<List<Integer>> permute(int[] nums) {
int len = nums.length;
List<List<Integer>> res = new ArrayList();
if(len == 0){
return res;
}
Deque<Integer> path = new ArrayDeque<Integer>();
boolean[] flag = new boolean[len]; // default false
backTrack(nums,len,0,path,flag,res);
return res;
}
private static void backTrack(int[] nums,int len,int depth,Deque<Integer> path,boolean[] flag,List<List<Integer>> res){
if(depth == len){
res.add(new ArrayList<Integer>(path));
return;
}
for(int i = 0; i < len ; i++){
if(flag[i]){
continue;
}
flag[i] = true;
path.addLast(nums[i]);
backTrack(nums,len,depth + 1,path,flag,res);
flag[i] = false;
path.removeLast();
}
}
子问题:组合总和
问题描述:
LeetCode 39. 组合总和
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
示例 1:
输入:candidates = [2,3,6,7], target = 7 输出:[[2,2,3],[7]] 解释: 2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。 7 也是一个候选, 7 = 7 。 仅有这两种组合。 示例 2:
输入: candidates = [2,3,5], target = 8 输出: [[2,2,2,2],[2,3,3],[3,5]]
Code:
List<List<Integer>> res = new ArrayList();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
cal(0,0,new LinkedList<Integer> (),candidates,target);
return res;
}
private void cal(int idx,int sum,LinkedList<Integer> track,int[] candidates,int target){
if(idx >= candidates.length || sum > target){
// 已经遍历到队尾,或者最大值超标,终止回溯
return ;
}
if(sum == target){
// 筛选完成
res.add(new ArrayList<Integer>(track));
return;
}
// 使用当前元素
track.addLast(candidates[idx]);
cal(idx,sum + candidates[idx],track,candidates,target);
// 不使用当前元素
track.removeLast();
cal(idx + 1,sum,track,candidates,target);
}
0-1背包问题
可以使用回溯法和动态规划两种方法处理。
背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。相似问题经常出现在商业、组合数学,计算复杂性理论、密码学和应用数学等领域中。也可以将背包问题描述为决定性问题,即在总重量不超过W的前提下,总价值是否能达到V?它是在1978年由Merkel和Hellman提出的。
问题描述:
给定n个重量为w1,w2,w3,…,wn,价值为v1,v2,v3,…,vn的物品和容量为C的背包 求这个物品中一个最有价值的子集,使得在满足背包的容量的前提下,包内的总价值最大
注意:0-1背包问题指的是每个物品只能使用一次
/**
* 0-1背包计算主函数,每个物品只能采用一次
* @param values:物品清单的价值
* @param weights:物品的重量
* @param capacity:
* @return
*/
int MAX = 0;
public int knapsack(int[] values,int[] weights,int capacity){
boolean[] flag = new boolean[values.length];
backTrack(values,weights,capacity,
0,0,flag);
return MAX;
}
// 回溯函数
private void backTrack(int[] values,int[] weights,int capacity,
int w,int v,boolean[] flag){
// w是当前背包重量
if (w > capacity){
return;
}
if(v > MAX){
System.out.println("当前价值:" + v);
for(int i = 0; i < flag.length; i++) {
System.out.print("| " + values[i] + " ");
System.out.print("--> " + flag[i] + " ");
}
System.out.println();
MAX = v;
}
for(int i = 0;i < values.length; i ++){
if (flag[i]){
continue;
}
flag[i] = true;
backTrack(values,weights,capacity,
w + weights[i],v + values[i],flag);
flag[i] = false;
}
}
正则表达式问题
<待填坑>
图的深度优先搜索
<待填坑>