代码随想录算法训练营第三十天 | 332.重新安排行程、51. N皇后、37. 解数独、回溯总结

332.重新安排行程

题目链接:https://leetcode.cn/problems/reconstruct-itinerary/
文档讲解:https://programmercarl.com/0332.%E9%87%8D%E6%96%B0%E5%AE%89%E6%8E%92%E8%A1%8C%E7%A8%8B.html
视频讲解:https://www.bilibili.com/video/BV1cy4y167mM/

思路

  • 选择合适的容器:选择Map来存储目的地信息,并且用一个Integer来存储机票的次数,当使用对应机票,就将次数-1,防止出现死循环。
Map<String, Map<String, Integer>> map = new HashMap<String, Map<String, Integer>>();
  • 回溯的返回值:本题的返回值是boolean,和之前的void不同。因为本题只用找到一条合适的路线就可以返回了,不像之前的题目要搜索所有的排列组合。

代码

class Solution {
    Map<String, Map<String, Integer>> map;
    Deque<String> res;
    public List<String> findItinerary(List<List<String>> tickets) {
        map = new HashMap<String, Map<String, Integer>>();
        res = new LinkedList<>();
        for (List<String> t : tickets) {
            Map<String, Integer> temp; //记录一个终点以及次数
            if (map.containsKey(t.get(0))) { // 如果map中存在t中的起点,就把终点及其对应的次数+1
                temp = map.get(t.get(0));
                temp.put(t.get(1), temp.getOrDefault(t.get(1), 0) + 1);
            } else { // 如果map中不存在t的起点,就在map中存入起点,终点和次数1
                temp = new TreeMap<>(); // 升序map
                temp.put(t.get(1), 1);
            }
            map.put(t.get(0), temp);
        }
        res.add("JFK");
        backtracking(tickets.size());
        return new ArrayList<>(res);
    }

    public boolean backtracking(int ticketNum) {
        if (res.size() == ticketNum + 1) return true;
        String last = res.getLast();
        if (map.containsKey(last)) { // 防止出现null
            for (Map.Entry<String, Integer> target : map.get(last).entrySet()) {
                int count = target.getValue();
                if (count > 0) {
                    res.add(target.getKey());
                    target.setValue(count - 1);
                    if (backtracking(ticketNum)) return true; // 如果下面传上来的是true,说明有了结果,就也返回true,不继续回溯
                    res.removeLast();
                    target.setValue(count);
                }
            }
        }
        return false;
    }
}

51. N皇后

题目链接:https://leetcode.cn/problems/n-queens/
文档讲解:https://programmercarl.com/0051.N%E7%9A%87%E5%90%8E.html
视频讲解:https://www.bilibili.com/video/BV1Rd4y1c7Bq/

思路

  • 二维矩阵中矩阵的高就是这棵树的高度,矩阵的宽就是树形结构中每一个节点的宽度。那么我们用皇后们的约束条件,来回溯搜索这棵树,只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了。
  • 递归函数参数
    定义全局变量二维列表res来记录最终结果。参数n是棋盘的大小,然后用row来记录当前遍历到棋盘的第几层了。
List<List<String>> res = new ArrayList<>();
public void backtracking(char[][] chessBoard, int n, int row) {
  • 递归终止条件
    当递归到棋盘最底层(也就是叶子节点)的时候,就可以收集结果并返回了。
if (row == n) {
    res.add(ArraytoList(chessBoard));
    return;
}
  • 单层搜索的逻辑
    递归深度就是row控制棋盘的行,每一层里for循环的i控制棋盘的列,一行一列,确定了放置皇后的位置。每次都是要从新的一行的起始位置开始搜,所以都是从0开始。
for (int i = 0; i < n; i++) {
    if (isValid(row, i, n, chessBoard)) {
        chessBoard[row][i] = 'Q';
        backtracking(chessBoard, n, row + 1);
        chessBoard[row][i] = '.';
    }
}

代码

class Solution {
    List<List<String>> res = new ArrayList<>();
    public List<List<String>> solveNQueens(int n) {
        char[][] chessBoard = new char[n][n];
        for (char[] c : chessBoard) {
            Arrays.fill(c, '.');
        }
        backtracking(chessBoard, n, 0);
        return res;
    }

    public void backtracking(char[][] chessBoard, int n, int row) {
        if (row == n) {
            res.add(ArraytoList(chessBoard));
            return;
        }
        for (int i = 0; i < n; i++) {
            if (isValid(row, i, n, chessBoard)) {
                chessBoard[row][i] = 'Q';
                backtracking(chessBoard, n, row + 1);
                chessBoard[row][i] = '.';
            }
        }
    }

    public boolean isValid(int row, int col, int n, char[][] chessBoard) {
        for (int i = 0; i < row; i++) { // 检查列
            if (chessBoard[i][col] == 'Q') return false;
        }
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { // 检查45°
            if (chessBoard[i][j] == 'Q') return false;
        }
        for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { // 检查135°
            if (chessBoard[i][j] == 'Q') return false;
        }
        return true;
    }

    public List<String> ArraytoList(char[][] chessBoard) {
        List<String> list = new ArrayList<>();
        for (char[] c : chessBoard) {
            list.add(String.copyValueOf(c));
        }
        return list;
    }
}

37. 解数独

题目链接:https://leetcode.cn/problems/sudoku-solver/
文档讲解:https://programmercarl.com/0037.%E8%A7%A3%E6%95%B0%E7%8B%AC.html
视频讲解:https://www.bilibili.com/video/BV1TW4y1471V/

思路

  • 本题是二维递归,需要两个循环加一个递归,两个循环来确定一个格子,递归来判断格子中是否能放1-9。
  • 递归的返回值是boolean,找到一种解法就可以返回了。

代码

class Solution {
    public void solveSudoku(char[][] board) {
        backtracking(board);
    }

    public boolean backtracking(char[][] board) {
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (board[i][j] == '.') {
                    for (char k = '1'; k <= '9'; k++) {
                        if (isValid(i, j, k, board)) {
                            board[i][j] = k;
                            if (backtracking(board)) return true; // 如果找到合适一组立刻返回
                            board[i][j] = '.';
                        }
                    }
                    return false; // 9个数都试完了,都不行,那么就返回false
                }
            }
        }
        return true; // 遍历完没有返回false,说明找到了合适棋盘位置了
    }
    public boolean isValid(int row, int col, char c, char[][] board) {
        for (int i = 0; i < board.length; i++) {
            if (i != row && board[i][col] == c) return false;
            if (i != col && board[row][i] == c) return false;
        }

        for (int i = (row / 3) * 3; i < (row / 3) * 3 + 3; i++) {
            for (int j = (col / 3) * 3; j < (col / 3) * 3 + 3; j++) {
                if (i != row && j != col && board[i][j] == c) return false;
            }
        }
        return true;
    }
}

回溯部分总结

文档讲解:https://programmercarl.com/%E5%9B%9E%E6%BA%AF%E6%80%BB%E7%BB%93.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值