【刷题系列】深度优先搜索(DFS)

系列汇总:《刷题系列汇总》



——————《剑指offeer》———————

1. 重建二叉树(二叉树 数组 DFS)

  • 题目描述:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
  • 优秀思路:【递归】根据前序列确定根节点,再根据根节点在中序序列中的位置划分左、右子数,切割出对应的中序子序列和前序子序列作为参数,递归该过程。
import java.util.Arrays;
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        //边界条件
        if(pre == null || in ==  null || pre.length == 0 || in.length == 0) return null;
        TreeNode tree = new TreeNode(pre[0]);
        for(int i = 0;i< in.length;i++){
            if(in[i] == pre[0]){
                tree.left = reConstructBinaryTree(Arrays.copyOfRange(pre,1,i+1),Arrays.copyOfRange(in,0,i));
                tree.right = reConstructBinaryTree(Arrays.copyOfRange(pre,i+1,pre.length),Arrays.copyOfRange(in,i+1,in.length));
                break;
            }
        }
        return tree;
    }
}

2. 平衡二叉树

  • 题目描述:输入一棵二叉树,判断该二叉树是否是平衡二叉树。在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树。
    平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
  • 优秀思路:【递归】递归计算左右子数的高度,最后比较两边的高度差。
public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        if(root == null) return true;
        int leftHeight = getHeight(root.left); // 左子树高度
        int rightHeight = getHeight(root.right);// 右子树高度
        if(Math.abs(leftHeight-rightHeight) <= 1) return true;
        return false;
    }
    // 计算数的高度
    private int getHeight(TreeNode node){
        if(node == null) return 0;
        int leftHeight = getHeight(node.left);
        int rightHeight = getHeight(node.right);
        // 核心:某子树的高度由长的一边决定
        return 1 + (leftHeight > rightHeight ? leftHeight :rightHeight);
    }
}

3. 矩阵中的路径

  • 题目描述:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如下图矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
    在这里插入图片描述
  • 我的思路(70% - 70%):【递归】首先找出所有的起始点,对每个起始点,判断当前起始点的未被访问的近邻是否存在=当前字符串首元素的,知道string长度为1(注意二维数组不能直接用"="赋值,否则一个改变另一个也会改变)
import java.util.*;
public class Solution {
    public boolean hasPath (char[][] matrix, String word) {
        if(matrix.length*matrix[0].length < word.length()) return false;
        boolean[][] visited;
        // 找到首字母在 matrix 中的位置
        for(int i = 0;i < matrix.length;i++){
            for(int j = 0;j < matrix[0].length;j++){
                if(matrix[i][j] == word.charAt(0)){
                    if(word.length() >= 2){
                        // 这个得每次重新创建
                        visited = new boolean[matrix.length][matrix[0].length];
                        visited[i][j] = true; 
                        if(move(matrix,visited,i,j,word.substring(1,word.length()))) return true;
                    }else{
                        return true;
                    }
                }
            }
        }
        return false;
    }
    // 根据当前位置判断其未被访问的近邻中是否存在符合要求的字母
    private boolean move(char[][] matrix,boolean[][] visited,int curRow,int curCol,String restWord){
        if(restWord == null) return false;
        boolean[][] tempVisited;
        String tempWord;
        boolean tag1=false, tag2=false, tag3=false, tag4=false;
        int flag = 0; //当前位置是否有未被访问的近邻 == restWord.charAt(0)
        if(curCol+1 <= matrix[0].length-1){ // 可右移
            if(!visited[curRow][curCol+1]){
                if(matrix[curRow][curCol+1] == restWord.charAt(0)){
                    flag = 1;
                    if(restWord.length() >= 2){ // 还有未找到的字母
                        tempVisited = copy(visited);
                        tempVisited[curRow][curCol+1] = true;
                        tempWord = restWord.substring(1,restWord.length());
                        tag1 = move(matrix,tempVisited,curRow,curCol+1,tempWord);
                    }else{
                        return true;
                    }
                }
            }
            
        }
        if(curCol-1 >= 0){ // 可左移
            if(!visited[curRow][curCol-1]){
                if(matrix[curRow][curCol-1] == restWord.charAt(0)){
                    flag = 1;
                    if(restWord.length() >= 2){ // 还有未找到的字母
                        tempVisited = copy(visited);
                        tempVisited[curRow][curCol-1] = true;
                        tempWord = restWord.substring(1,restWord.length());
                        tag2 = move(matrix,tempVisited,curRow,curCol-1,tempWord);
                    }else{
                        return true;
                    }
                }
            }
            
        }
        if(curRow-1 >= 0){ // 可上移
            if(!visited[curRow-1][curCol]){
                if(matrix[curRow-1][curCol] == restWord.charAt(0)){
                    flag = 1;
                    if(restWord.length() >= 2){ // 还有未找到的字母
                        tempVisited = copy(visited);
                        tempVisited[curRow-1][curCol] = true;
                        tempWord = restWord.substring(1,restWord.length());
                        tag3 = move(matrix,tempVisited,curRow-1,curCol,tempWord);
                    }else{
                        return true;
                    }
                }
            }
        }
        if(curRow+1 <= matrix.length-1){ // 可下移
            if(!visited[curRow+1][curCol]){
                if(matrix[curRow+1][curCol] == restWord.charAt(0)){
                    flag = 1;
                    if(restWord.length() >= 2){ // 还有未找到的字母
                        tempVisited = copy(visited);
                        tempVisited[curRow+1][curCol] = true;
                        tempWord = restWord.substring(1,restWord.length());
                        tag4 = move(matrix,tempVisited,curRow+1,curCol,tempWord);
                    }else{
                        return true;
                    }
                }
            }
        }
        if(flag == 0) return false;
        return tag1 || tag2 || tag3 || tag4; // 有一个路线走的通就行
    }
    // 二维数组复制
    private boolean[][] copy(boolean[][] visited){
        boolean[][] res = new boolean[visited.length][visited[0].length];
        for(int i = 0;i < visited.length;i++){
            for(int j = 0;j < visited[0].length;j++){
                res[i][j] = visited[i][j];
            }
        }
        return res;
    }
}
  • 优秀思路:思路类似,代码精简,不用上下左右每部分各写一个,写成一个即可
import java.util.*;
public class Solution {
    public boolean hasPath (char[][] matrix, String word) {
        if(matrix.length * matrix[0].length < word.length()) return false;
        boolean[][] visited = new boolean[matrix.length][matrix[0].length];
        for(int i = 0;i < matrix.length;i++){
            for(int j = 0;j < matrix[0].length;j++){
                if(matrix[i][j] == word.charAt(0)){ // 起点
                    if(move(matrix,visited,i,j,word)) return true;
                }
            }
        }
        return false;
    }
    // 根据当前位置判断其未被访问的近邻中是否存在符合要求的字母
    private boolean move(char[][] matrix,boolean[][] visited,int curRow ,int curCol ,String restWord){
    	// 写成这种形式,以应对各种情况
        if(curRow < 0||curCol < 0 || curRow >= matrix.length || curCol >= matrix[0].length || visited[curRow][curCol]) return false;
        if(matrix[curRow][curCol] == restWord.charAt(0)){
            if(restWord.length()==1) return true; // 比较到最后一位
            else{ // 比较其他位
                visited[curRow][curCol] = true;
                // 上、下、左、右有一条线路走得通即可
                if(move(matrix,visited,curRow+1,curCol,restWord.substring(1,restWord.length()))
                    || move(matrix,visited,curRow-1,curCol,restWord.substring(1,restWord.length()))
                    || move(matrix,visited,curRow,curCol+1,restWord.substring(1,restWord.length()))
                    || move(matrix,visited,curRow,curCol-1,restWord.substring(1,restWord.length()))){// 有其中一个方向走的通
                    return true;
                }else{ // 当前线路四个方向都走不通,恢复访问状态
                    visited[curRow][curCol] = false;
                    return false;
                }
            }
        }else{
            return false;
        }
    }
}

——————《LeectCode》———————

1. 岛屿数量

  • 题目描述:给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。
  • 我的思路(优秀):【深度优先搜索】每发现一个新岛屿,就采用递归的方式标记其所有相连陆地
class Solution {
    public int numIslands(char[][] grid) {
        if(grid.length == 0 || grid[0].length == 0) return 0;
        int count = 0; // 岛屿数量
        boolean[][] vis = new boolean[grid.length][grid[0].length]; // 访问状态
        for(int i = 0;i < grid.length;i++){
            for(int j = 0;j < grid[0].length;j++){
                if(grid[i][j]=='1' && !vis[i][j]){ // 发现新岛屿起点
                    count++; 
                    vis = visUpdate(grid,vis,i,j);// 标记该岛屿所有位置
                }
            }
        }
        return count;
    }
    // 标记岛屿
    private boolean[][] visUpdate(char[][] grid,boolean[][] vis,int i,int j){
        if(i<0 || j<0 || i>=grid.length || j>= grid[0].length) return vis;
        if(grid[i][j] == '1' && !vis[i][j]){ // 发现新的相连陆地
            vis[i][j] = true;
            // 继续寻找该陆地的相邻陆地
            vis = visUpdate(grid,vis,i,j+1); // 右边
            vis = visUpdate(grid,vis,i+1,j); // 下边
            vis = visUpdate(grid,vis,i,j-1); // 左边
            vis = visUpdate(grid,vis,i-1,j); // 上边
        }
        return vis; 
    }
}
  • 优秀思路2(不如1):【广度优先搜索】主循环和DFS类似,不同点是在于搜索某岛屿边界的方法不同。借用一个队列 queue,判断队列首部节点 (i, j) 是否未越界且为 1:若是则置零(删除岛屿节点),并将此节点上下左右节点 (i+1,j),(i-1,j),(i,j+1),(i,j-1) 加入队列;若不是则跳过此节点;循环 pop 队列首节点,直到整个队列为空,此时已经遍历完此岛屿。
class Solution {
    public int numIslands(char[][] grid) {
        int count = 0;
        for(int i = 0; i < grid.length; i++) {
            for(int j = 0; j < grid[0].length; j++) {
                if(grid[i][j] == '1'){
                    bfs(grid, i, j);
                    count++;
                }
            }
        }
        return count;
    }
    private void bfs(char[][] grid, int i, int j){
        Queue<int[]> list = new LinkedList<>();
        list.add(new int[] { i, j });
        while(!list.isEmpty()){ // 停止条件是删除完该岛屿所有陆地
            int[] cur = list.remove(); // 移除当前陆地
            i = cur[0]; j = cur[1]; // 并取出其位置
            if(0 <= i && i < grid.length && 0 <= j && j < grid[0].length && grid[i][j] == '1') { // 若其为陆地
                grid[i][j] = '0'; // 删除该陆地
                // 放入其4个邻居,重复上述过程
                list.add(new int[] { i + 1, j });
                list.add(new int[] { i - 1, j });
                list.add(new int[] { i, j + 1 });
                list.add(new int[] { i, j - 1 });
            }
        }
    }
}

2. 二叉树的最大深度

  • 题目描述:给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
  • 优秀思路(我的):【深度优先搜索】递归
class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) return 0;
        int leftDepth = maxDepth(root.left);
        int rightDepth = maxDepth(root.right);
        return Math.max(leftDepth,rightDepth) + 1;
    }
}

3. 查找集群内的关键连接(困难)

  • 题目描述:力扣数据中心有 n 台服务器,分别按从 0n-1 的方式进行了编号。它们之间以「服务器到服务器」点对点的形式相互连接组成了一个内部集群,其中连接 connections 是无向的。从形式上讲,connections[i] = [a, b] 表示服务器 a 和 b 之间形成连接。任何服务器都可以直接或者间接地通过网络到达任何其他服务器。关键连接是在该集群中的重要连接,也就是说,假如我们将它移除,便会导致某些服务器无法访问其他服务器。请你以任意顺序返回该集群内的所有关键连接。
  • 优秀思路:使用tarjan算法找出图中非强连通部分和强连通部分,过程中如果某连接两边的节点的id不同,则加入ans。参考:tarjan算法讲解视频
class Solution {
    /* 参数定义在主函数中,可被各个子函数访问到 */
    int time; // 时间戳
    int[] visTime; // 节点的访问时间
    int[] minTime; // 节点能回溯到的最早时间(相当于id,若该值相同则为同一ID)
    List<List<Integer>> ans = new ArrayList<>(); // 结果
    List<Integer>[] conn; // 存储服务器之间的连接关系(给出的格式太复杂了)

    public List<List<Integer>> criticalConnections(int n, List<List<Integer>> connections) {

        /* 参数初始化 */
        conn = new ArrayList[n];
        visTime = new int[n];
        minTime = new int[n];
        time = 1;

        /* 找出服务器之间的连接关系并存储 */
        for (int i = 0; i < n; i++) {
            conn[i] = new ArrayList<>(); // 为每个服务器建立一个序列,方便add于其相连的服务器
        }
        for (List<Integer> connection : connections) {
            int v1 = connection.get(0), v2 = connection.get(1); // 找出所有连接上的2个服务器
            conn[v1].add(v2);
            conn[v2].add(v1);
        }
        /* 遍历服务器0 */ 
        dfs(0, -1);
        return ans;
    }

    /* 遍历各服务器,寻找其连通关系 */
    public void dfs(int cur, int pre) { // 无向图需要记录上个节点(pre),防止子节点访问父节点
        visTime[cur] = minTime[cur] = time++; // 可连续赋值
        for (int next : conn[cur]) { // 遍历可能访问的下个节点
            if (next == pre) continue; // 如果访问的是父节点,跳过
            if (visTime[next] == 0) { // 如果未访问(数组默认值为0)
                dfs(next, cur);
                minTime[cur] = Math.min(minTime[cur], minTime[next]); // 更新minTime,即ID
                if (minTime[next] > visTime[cur]) { // 下一节点id不可回溯到当前节点
                    ans.add(Arrays.asList(cur, next));
                }
            } else { // 如果访问过
                minTime[cur] = Math.min(minTime[cur], visTime[next]);
            }
        }
    }
}

4.将有序数组转换为二叉搜索树

  • 题目描述:给你一个整数数组 nums ,其中元素已经按升序排列,请你将其转换为一棵高度平衡二叉搜索树。高度平衡二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
  • 优秀思路(我的):【递归】升序排列即为二叉搜索树的中序遍历(左根右),因此递归的寻找中间节点作为根节点,根节点左边的作为左子树,右边的为右子树
class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        if(nums == null || nums.length == 0) return null;
        int mid = nums.length/2;
        TreeNode tree = new TreeNode(nums[mid]);
        tree.left = sortedArrayToBST(Arrays.copyOfRange(nums,0,mid));
        tree.right = sortedArrayToBST(Arrays.copyOfRange(nums,mid+1,nums.length));
        return tree;
    }
}

5. 删除无效的括号(困难)

  • 题目描述:给你一个由若干括号和字母组成的字符串 s ,删除最小数量的无效括号,使得输入的字符串有效。返回所有可能的结果。答案可以按 任意顺序 返回。
  • 优秀思路:判断字符串类型
    • 如果闭合括号>开始括号,则递归逐个找到不符合条件的第一个闭合处,从0到第一个闭合处,每个闭合括号都进行删除尝试(这个区间的每个闭合括号都是有可能可以被删除的)。
    • 如果开始括号 > 闭合括号,则交换两括号、颠倒字符串,从而又变成了第一种情况。
class Solution {
    public List<String> removeInvalidParentheses(String s) {
        List<String> res = new ArrayList<>();
        dfs(s, res, '(', ')', 0, 0);
        return res;
    }
    // i 当前遍历到的字符位置
    private void dfs(String s, List<String> res, char opening, char closing, int i, int lastRemoved) {
        int count = 0;
        // 找到第一个不合法的closing处
        while (i < s.length() && count >= 0) { // 注意count<0会跳出
            if (s.charAt(i) == opening) {
                count++;
            } else if (s.charAt(i) == closing) {
                count--;
            }
            i++;
        }
        if (count < 0) { // 闭合括号更多,删除
            for (int j = lastRemoved; j < i; j++) {
                // 该括号为闭合括号 且 该括号要么位于首位,要么该括号前一位不是闭合括号
                // 即 ① 闭合括号位于首位必删
                //    ② 为避免取得相同结果,连续几个闭合括号,删一个即可
                if (s.charAt(j) == closing && (j == 0 || s.charAt(j - 1) != closing)) { 
                	// i 即本次搜寻到的第一个无效括号处,i-1 是下次搜索无效括号的起点
                	// j 即本次删除无效闭合括号的地方,也是下次删除的起点
                    dfs(s.substring(0, j) + s.substring(j + 1), res, opening, closing, i - 1, j); 
                }
            }
        } else if (count > 0) { //开始括号更多,交换开始和闭合括号,并且颠倒字符串再来一遍
            dfs(new StringBuilder(s).reverse().toString(), res, closing, opening, 0, 0);
        } else { // 平衡,加入结果
            if (opening == '(') { // 说明是此时为正向
                res.add(s);
            } else { // 反向
                res.add(new StringBuilder(s).reverse().toString());
            }
        }
    }
}

6. 字符串解码

  • 题目描述:给定一个经过编码的字符串,返回它解码后的字符串。编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a2[4] 的输入。
  • 我的思路(7% - 25 %):需要反复的从头开始遍历,效率低
    • 1、找到最里层完整码段(数字 + [ + 字母 + ])先进行变换
    • 2、将s更新为:码段前 + 变换码段 +码段后
    • 3、递归上述过程,直至s内无数字
class Solution {
    public String decodeString(String s) {
        // 找到最里面的括号
        int flag = 0; // 是否寻到完整码段:数字【字母】
        StringBuilder strNum = new StringBuilder();
        StringBuilder sGernerate = new StringBuilder();
        int lenNum = 0; // 数字的位数
        int endNum = 0; // 数字的结束位置
        int index2 = 0; // ]的后一位置
        int stateNum = 0;
        String strChar = ""; // 存储每段字母串
        for(int i = 0;i < s.length();i++){
            flag = 0;
            if(s.charAt(i) <= '9' && s.charAt(i) >= '0'){
                strNum.append(s.charAt(i));
                endNum = i;
                lenNum++;
                stateNum = 1;
            }else if(s.charAt(i)=='['){
                strChar = "";
                for(int j = i+1;j < s.length();j++){
                    if((s.charAt(j) <= '9' && s.charAt(j) >= '0') || s.charAt(j)=='['){ // 遇到数字或者新括号作废
                        strNum.delete( 0, strNum.length());
                        lenNum = 0;
                        break;
                    }else if(s.charAt(j) <= 'z' && s.charAt(j) >= 'a'){
                        strChar += s.charAt(j);
                    }else if(s.charAt(j)==']'){
                        index2 = j+1;
                        flag = 1;
                        break;
                    }
                }
            }
            // 根据【前数字重复该字符串
            if(flag == 1){
                sGernerate.delete( 0, sGernerate.length());
                for(int ind = 0;ind<Integer.valueOf(strNum.toString());ind++){
                    sGernerate.append(strChar);
                }
                s = s.substring(0,endNum-lenNum+1) + sGernerate.toString() + s.substring(index2); // 
                lenNum = 0;
                strNum.delete( 0, strNum.length());
            }
        }
        if(stateNum == 0) return s; // 没有数字了
        return decodeString(s);
    }
}
  • 优秀思路:递归
    • 发现数字位:对数字为后面的字符串递归得到需要复制的字符串,对字符串进行复制
    • 发现字母位:存储起来
    • 返回 未复制的字母+字符串
class Solution {
    String str;
    int index;

    public String decodeString(String s) {
        str = s;
        index = 0;
        return getString();
    }
	
    private String getString(){
        if(index == str.length() || str.charAt(index) == ']') return "";

        char cur = str.charAt(index); //当前字符
        int times = 1; // 复制倍数
        String Res = "";

        if(Character.isDigit(cur)){
            times = getTimes(); //解析数字
            index++; // 跳过左[
            String tempString = getString(); //解析括号内部字符串,即需复制字符串
            index++; // 跳过右[
            while(times-- > 0){ //构造字符串
                Res += tempString;
            }
        }else if(Character.isLetter(cur)){
            Res = String.valueOf(str.charAt(index++)); // 速度比c+""快多了
        }
        return Res + getString(); // 返回注意

    }
	// 解析多位数字
    private int getTimes(){
        int num = 0;
        while(index < str.length() && Character.isDigit(str.charAt(index))){
            num = 10*num + str.charAt(index++) - '0';
        }
        return num;
    }
}

7. 相同的树

  • 题目描述:给你两棵二叉树的根节点 pq ,编写一个函数来检验这两棵树是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
  • 优秀思路(我的):【递归】递归寻找左右子树比较根节点
class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q == null) return true;
        if(p == null || q == null || p.val != q.val) return false;
        boolean x1 = isSameTree(p.left,q.left);
        boolean x2 = isSameTree(p.right,q.right);
        return x1 && x2;
    }
}

8. 从前序与中序遍历序列构造二叉树

  • 题目描述:根据一棵树的前序遍历与中序遍历构造二叉树(树中无重复元素)
  • 我的思路(7% - 8%):【递归】根据前序遍历找到根节点,根据根节点在中序遍历中的位置找到左子树和右子树
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder == null || preorder.length==0 || inorder == null || inorder.length == 0) return null;
        // 根据前序遍历找到根节点
        TreeNode newNode = new TreeNode(preorder[0]);
        for(int i = 0;i<inorder.length;i++){
            if(inorder[i] == preorder[0]){
                // 根据根节点在中序遍历中的位置找到左子树和右子树
                newNode.left = buildTree(Arrays.copyOfRange(preorder,1,i+1),
                    Arrays.copyOfRange(inorder,0,i));
                newNode.right = buildTree(Arrays.copyOfRange(preorder,i+1,preorder.length),
                    Arrays.copyOfRange(inorder,i+1,inorder.length));
            }
        }
        return newNode;
    }
}
  • 优秀思路:思路和我的类似,主要不同是
  • ① 因为得重复的寻找根节点,采用循环遍历法在中序遍历中寻找根节点效率太低,故建立HashMap存储各个节点的位置,从而方便查找。
  • ② 事实上,我们不需要真的把 preorderinorder 切分了,只需要用分别用两个指针指向开头和结束位置即可。注意下边的两个指针指向的数组范围是包括左边界,不包括右边界。
class Solution {
    HashMap<Integer, Integer> map = new HashMap<>(); 
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        for (int i = 0; i < inorder.length; i++) {
            map.put(inorder[i], i);
        }
        return buildTreeHelper(preorder, 0, preorder.length, inorder, 0, inorder.length);
    }

    private TreeNode buildTreeHelper(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end) {
        if (p_start == p_end) return null;
        TreeNode newNode = new TreeNode(preorder[p_start]);
        int i_rootInMid = map.get(preorder[p_start]); // 根节点在中序遍历中的位置
        int lenLeft = i_rootInMid - i_start; // 本次的左子树长度
        // 两个遍历的左右指标更新是最核心的!尤其是left部分
        newNode.left = buildTreeHelper(preorder,p_start+1,p_start+lenLeft+1,inorder,i_start,i_rootInMid);
        newNode.right = buildTreeHelper(preorder,p_start+lenLeft+1,p_end,inorder,i_rootInMid+1,i_end);
        return newNode;
    }
}

9. 岛屿的最大面积

  • 题目描述:给定一个包含了一些 01 的非空二维数组 grid 。一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 )
  • 我的思路(优秀 75%- 94%):定义访问状态,每找到一个没被搜索过的1(没搜索过是重点),沿其上下左右四个方向进行搜索
class Solution {
    boolean[][] vis;
    int area = 0; // 定义全局变量,节省储存空间
    public int maxAreaOfIsland(int[][] grid) {
        if(grid==null || grid.length == 0 || grid[0]==null ||grid[0].length == 0) return 0;
        int maxArea = 0;
        vis = new boolean[grid.length][grid[0].length];
        for(int i = 0;i<grid.length;i++){
            for(int j = 0;j<grid[0].length;j++){
                if(grid[i][j]==1 && !vis[i][j]){ // 这里可加快效率
                    area = 0;
                    findIsland(grid,i,j);
                    maxArea = Math.max(maxArea,area);
                }
            }
        }
        return maxArea;
    }
    private void findIsland(int[][] grid,int i,int j){
        if(i<0 || i>=grid.length || j<0 || j>=grid[0].length || vis[i][j] || grid[i][j] !=1 ) return;
        area++;
        vis[i][j] = true;
        findIsland(grid,i+1,j); // 下
        findIsland(grid,i,j+1); // 右
        findIsland(grid,i-1,j); // 上
        findIsland(grid,i,j-1); // 左
    }
}
  • 思路代码改进(100% - 80%):略去vis矩阵,直接用grid兼职记录访问状态,从而减少了对vis的操作时间和存储空间
class Solution {
    int area = 0; // 定义全局变量,节省储存空间
    public int maxAreaOfIsland(int[][] grid) {
        if(grid==null || grid.length == 0 || grid[0]==null ||grid[0].length == 0) return 0;
        int maxArea = 0;
        for(int i = 0;i<grid.length;i++){
            for(int j = 0;j<grid[0].length;j++){
                if(grid[i][j]==1){ // 这里可加快效率
                    area = 0;
                    findIsland(grid,i,j);
                    maxArea = Math.max(maxArea,area);
                }
            }
        }
        return maxArea;
    }
    private void findIsland(int[][] grid,int i,int j){
        if(i<0 || i>=grid.length || j<0 || j>=grid[0].length || grid[i][j] !=1) return;
        area++;
        grid[i][j] = 0;
        findIsland(grid,i+1,j); // 下
        findIsland(grid,i,j+1); // 右
        findIsland(grid,i-1,j); // 上
        findIsland(grid,i,j-1); // 左
    }
}

10. 由斜杠划分区域(困难,优秀思路待补充)

  • 题目描述:在由 1 x 1 方格组成的 N x N 网格 grid 中,每个 1 x 1 方块由 /、\ 或空格构成。这些字符会将方块划分为一些共边的区域。(请注意,反斜杠字符是转义的,因此 \ 用 “\\” 表示。)。返回区域的数目。
  • 我的思路(5% - 5%):利用公共边对各区域进行搜索合并
    • 为每条单位边分配id
    • map记录每块方形区域(" “)或三角形区域(”“或”/")的边的id
    • map2记录每条边相关的小块id:map2
    • 根据map和map2构造dfs合并函数,递归进行区域搜索合并
class Solution {
    public int regionsBySlashes(String[] grid) {

        if(grid==null) return 0;

        // 确定N的数量
        int N = grid.length;

        // 给N*N方格的内部的每条单位边赋id,并记录每个小区域的边界单位边id
        HashMap<Integer,ArrayList<Integer>> map = new HashMap<Integer,ArrayList<Integer>>(); //
        HashMap<Integer,ArrayList<Integer>> map2 = new HashMap<Integer,ArrayList<Integer>>();
        int count = 0; // 小块数量
        ArrayList<Integer> indexEdge; 
        ArrayList<Integer> indexPiece; 
        for(int i = 0;i<N;i++){
            for(int j = 0;j<N;j++){
                indexEdge = new ArrayList<Integer>();
                indexPiece = new ArrayList<Integer>();
                /* 空格:只有一个小方块,上下左右四条边id都放进去 */
                if(grid[i].charAt(j)==' '){
                    map2 = myPut(map2,(N+1)*j+i,count);// 上
                    map2 = myPut(map2,(N+1)*j+i+1,count);// 下
                    map2 = myPut(map2,((N+1)*N-1)+(N+1)*i+j+1,count); // 左
                    map2 = myPut(map2,((N+1)*N-1)+(N+1)*i+j+2,count);// 右

                    indexEdge.add((N+1)*j+i); // 上
                    indexEdge.add((N+1)*j+i+1); // 下
                    indexEdge.add(((N+1)*N-1)+(N+1)*i+j+1); // 左
                    indexEdge.add(((N+1)*N-1)+(N+1)*i+j+2); // 右
                    map.put(count++,indexEdge);
                }

                /* /:两个小斜块 */
                else if(grid[i].charAt(j)=='/'){
                    map2 = myPut(map2,(N+1)*j+i,count);// 上
                    map2 = myPut(map2,((N+1)*N-1)+(N+1)*i+j+1,count); // 左

                    // 斜块1加入其上方和左方边界id
                    indexEdge.add((N+1)*j+i); // 上
                    indexEdge.add(((N+1)*N-1)+(N+1)*i+j+1); // 左
                    map.put(count++,indexEdge);

                    map2 = myPut(map2,(N+1)*j+i+1,count);// 下
                    map2 = myPut(map2,((N+1)*N-1)+(N+1)*i+j+2,count);// 右
                    // 斜块2加入其右方和下方边界id
                    indexEdge = new ArrayList<Integer>();
                    indexEdge.add(((N+1)*N-1)+(N+1)*i+j+2); // 右
                    indexEdge.add((N+1)*j+i+1); // 下
                    map.put(count++,indexEdge);
                }

                /* /:两个小斜块 */
                else if(grid[i].charAt(j)==92){
                    map2 = myPut(map2,(N+1)*j+i,count);// 上
                    map2 = myPut(map2,((N+1)*N-1)+(N+1)*i+j+2,count);// 右

                    // 斜块1加入其上方和右方边界id
                    indexEdge.add((N+1)*j+i); // 上
                    indexEdge.add(((N+1)*N-1)+(N+1)*i+j+2); // 右
                    map.put(count++,indexEdge);

                    map2 = myPut(map2,(N+1)*j+i+1,count);// 下
                    map2 = myPut(map2,((N+1)*N-1)+(N+1)*i+j+1,count); // 左
                    // 斜块2加入其左方和下方边界id
                    indexEdge = new ArrayList<Integer>();
                    indexEdge.add(((N+1)*N-1)+(N+1)*i+j+1); // 左
                    indexEdge.add((N+1)*j+i+1); // 下
                    map.put(count++,indexEdge);
                }
            }
        }
        if(count<=2) return count;

        // 寻找同一区域的小块
        int[] vis = new int[count];
        int countFinal = 0;
        for(int i = 0;i<map.size();i++){
            if(vis[i] == 0){
                vis[i] = 1;
                countFinal++;
                for(Integer c1 : map.get(i)){// 遍历当前小块的边
                    for(Integer c2 : map2.get(c1)){ // 遍历当前小块的边的对应的小块
                        if(vis[c2] == 0){
                            merge(map,map2,vis,c2);
                        }
                    }
                }
            }
        }
        return countFinal;
    }

    // 根据map和map2合并小块
    // map:记录每个小块的id
    // map:记录每条边的小块
    private void merge(HashMap<Integer,ArrayList<Integer>> map,HashMap<Integer,ArrayList<Integer>> map2,int[] vis,int cur){
        // 根据map找到当前节点
        for(Integer c:vis){
            if(c==0){
                if(vis[cur] == 0){
                    vis[cur] = 1;
                    for(Integer c1 : map.get(cur)){// 遍历当前小块的边
                        for(Integer c2 : map2.get(c1)){ // 遍历当前小块的边的对应的小块
                            if(vis[c2] == 0){
                                merge(map,map2,vis,c2);
                            }
                        }
                    }
                }
            }
        }
        return;
    }
    private HashMap<Integer,ArrayList<Integer>> myPut(HashMap<Integer,ArrayList<Integer>> map2,int key,int piece){
        if(map2.size()==0 || !map2.containsKey(key)){
            ArrayList<Integer> temp = new ArrayList<Integer>();
            temp.add(piece);
            map2.put(key,temp);
        }else{
            map2.get(key).add(piece);
        }
        return map2;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值