DAY51

113 阅读7分钟

第十一章:图论part03

101.  孤岛的总面积

基础题目 可以自己尝试做一做 。

www.programmercarl.com/kamacoder/0…

102.  沉没孤岛

和上一题差不多,尝试自己做做

www.programmercarl.com/kamacoder/0…

/**
 * @param {number[][]} grid
 * @return {number}
 */
var numEnclaves = function (grid) {
    let count = 0;
    let rows = grid.length;
    let cols = grid[0].length;

    function dfs(i, j) {
        if (i < 0 || j < 0 || i >= rows || j >= cols || grid[i][j] === 0) {
            return 0;
        }

        grid[i][j] = 0;
        let area = 1;

        area += dfs(i - 1, j);  // 上
        area += dfs(i, j + 1);  // 右
        area += dfs(i + 1, j);  // 下
        area += dfs(i, j - 1);  // 左
        return area;
    }

    // 消除四周与边界相连的陆地
    for (let i = 0; i < rows; i++) {
        if (grid[i][0] === 1) dfs(i, 0);  // 左边界
        if (grid[i][cols - 1] === 1) dfs(i, cols - 1);  // 右边界
    }

    for (let j = 0; j < cols; j++) {
        if (grid[0][j] === 1) dfs(0, j);  // 上边界
        if (grid[rows - 1][j] === 1) dfs(rows - 1, j);  // 下边界
    }

    // 统计所有不接触边界的陆地面积
    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            if (grid[i][j] === 1) {
                count += dfs(i, j);
            }
        }
    }
    
    return count;
};

关键点说明:

  1. 深度优先搜索 (DFS) 用于从某个陆地格子扩展,遍历所有连通的陆地。遍历到的陆地都会标记为 0,防止重复计数。
  2. 边界处理:通过对第一列、最后一列,第一行和最后一行的所有陆地格子执行 DFS,将与边界相连的陆地 "沉没"。
  3. 最终结果:遍历完边界之后,再遍历内部格子,统计不接触边界的岛屿面积。

通过这种方式,所有孤立的岛屿面积会被正确计算。 沉默孤岛统计的是个数(主函数中累加),孤岛的总面积统计的是面积(dfs函数中累加Ï)

103.  水流问题

需要点优化思路,建议先自己读题,相处一个解题方法,有时间就自己写代码,没时间就直接看题解,优化方式 会让你 耳目一新。

www.programmercarl.com/kamacoder/0…

那么我们可以 反过来想,从第一组边界上的节点 逆流而上,将遍历过的节点都标记上。

同样从第二组边界的边上节点 逆流而上,将遍历过的节点也标记上。

然后两方都标记过的节点就是既可以流太平洋也可以流大西洋的节点

DFS 实现的关键点:

  • DFS 的遍历方向:向四个方向(上、下、左、右)遍历,确保水流只能从高处流向低处或等高度处。
  • 边界条件:太平洋在左边界和上边界,大西洋在右边界和下边界。
  • 标记访问状态:通过两个矩阵分别记录每个单元格是否能够流向太平洋或大西洋。

代码实现:

/**
 * @param {number[][]} heights
 * @return {number[][]}
 */
var pacificAtlantic = function(heights) {
    let rows = heights.length;
    let cols = heights[0].length;

    // 创建两个矩阵来标记是否可以流向太平洋和大西洋
    let pacific = Array.from({ length: rows }, () => Array(cols).fill(false));
    let atlantic = Array.from({ length: rows }, () => Array(cols).fill(false));

    let directions = [[-1, 0], [1, 0], [0, -1], [0, 1]];  // 上下左右四个方向

    // 深度优先搜索函数
    function dfs(i, j, visited) {
        visited[i][j] = true;

        // 向四个方向探索
        for (let [di, dj] of directions) {
            let ni = i + di, nj = j + dj;

            // 检查新位置是否越界、是否已经访问过、以及新位置的高度是否大于等于当前高度
            if (ni >= 0 && ni < rows && nj >= 0 && nj < cols && !visited[ni][nj] && heights[ni][nj] >= heights[i][j]) {
                dfs(ni, nj, visited);
            }
        }
    }

    // 从太平洋的边界开始DFS
    for (let i = 0; i < rows; i++) {
        dfs(i, 0, pacific);  // 左边界
        dfs(i, cols - 1, atlantic);  // 右边界
    }
    for (let j = 0; j < cols; j++) {
        dfs(0, j, pacific);  // 上边界
        dfs(rows - 1, j, atlantic);  // 下边界
    }

    // 查找同时能流向太平洋和大西洋的单元格
    let result = [];
    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            if (pacific[i][j] && atlantic[i][j]) {
                result.push([i, j]);
            }
        }
    }

    return result;
};

关键点解释:

  1. DFS 遍历:从矩阵的四个边界(与太平洋和大西洋相邻)开始,利用深度优先搜索标记每个单元格是否可以流向太平洋和大西洋。
  2. 条件判断:DFS 过程中,每次只从较高或相同高度流向较低或相同高度的单元格,这样可以模拟水流的过程。
  3. 双标记矩阵pacificatlantic 两个矩阵分别记录每个单元格是否可以流向太平洋或大西洋,最后遍历矩阵,寻找能同时流向两者的单元格。

总结:

  • 使用DFS,确保在四个方向上遍历时只从高处流向低处或等高处。
  • 通过分别从太平洋边界和大西洋边界开始进行搜索,可以确定哪些单元格能流向两个海洋。

这样便能高效地解决这个问题。

104.建造最大岛屿

同样优化思路也会让你耳目一新,自己想比较难想出来。

www.programmercarl.com/kamacoder/0…

思路概述

  1. 遍历矩阵:首先遍历整个矩阵,找到所有的岛屿(陆地部分),并计算每个岛屿的面积。同时,可以为每个岛屿分配一个唯一的标识符,以便在后续步骤中引用。

  2. 存储岛屿面积:使用一个字典(或数组)来存储每个岛屿的面积,岛屿的标识符作为键,面积作为值。

  3. 尝试将水单元格转为陆地:遍历每个水单元格(0),检查它周围的四个方向,找到相邻的陆地岛屿(1)。通过将水单元格转为陆地,可以连接相邻的岛屿,计算新的岛屿面积。只有当水单元格(0)与至少一个岛屿(1)相邻时,将其转换为陆地(1)才有意义。因此,在遍历水单元格时,我们应该检查它的四个相邻方向(上、下、左、右),看看是否有相邻的陆地单元格。

  4. 计算最大面积:对于每个水单元格,计算将其转为陆地后形成的新岛屿面积,将其与当前最大岛屿面积进行比较,更新最大值。

  5. 考虑无水单元格的情况:如果矩阵中没有水单元格,直接返回当前最大岛屿面积。

代码示例

下面是一个简单的 JavaScript 实现示例:

/**
 * @param {number[][]} grid
 * @return {number}
 */
var largestIsland = function(grid) {
    const n = grid.length;
    const islandArea = {};
    let islandId = 2; // 从2开始为岛屿标识符(避免与0和1冲突)
    let maxArea = 0;

    // 深度优先搜索,计算岛屿面积
    const dfs = (i, j, id) => {
        if (i < 0 || i >= n || j < 0 || j >= n || grid[i][j] !== 1) {
            return 0;
        }
        grid[i][j] = id; // 标记为当前岛屿
        let area = 1; // 当前单元格的面积
        area += dfs(i - 1, j, id); // 上
        area += dfs(i + 1, j, id); // 下
        area += dfs(i, j - 1, id); // 左
        area += dfs(i, j + 1, id); // 右
        return area;
    };

    // 第一次遍历,计算岛屿面积
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
            if (grid[i][j] === 1) {
                const area = dfs(i, j, islandId);
                islandArea[islandId] = area; // 存储岛屿面积
                maxArea = Math.max(maxArea, area); // 更新最大面积
                islandId++;
            }
        }
    }

    // 第二次遍历,尝试将水单元格转为陆地
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
            if (grid[i][j] === 0) {
                const seen = new Set();
                let newArea = 1; // 将水转为陆地,面积加1
                // 检查四个方向
                for (const [di, dj] of [[-1, 0], [1, 0], [0, -1], [0, 1]]) {
                    const ni = i + di;
                    const nj = j + dj;
                    if (ni >= 0 && ni < n && nj >= 0 && nj < n && grid[ni][nj] > 1) {
                        const id = grid[ni][nj];
                        if (!seen.has(id)) {
                            seen.add(id);
                            newArea += islandArea[id]; // 加上相邻岛屿的面积
                        }
                    }
                }
                maxArea = Math.max(maxArea, newArea); // 更新最大面积
            }
        }
    }

    return maxArea;
};

总结

  • 首先通过 DFS 计算每个岛屿的面积并存储在字典中。
  • 然后再遍历每个水单元格,检查相邻的陆地,计算转为陆地后新形成的岛屿面积。
  • 维护一个最大岛屿面积的变量,在遍历过程中不断更新,最终返回结果。

这种方法的时间复杂度大约是 O(n^2),适合处理较大的矩阵。希望这个思路和示例代码能对你有所帮助!如果有任何疑问,欢迎随时问我!