回溯算法是在一棵n叉树上的深度优先遍历
1、不重复+不考虑顺序
n个数选k个数组合(不重复,不考虑顺序)
77. 组合
2021阿里巴巴编程题(2星)
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
int n=scan.nextInt();
int k=scan.nextInt();
List<List<Integer>> res = new ArrayList<>();
// 从 1 开始是题目的设定
Deque<Integer> path = new ArrayDeque<>();
dfs(n, k, 1, path, res);
for (List<Integer> li:res){
for (int i:li){
System.out.print(i);
}
System.out.println();
}
}
private static void dfs(int n, int k, int begin, Deque<Integer> path, List<List<Integer>> res) {
// 递归终止条件是:path 的长度等于 k
if (path.size() == k) {
res.add(new ArrayList<>(path));
return;
}
// 遍历可能的搜索起点
for (int i = begin; i <= n; i++) {
// 向路径变量里添加一个数
path.addLast(i);
// 下一轮搜索,设置的搜索起点要加 1,因为组合数理不允许出现重复的元素
dfs(n, k, i + 1, path, res);
// 重点理解这里:深度优先遍历有回头的过程,因此递归之前做了什么,递归之后需要做相同操作的逆向操作
path.removeLast();
}
}
2、不重复+考虑顺序+无目标值
给定一个不含重复数字的数组 nums ,返回其所有可能的全排列 ,你可以按任意顺序返回答案(不重复,考虑顺序)
46. 全排列
递归的终止条件是: 一个排列中的数字已经选够了 ,因此我们需要一个变量来表示当前程序递归到第几层,我们把这个变量叫做 depth;
布尔数组 used,初始化的时候都为 false 表示这些数还没有被选择,当我们选定一个数的时候,就将这个数组的相应位置设置为 true
public List<List<Integer>> permute(int[] nums) {
int len = nums.length;
// 使用一个动态数组保存所有可能的全排列
List<List<Integer>> res = new ArrayList<>();
if (len == 0) {
return res;
}
boolean[] used = new boolean[len];
List<Integer> path = new ArrayList<>();
dfs(nums, len, 0, path, used, res);
return res;
}
private void dfs(int[] nums, int len, int depth,List<Integer> path,
boolean[] used,List<List<Integer>> res) {
if (depth == len) {
res.add(path);
return;
}
// 在非叶子结点处,产生不同的分支,这一操作的语义是:在还未选择的数中依次选择一个元素作为下一个位置的元素,这显然得通过一个循环实现。
for (int i = 0; i < len; i++) {
if (!used[i]) {
path.add(nums[i]);
used[i] = true;
dfs(nums, len, depth + 1, path, used, res);
// 注意:下面这两行代码发生 「回溯」,回溯发生在从 深层结点 回到 浅层结点 的过程,代码在形式上和递归之前是对称的
used[i] = false;
path.remove(path.size() - 1);
}
}
}
参考:回溯算法 + 剪枝
3、组合总和:单选+目标和固定
给你一个 无重复元素的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的所有不同组合
39. 组合总和
class Solution {
private List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<Integer> path = new ArrayList<>();
//对candidates排序,以减少回溯次数
Arrays.sort(candidates);
backtrack(path,candidates,target,0,0);
return res;
}
private void backtrack(List<Integer> path,int[] candidates,int target,int sum,int begin) {
if(sum == target) {
res.add(new ArrayList<>(path));
return;
}
for(int i = begin;i < candidates.length;i++) {
int rs = candidates[i] + sum;
if(rs <= target) {
path.add(candidates[i]);
backtrack(path,candidates,target,rs,i);
path.remove(path.size()-1);
} else {
break;
}
}
}
}
4、组合总和 :单选+目标和固定
找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
- 只使用数字1到9
- 每个数字 最多使用一次
返回 所有可能的有效组合的列表
216. 组合总和 III
class Solution {
List<List<Integer>> res=new LinkedList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
int[] arr={1,2,3,4,5,6,7,8,9};
Deque<Integer> path=new LinkedList<>();
dfs(k,n,0,0,arr,path);
return res;
}
private void dfs(int k, int target,int sum, int begin, int[] arr,Deque<Integer> path){
if(sum==target && path.size()==k){
res.add(new LinkedList<>(path));
return;
}
for (int i=begin;i<arr.length;i++){
int temp=arr[i]+sum;
if(temp<=target && path.size()<k){
path.offer(arr[i]);
dfs(k,target,temp,i+1,arr,path);
path.removeLast();
}else {break;}
}
}
}
5、组成内容可重复,个数不定,总和相同且固定
public class Main {
static int total = 0;
static List<List<Integer>> res = new ArrayList<>();
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
int m = in.nextInt();
int n = in.nextInt();
if (m <=1 || n<=1) {
System.out.println(1);
return;
}
backtrack(m, 0, n, 0, 1);
System.out.println(total+1);
for (List<Integer> pa : res) {
System.out.println(pa);
}
}
private static void backtrack( int num, int size, int target, int sum,
int begin) {
if (sum == num) {
total++;
return;
}
for (int i = begin; i < num; i++) {
sum = i + sum;
if (size+1 <= target) {
backtrack(num, size+1, target, sum, i);
sum = sum - i;
} else {
break;
}
}
}
}
6、迷宫问题:DFS
import java.util.*;
// 题目已经提示了 【迷宫只有一条通道】,则直接使用 DFS 找路径就行了,如有多条路径找最短考虑使用 BFS
import java.util.*;
public class Main {
public static ArrayList<int[]> path = new ArrayList<>();//搜索所有可能的路径
public static ArrayList<int[]> best_path = new ArrayList<>();//最短路径
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
while (in.hasNextInt()) { // 注意 while 处理多个 case
path.clear();
best_path.clear();//每个用例之前,都要清空下路径
int n = in.nextInt();
int m = in.nextInt();
int[][] maze = new int[n][m];
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
maze[i][j] = in.nextInt();
}
}
dfs(0,0,maze);//深搜+回溯
for(int[] pathi:best_path){
System.out.println("(" + pathi[0] + "," + pathi[1] + ")");
}
}
}
public static void dfs(int i,int j,int[][] maze){
//越界了
if(i<0 || i>maze.length-1 || j<0 || j>maze[0].length-1){
return;
}
//撞墙了或者说这个点来过
if(maze[i][j]==1){
return;
}
//终止条件,找到终点了
if(i==maze.length-1 && j==maze[0].length-1){
path.add(new int[]{maze.length-1,maze[0].length-1});//添加终点
if(best_path.size()==0 || path.size()<best_path.size()){//遇到更短的路径
best_path.clear();//清空之前的路径
for(int[] path0:path){
best_path.add(path0);
}
}
return;
}
//继续搜索
maze[i][j] = 1;//标记走过的点
path.add(new int[]{i,j});//添加到路径中
dfs(i-1,j,maze);
dfs(i+1,j,maze);
dfs(i,j-1,maze);
dfs(i,j+1,maze);//以i j为中心,上下左右搜索
maze[i][j] = 0;//回溯,恢复到之前的状态
path.remove(path.size()-1);//回溯,移除最后一个点
}
}
7、乱序组合+单选+结果为某一个目标值
// package org.jzy.huawei108;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
// 初始化数字数组和标志数组
int[] nums = new int[4];
int[] signs = new int[4];
for (int i = 0; i < nums.length; i++) {
nums[i] = scanner.nextInt();
}
boolean can = false; // 是否能得到24
for (int i = 0; i < nums.length; i++) {
signs[i] = 1;
if (dfs(nums, signs, nums[i], 24)) {
can = true;
break;
}
signs[i] = 0;
}
System.out.println(can);// 输出结果
}
}
/**
* 深度优先算法逻辑
*
* @param nums 输入的4个数字
* @param signs 访问标志数组
* @param v 顶点的值
* @param required 需要通过四则运算得到的数字
* @return
*/
private static boolean dfs(int[] nums, int[] signs, int v, int required) {
boolean allVisited = true;// 四个角均被访问
for (int sign : signs) {
if (sign == 0) {
allVisited = false;
}
}
if (allVisited) {
return v == required; // 在所有节点均被访问的前提下,判断最后的结果是否为所需要的结果。
}
// 访问所有的邻接顶点
for (int i = 0; i < nums.length; i++) {
if (signs[i] == 0) {
signs[i] = 1;
if (dfs(nums, signs, v + nums[i], required) // 加法
|| dfs(nums, signs, v - nums[i], required) // 减法
|| dfs(nums, signs, v * nums[i], required) // 乘法
|| nums[i] != 0 && v % nums[i] == 0 && dfs(nums, signs, v / nums[i], required)/* 除法 */) {
return true;// 如果可以通过四则运算得到需要的结果,则返回true。
}
signs[i] = 0; // 如果不能通过四则运算得到,则进行回溯。
}
}
return false;//当所有情况均得不到需要的结果,返回false。
}
}
本文深入讲解了回溯算法的应用场景及实现方式,包括组合问题、排列问题、迷宫问题等,并提供了详细的代码示例。

2607

被折叠的 条评论
为什么被折叠?



