LeetCode -- 递归 dfs、回溯

发布时间 2023-05-04 13:59:53作者: archaique

22. 括号生成

 

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> result = new ArrayList();
        if (n == 0) {
            return result;
        }
        // 必须要用字符串,每次拼接要产生新对象。不能用 StringBuffer StringBuilder 之类,栈里不能都操作一个对象
        dfs(n, n, "", result);
        return result;
    }

    private void dfs(int leftN, int rightN, String currentNodeStr, List<String> result) {
        // 左右括号都用完
        if (leftN == 0 && rightN == 0) {
            result.add(currentNodeStr);
            return;
        }

        // 剩余的右括号不仅要 cover 掉剩余的左括号,还要 cover 掉现有没匹配上的左括号
        // 所以剩余的右括号一定要大于剩余的左括号
        if (rightN < leftN) {
            return;
        }

        // 左、右递归
        if (leftN > 0) {
            // 必须要 leftN-1 ,不能 leftN--
            dfs(leftN-1, rightN, currentNodeStr + "(", result);
        }
        if (rightN > 0) {
            // 必须要 rightN-1 ,不能 rightN--
            dfs(leftN, rightN-1, currentNodeStr + ")", result);
        }
    }
}

 

 

200. 岛屿数量

695. 岛屿的最大面积

精品题解 https://leetcode.cn/problems/number-of-islands/solution/dao-yu-lei-wen-ti-de-tong-yong-jie-fa-dfs-bian-li-/

注意深度优先遍历,对一格陆地(=='1')遍历, 就会把与它连通的所有陆地遍历到,全部标记为2, 完成一个岛屿。从而这一次标记到的作为一个岛屿数量 +1

for (int i=0; i<grid.length;i++) {
            for (int j=0;j<grid[0].length;j++) {
                // 深度优先遍历,对一格陆地(=='1')遍历,
                // 就会把与它连通的所有陆地遍历到,全部标记为2。从而这一次标记到的作为一个岛屿数量 +1
                if (grid[i][j] == '1') {
                    dfs(grid, i, j);
                    islandNum++;
                }
            }
        }

岛屿数量

class Solution {
    public int numIslands(char[][] grid) {
        int islandNum = 0;
        for (int i=0; i<grid.length;i++) {
            for (int j=0;j<grid[0].length;j++) {
                // 深度优先遍历,对一格陆地(=='1')遍历,
                // 就会把与它连通的所有陆地遍历到,全部标记为2。从而这一次标记到的作为一个岛屿数量 +1
                if (grid[i][j] == '1') {
                    dfs(grid, i, j);
                    islandNum++;
                }
            }
        }
        return islandNum;
    }

    private void dfs(char[][] grid, int x, int y) {
        if (!isInGrid(grid, x, y)) {
            return;
        }
        if (grid[x][y] != '1') {
            return;
        }
        // 访问过的标记为2,不再重复访问
        grid[x][y] = '2';
        // 对这个格子的上下左右分别dfs
        //
        dfs(grid, x, y+1);
        //
        dfs(grid, x, y-1);
        //
        dfs(grid, x-1, y);
        //
        dfs(grid, x+1, y);
    }

   // 判断这个格子是否已经超出了 grid 的范围
    private boolean isInGrid(char[][] grid, int x, int y) {
        // 注意是 >= 0 不是 >0
        return x >= 0 && x < grid.length && y >= 0 && y < grid[0].length;
    }
}

 

岛屿的最大面积

class Solution {
    public int maxAreaOfIsland(int[][] grid) {
        int maxAreaOfIsland = 0;
        for (int i=0;i<grid.length;i++) {
            for (int j=0;j<grid[0].length;j++) {
                // 每次 dfs 完的都是一整块岛屿
                if (grid[i][j] == 1) {
                    int thisIslandArea = dfs(grid, i, j);
                    maxAreaOfIsland = Math.max(maxAreaOfIsland, thisIslandArea);
                }
            }
        }
        return maxAreaOfIsland;
    }

    private int dfs(int[][] grid, int x, int y) {
        if (!isInGrid(grid, x, y)) {
            return 0;
        }
        if (grid[x][y] != 1) {
            return 0;
        }
        grid[x][y] = 2;

        // 自己1 + 上下左右
        return 1 + dfs(grid, x, y+1)
                + dfs(grid, x, y-1)
                + dfs(grid, x-1, y)
                + dfs(grid, x+1, y);
    }

    private boolean isInGrid(int[][] grid, int x, int y) {
        return x>=0 && x<grid.length && y>=0 && y<grid[0].length;
    }
}

 

 

51. N 皇后

在类中定义一个全局的结果

树的最大层数是行,每个分支是列,每次递归行数加一,循环对每一列进行递归

递归函数的最开始,判断 如果行数已经到了最后一行(=n),添加到全局结果中,return

判断可以放置,就先在棋盘上放置好(设为 "Q"),再进行递归

注意如果 判断不可以放置,不用 return,而是回来之后恢复现场,重新设为 ".",也就是回溯恢复现场

判断能否放置:检查此次皇后放置位置的同一列的上半部分。以及左上斜对角和右上斜对角(下半部分不用检查),检查斜对角的时候注意是单层循环,ij行列同时加减,不是双层循环

 

 关于回溯

先将第一个皇后放在第一行的第一列上,符合题目要求

开始放置第二个皇后。放在第二行的第一个与第一行的皇后为同一列,不符合题意,继续向后搜素,放在第二列上面与第一个皇后在同一斜线上,不符合题意,继续向后搜素,发现放在第三列符合题意

开始放置第三个皇后。放在第三行的任意位置都会出现冲突,此时需要回溯,将第二个皇后放置在第四列,此时符合题意,继续放置第三个皇后,发现第三个皇后放置在第三行的第二列符合题意

继续放置第四个皇后。放在第四行的任意位置都会出现冲突,此时需要回溯,第三个皇后向后移动,发现依然不符合题意,继续回溯,第二行的皇后无法再向后移动,继续回溯,将第一个皇后向后移动到第二列,符合题意

移动第二个皇后,发现放在第四列符合题意

移动第三个皇后,发现放在第一列符合题意

移动第四个皇后,发现放在第三列符合题意

回溯结束

 

class Solution {


    private static List<List<String>> res = new ArrayList();


    public Solution() {
        // 不知道为啥,提交答案的时候似乎有之前用例的残余
        res.clear();
    }

    public List<List<String>> solveNQueens(int n) {
        dfs(n, getInitChessBoard(n), 0);
        return res;
    }

    private void dfs(int n, char[][] chessBoard, int newQueenRow) {
        if (newQueenRow == n) {
            res.add(char2DArray2StringList(chessBoard, n));
            return;
        }


         // 对每个分支 dfs。每个分支是每一列。(列的变化通过在 chessBoard 的放置传递进去,而层数即行的变化需要作为显式参数)
        for (int newQueenCol=0;newQueenCol<n;++newQueenCol) {
            // 不能 return 返回,因为对那些不满足条件的,要回溯,即回去恢复现场
            /* if (!canSet(n, chessBoard, newQueenRow, newQueenCol)) {
                return;
            }
            */
            if (canSet(n, chessBoard, newQueenRow, newQueenCol)) {
                // 可以设置了再设置
                chessBoard[newQueenRow][newQueenCol] = 'Q';
                dfs(n, chessBoard, newQueenRow + 1);
                // 回溯就指的是这一步,不满足条件的不要 return退出搜索,而是到这里回来后恢复现场
                chessBoard[newQueenRow][newQueenCol] = '.';
            }
        }
    }

    private boolean canSet(int n, char[][] chessBoard, int newQueenRow, int newQueenCol) {
        // 不用检查同一行(每次 dfs 层数 +1 也就是说限制了一行只能放置一个)

        // 检查同一列上面
        for (int i=0; i<newQueenRow; i++) {
            // 不是自己且为 Q
            if (chessBoard[i][newQueenCol] == 'Q') {
                return false;
            }
        }

        // 检查左上斜对角(不用检查下半截)
        // 注意不是双层循环,斜对角要横纵坐标同进同退,是单层循环
        for (int i=newQueenRow-1,j=newQueenCol-1;i>=0 && j>=0; i--,j--) {
            if (chessBoard[i][j] == 'Q') {
                return false;
            }
        }

        // 检查右上斜对角(不用检查下半截)
        // 注意不是双层循环,斜对角要横纵坐标同进同退,是单层循环
        for (int i=newQueenRow-1,j=newQueenCol+1;i>=0 && j<n; i--,j++) {
            if (chessBoard[i][j] == 'Q') {
                return false;
            }
        }
        return true;
    } 

    private char[][] getInitChessBoard(int n) {
        char[][] initChessBoard = new char[n][n];
        for (int i=0;i<n;i++) {
            for (int j=0;j<n;j++) {
                initChessBoard[i][j] = '.';
            }
        }
        return initChessBoard;
    }

    private List<String> char2DArray2StringList(char[][] charArray, int n) {
        List<String> list = new ArrayList();
        for (int i=0;i<n;i++) {
            // copy 一行的字符 到 字符串 s
            String s = String.copyValueOf(charArray[i], 0, n);
            list.add(s);
        }
        return list;
    }

   
}

 

 

17. 电话号码的字母组合

递归树的层数是按钮的总个数,每次递归层数加一
递归树的分支是每个按钮上对应的那几个字符,因此每次递归对这几个字符分别递归

class Solution {

    public Solution() {
        resList.clear();
    }

    private List<String> resList = new ArrayList();

    private static Map<Character, List<Character>> buttonMap = new HashMap();

    static {
        buttonMap.put('2', Arrays.asList('a', 'b', 'c'));
        buttonMap.put('3', Arrays.asList('d', 'e', 'f'));
        buttonMap.put('4', Arrays.asList('g', 'h', 'i'));
        buttonMap.put('5', Arrays.asList('j', 'k', 'l'));
        buttonMap.put('6', Arrays.asList('m', 'n', 'o'));
        buttonMap.put('7', Arrays.asList('p', 'q', 'r', 's'));
        buttonMap.put('8', Arrays.asList('t', 'u', 'v'));
        buttonMap.put('9', Arrays.asList('w', 'x', 'y', 'z'));
    }


    public List<String> letterCombinations(String digits) {
        if (digits == null || "".equals(digits)) {
            return resList;
        }
        dfs(digits, 0, "");
        return resList;
    }

    private void dfs(String digits, int curButtonLayer, String curRes) {
        // 走到最后一层叶子节点,结果获得,递归结束
        if (curButtonLayer == digits.length()) {
            resList.add(curRes);
            return;
        }

        //根据层数获得当前按钮
        Character curButton = digits.charAt(curButtonLayer);
        //根据当前按钮获得当前按钮上的字符
        List<Character> buttonChars = buttonMap.get(curButton);

        // 递归树的分支是每个按钮上对应的那几个字符,因此每次递归对这几个字符分别递归
        for (int i=0;i<buttonChars.size();i++) {
            // 递归树的层数是按钮的总个数,每次递归层数加一
            dfs(digits, curButtonLayer + 1, curRes+buttonChars.get(i));
        }
    }
}