算法学习之回溯问题(java版)

算法学习之回溯问题

解决一个回溯问题,实际上就是一个决策树的遍历过程

概念

在解决回溯问题时,我们需要关注:

  • 路径:已经做出的选择
  • 选择列表:当前情况下可以做出的选择
  • 结束条件:到达决策树底层,无法再做选择的条件

算法框架(伪代码)

result = []
def backtrack(路径, 选择列表): 
	if 满足结束条件:
		result.add(路径) 
		return
	for 选择 in 选择列表: 
		#### 做选择 
	    # 将该选择从选择列表移除 
	    选择列表.remove(选择)
	    # 把选择加入到路径中
	    路径.add(选择) 
	    
	    # 递归
	    backtrack(路径, 选择列表)
	     
	    #### 撤销选择
	    # 把选择从路径中删除 
	    路径.remove(选择) 
	    #将该选择再加入选择列表
	    选择列表.add(选择)

这里我已经把for循环内的代码分为了三个部分,先做选择,然后在这个选择的基础下进行下一次的递归,最后一步就是撤销之前的选择。
注意:做选择与撤销选择的步骤是镜像的。

在这里插入图片描述
这张图中,[2]就是「路径」,表示已经做过的选择;[1,3]就是「选择列表」,表示可以做出的选择;「结束条件」就是遍历到树的底层,在这里就是选择列表为空的时候。
在做选择前,从选择列表中移除,然后路径中加入选择。递归。在路径中删除选择,在选择列表中添加选择。

例题

全排列问题

输入一个数组,给出这个数组的所有排列方式。
例如输入:[1,2],输出:[[1,2],[2,1]]

List<List<Integer>> res = new LinkedList<>();

List<List<Integer>> permute(int[] nums) {
    // 记录路径
    LinkedList<Integer> track = new LinkedList<>();
    backtrack(nums, track);
    return res;
}

void backtrack(int[] nums, LinkedList<Integer> track) {
    // 结束条件
    if (track.size() == nums.length) {
        res.add(new LinkedList(track));
        return;
    }
    for (int i = 0; i < nums.length; i++) {
    	// 这个代替了从选择队列中删除选择,与递归后的恢复选择
        if (track.contains(nums[i]))
            continue;
        // 做出选择
        track.add(nums[i]);
        // 进入下一层决策树
        backtrack(nums, track);
		// 撤销选择
        track.removeLast();
    }
}

N皇后问题

给你一个 N×N 的棋盘,让你放置 N 个 皇后,使得它们不能互相攻击。
PS:皇后可以攻击同一行、同一列、左上左下右上右下四个方向的任意单位。

思路:可以把棋盘的每一行想成是决策树的一层,而这一行中的每一列就是决策树这一层中的N个节点。

List<char[][]> res = new ArrayList<>();

List<char[][]> solveNQueens(int n) {
    char[][] board = new char[n][n];
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            board[i][j] = '.';
        }
    }
    backtrack(board, 0);
    return res;
}

void backtrack(char[][] board, int row) {
    if (row == board.length) {
        int n = board.length;
        char[][] tmp = new char[n][n];
        // 注意深浅拷贝问题。clone方法在二维数组时是浅拷贝。
        // 解决方法就是一行一行的使用clone
        for (int i = 0; i < n; i++) {
            tmp[i] = board[i].clone();
        }
        res.add(tmp);
        return;
    }
    int n = board.length;
    for (int col = 0; col < n; col++) {
        if (!isValid(board, row, col)) {
            continue;
        }
        board[row][col] = 'Q';
        backtrack(board, row + 1);
        board[row][col] = '.';
    }
}

boolean isValid(char[][] board, int row, int col) {
    int n = board.length;
    // 左右上下
    for (int i = 0; i < n; i++) {
        if (board[i][col] == 'Q' || board[row][i] == 'Q')
            return false;
    }
    // 右上
    for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
        if (board[i][j] == 'Q')
            return false;
    }
    // 右下
    for (int i = row + 1, j = col + 1; i < n && j < n; i++, j++) {
        if (board[i][j] == 'Q')
            return false;
    }

    // 左上
    for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
        if (board[i][j] == 'Q')
            return false;
    }
    // 左下
    for (int i = row + 1, j = col - 1; i < n && j >= 0; i++, j--) {
        if (board[i][j] == 'Q')
            return false;
    }

    return true;
}

个人总结

思路:for(放在首位的多个选择){从数组中取元素,递归,把取的元素撤销。}

之前要把可做的选择给放到数组中。
注意:深浅拷贝问题

申明:本博文是看了labuladong的算法小抄之后个人的理解以及总结。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值