首页 > 其他分享 >LeetCode -- 递归 dfs、回溯

LeetCode -- 递归 dfs、回溯

时间:2023-05-04 14:01:46浏览次数:43  
标签:return 题意 递归 -- dfs int buttonMap LeetCode

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));
        }
    }
}

 

标签:return,题意,递归,--,dfs,int,buttonMap,LeetCode
From: https://www.cnblogs.com/suBlog/p/17371026.html

相关文章

  • oracle修改用户密码的方法
    Oracle用户名及默认密码 修改oracle用户的密码有以下方法:普通用户 (1)通过alteruser语法来进行修改,这也是最常见的方式:(2)第二种方式,是通过password命令来修改:从安全性角度来说,推荐大家通过第二种方式来修改用户密码,这样可防止明文密码泄露。sys用户......
  • Uniapp HBuilderX 编译 运行到手机 内存不足:***** out of memory
    HBuilderX内置node版本是32位,如果遇到JavaScriptheapoutofmemory问题,可以自行下载64位的Node进行替换替换HBuilderX 内置的node.exe文件:HBuilderX\plugins\node\node.exe用自己安装的node里面的 node.exe文件即可。替换过后再次运行会提示安装对应的binding.node......
  • netstat命令端口状态
    TCP协议规定,对于已经建立的连接,网络双方要进行四次握手才能成功断开连接,如果缺少了其中某个步骤,将会使连接处于假死状态,连接本身占用的资源不会被释放。网络服务器程序要同时管理大量连接,所以很有必要保证无用连接完全断开,否则大量僵死的连接会浪费许多服务器资源。在众多TCP状态......
  • LVM入门基本操作
    学前了解:创建有两种方式,一种是基于磁盘的,另外一种是基于分区的,如果是基于分区的就要通过fdisk或parted方式划分好分区,不要格式化,如果基于磁盘的就不需要创建分区了,直接就可以通过创建物理卷。只有创建好了物理卷之后才能添加到卷组,并在卷组里面创建逻辑卷,后格式化才能存放数据。(物......
  • 基于ChatGPT的文档知识库客服系统-支持上传网址/文本/docx等数据
    现在,很多公司都有自己的内容知识库,会产生大量的碎片话的内部知识,但是这样内部知识难以整合搜索。我开发的文档知识库客服系统gofly.v1kf.com,可以应用于企业内部知识库管理,用户可以使用自然语言提问,让ChatGPT自动归纳总结企业知识信息,帮助员工快速获取所需知识,提升资源流转效率......
  • QQ和微信amr转mp3
    微信和QQ导出的amr音频文件,大家可以发现用一般播放器都是无法正常播放的。原因是虽然音频格式是amr,但却不是标准amr编码的,而是采用了Silkv3音频编码,导致很多播放器都播放不了。本工具可以对此类amr进行单个文件快速播放或批量格式转换成MP3。下载:链接:https://pan.baidu.com/s......
  • 医疗行业中台设计-前言
         这几年一直从事医疗行业数据治理相关工作。深入了解了这个行业的现状,未来并不知道是啥样。虽然医疗行业教其他行业起步很晚,各种新技术近几年来在该行业应用场景层次不穷,医疗行业确实有很大的挖掘价值,专攻专精的精神在最近几年各种概念伴随这PPT影响下,开始飘了起来。 ......
  • 内网工控机通过联网笔记本上网
    1、工控机与笔记本通过网卡连接。2、笔记本win11,工控机ubuntu14.043、笔记本设置共享上网  参考https://zhidao.baidu.com/question/505682783651825564.html,此文。  1)打开控制面板,进入WLAN的属性界面    2)确定后出现一个提示,笔记本的本地连接变成192.168......
  • DB - OLAP 和 OLTP
    总结OLTP(On-LineTransactionProcessing):联机事务处理,典型代表是关系型数据库(mysql),它的数据存储在服务器本地的文件里OLAP(On-LineAnalyticalProcessing):  联机分析处理,OLAP型数据库的典型代表是分布式文件系统(hive),它的数据存储在HDFS集群里详细说明场景和应用的区......
  • 阿克曼-彼得函数
    TheAckermannfunctionisarecursivefunctionthattakestwonon-negativeintegersasinputsandreturnsanon-negativeintegerasoutput.Thefunctionisdefinedasfollows: 阿克曼函数是一个数学函数,它是递归定义的,并且增长非常快。它是最简单的例子之一,它是一......