【回溯】

本文深入讲解了回溯算法的应用场景及实现方式,包括组合问题、排列问题、迷宫问题等,并提供了详细的代码示例。

回溯算法是在一棵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、组成内容可重复,个数不定,总和相同且固定

HJ61放苹果

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

HJ43迷宫

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、乱序组合+单选+结果为某一个目标值

HJ67 24点游戏算法

// 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。
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值