【代码随想录训练营】【Day30】第七章|回溯算法|332.重新安排行程|51. N皇后|37. 解数独|总结

重新安排行程

题目详细:LeetCode.332

这道题我是先看题解再有自己的思路,然后做出来的,解题思路的过程都写在代码注释里了,详细的题解可查阅:《代码随想录》 — 重新安排行程

Java解法(递归,回溯):

class Solution {
    Deque<String> ans = new ArrayDeque<>();
    // Map<出发机场, Map<目地机场, 机票数量>>
    Map<String, Map<String, Integer>> map = new HashMap<>();

    // 返回值boolean,true 代表找到一种合理的行程
    public boolean backTrack(int n){
        // 如果 到达的机场个数 == 机票数量 + 1,说明所有的机票都已被使用一次,是一种合理的行程
        if(ans.size() == n + 1){
            return true;
        }
        // 获取当前出发的机场
        String start = ans.getLast();
        if(this.map.containsKey(start)){
            // 获取当前机场可到达的目的地(已按字典升序排序 )
            Map<String, Integer> end_map = this.map.get(start);
            // 按顺序遍历目的地并安排行程
            for(Map.Entry<String, Integer> end_entry : end_map.entrySet()){
                String end = end_entry.getKey();
                int count = end_entry.getValue();
                // 机票数量 conut > 0 才能继续行程
                if(count > 0){
                    ans.offer(end);
                    end_map.put(end, count - 1); // 每张机票只能使用一次,对应的航班次数 - 1
                    if(backTrack(n)){
                        // 得到一种排序靠前的合理的行程即可返回结果
                        return true;
                    }
                    // 回溯
                    ans.removeLast();
                    end_map.put(end, count);
                }
            }
        }
        // 如果出发地已经没有下一段行程,则说明该行程安排不合理,返回 false
        return false;
    }

    public List<String> findItinerary(List<List<String>> tickets) {
        // 遍历每一段行程
        for(List<String> ticket: tickets){
            String start = ticket.get(0);
            String end = ticket.get(1);
            // 记录出发地到达多个目的地的次数
            Map<String, Integer> temp;
            if(!this.map.containsKey(start)){
                // 容器按字典升序排序 
                temp = new TreeMap<>(); 
                temp.put(end, 1);
            }else{
                temp = this.map.get(start);
                temp.put(end, temp.getOrDefault(end, 0) + 1);
            }
            this.map.put(start, temp);
        }
        // 行程规定从 JFK 开始。
        this.ans.offer("JFK");
        this.backTrack(tickets.size());
        return new ArrayList<String>(this.ans);
    }
}

在搜索函数backTrack中,我们可以看到其返回值是boolean类型,为什么不是跟之前的题目的返回值一样是void类型呢?

因为在之前的题目中,不论是求解组合问题、子集问题还是求解子序列问题,这些都要求我们找出所有的结果放进结果集中返回,所以需要对树形结构的每一条路径都进行搜索;

但是在这道题中,虽然可能存在多种行程方案,但题目只要求我们找出一种合理的行程方案即可,也就是我们在树形结构中找到某一条满足结果的路径时,就可以直接返回该结果,并不需要继续对其他路径进行搜索寻找其他方案;

所以我们可以设置搜索函数的返回值为boolean类型,来标识在递归过程中是否已经找到了一种合理的行程方案,返回值为 true,那么就可以直接返回结果了。


N皇后

题目详细:LeetCode.51

这道题我是先看题解再有自己的思路,然后做出来的,解题思路的过程都写在代码注释里了,详细的题解可查阅:《代码随想录》 — N皇后

与题解中的答案不同的是,我是选择了一维数组来表示棋盘;

  • 数组的下标表示第几行
  • 下标对应元素的值表示第几列

再一个难点就是如何判断当前位置是否能放置皇后:

public boolean isVaild(int[] board, int row, int col){
	   for(int i = 0; i <= row; i++){
	        if(board[i] == col) return false;
	        if(Math.abs(i - row) == Math.abs(board[i] - col)) return false;
	   }
	   return true;
}

可以发现选用一维数组表示棋盘的好处,在于校验N皇后放置的位置时,遍历棋盘的时间复杂度为 O(n) ,且只需要判断以下两个条件即可:

  • 当前列是否存在皇后:board[i] == col,如果某一行出现了与当前位置相同的列数,则表示在同一列上已存在皇后,当前位置不能放置皇后
  • 对角线上是否存在皇后:通过观察棋盘可以发现,当皇后处在同一斜线上时,它们的位置关系满足Math.abs(i - row) == Math.abs(board[i] - col),如果满足,则表示在同一对角线上已存在皇后,当前位置不能放置皇后。

Java解法(递归,回溯):

class Solution {
    List<List<String>> ans = new ArrayList<>();

    // 将棋盘转化为题目要求的格式
    public List<String> boardToList(int[] board){
        List<String> res = new ArrayList<>();
        for(int row = 0; row < board.length; row++){
            int col = board[row];
            char[] board_row = new char[board.length];
            Arrays.fill(board_row, '.');
            board_row[col] = 'Q';
            res.add(new String(board_row));
        }
        return res;
    }

    // 判断指定位置是否能够放置皇后
    public boolean isVaild(int[] board, int row, int col){
        for(int i = 0; i <= row; i++){
            // 判断同一列上是否存在皇后
            if(board[i] == col) return false;
            // 判断同一对角线上是否存在皇后
            if(Math.abs(i - row) == Math.abs(board[i] - col)) return false;
        }
        return true;
    }

    public void backTrack(int[] board, int row){
        // 如果最后一行已放完皇后,即表示满足一种放置方案,将该放置方案加入结果集
        if(row == board.length){
            this.ans.add(this.boardToList(board));
            return;
        }
        // 循环:在当前行的每一列尝试放置皇后
        for(int col = 0; col < board.length; col++){
            // 判断当前位置是否能够放置皇后
            if(this.isVaild(board, row, col)){
                board[row] = col; // 记录皇后放置的位置
                this.backTrack(board, row + 1); // 递归继续放置下一行
                board[row] = -1;  // 回溯
            }
        }
    }


    public List<List<String>> solveNQueens(int n) {
        // 使用一维数组来表示棋盘,数组的下标表示第几行,下标对应元素的值表示第几列
        int[] board = new int[n];
        // 填充值为-1,表示该行未放置皇后
        Arrays.fill(board, -1);
        this.backTrack(board, 0);
        return this.ans;
    }
}

解数独

题目详细:LeetCode.37

这道题我是先看题解再有自己的思路,然后做出来的,解题思路的过程都写在代码注释里了,详细的题解可查阅:《代码随想录》 — 解数独

class Solution {
    // 判断在指定位置填入指定数字的合法性
    public boolean isVaild(char[][] board, int row, int col, int val){
        // 判断同行同列是否存在重复的数值
        for(int i = 0; i < board.length; i++){
            if(board[row][i] == val) return false;
            if(board[i][col] == val) return false;
        }
        // 判断分隔的9宫格里是否存在重复的数值
        int startRow = (row / 3) * 3;
        int startCol = (col / 3) * 3;
        for (int i = startRow; i < startRow + 3; i++){
            for (int j = startCol; j < startCol + 3; j++){
                if (board[i][j] == val){
                    return false;
                }
            }
        }
        return true;
    }

    public boolean backTrack(char[][] board){
        // 用双重循环来遍历行和列
        for(int row = 0; row < board.length; row++){
            for(int col = 0; col < board.length; col++){
                // 用递归来尝试放置数字1~9
                if(board[row][col] == '.'){
                    for(char n = '1'; n <= '9'; n++){
                        if(this.isVaild(board, row, col, n)){
                            board[row][col] = n;
                            if(this.backTrack(board)){
                                // 如果在递归过程中已得到一种数独解,那么直接返回true结束搜索,无需继续求解其他结果
                                return true; 
                            }
                            board[row][col] = '.';
                        }
                    }
                    // 如果1~9都无法放置,说明该盘数独无解,返回false;
                    return false;
                }
            }
        }
        // 如果棋盘都已填入数字,即解出一种数独结果,返回true;
        return true;
    }

    public void solveSudoku(char[][] board) {
        this.backTrack(board);
    }
}

总结

详细的总结可查阅:《代码随想录》 — 回溯算法 — 总结


虽然最后的练习题难度都为Hard,但是一遍看题解,一遍自己理清思路,花的时间虽长,但还是啃下来了,痛快哉啊!

路漫漫其修远兮,吾将上下而求索。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值