200、島嶼數量 | 演算法(leetcode,附思維導圖 + 全部解法)300題

碼農三少 發表於 2022-06-13
演算法 LeetCode

零 標題:演算法(leetcode,附思維導圖 + 全部解法)300題之(200)島嶼數量

一 題目描述

題目描述
題目描述

二 解法總覽(思維導圖)

思維導圖

三 全部解法

1 方案1

1)程式碼:

// 方案1 “自己。模擬 - 標記法”。

// 思路:
// 1)狀態初始化:m = grid.length, n = grid[0].length; 。
// tempMap = new Map(), resMap = getMapByGrid(), resCount = 0; 。
// 2)核心:迴圈處理 —— 條件為 存在未被訪問過的陸地 。
// 3)返回結果 resCount 。
var numIslands = function(grid) {
    const getMapByGrid = () => {
        let resMap = new Map();

        for (let i = 0; i < m; i++) {
            for (let j = 0; j < n; j++) {
                if (grid[i][j] === '1') {
                    const tempVal = `${i}#${j}`;
                    resMap.set(tempVal, 1);
                }
            }
        }

        return resMap;
    };

    // 1)狀態初始化:m = grid.length, n = grid[0].length; 。
    // tempMap = new Map(), resMap = getMapByGrid(), resCount = 0; 。
    const m = grid.length,
        n = grid[0].length;
    let // tempMap:目前已被訪問過的陸地。
        tempMap = new Map(),
        resMap = getMapByGrid(),
        resCount = 0;

    // 2)核心:迴圈處理 —— 條件為 存在未被訪問過的陸地 。
    while (resMap.size !== 0) {
        for (const [key, val] of resMap) {
            if (!tempMap.has(key)) {
                tempMap.set(key, 1);
                let tempQueue = [key];
                
                while (tempQueue.length !== 0) {
                    const key = tempQueue.shift(),
                        [tempI, tempJ] = key.split('#').map(v => parseInt(v));
                    
                    // 標記為已被訪問。
                    resMap.delete(key);

                    // 4個方向的訪問。
                    // 上
                    if (tempI - 1 >= 0 && grid[tempI - 1][tempJ] === '1') {
                        const key = `${tempI - 1}#${tempJ}`;
                        if (!tempMap.has(key)) {
                            tempQueue.push(key);
                            tempMap.set(key, 1);
                        }
                    }
                    // 下

                    if (tempI + 1 < m && grid[tempI + 1][tempJ] === '1') {
                        const key = `${tempI + 1}#${tempJ}`;
                        if (!tempMap.has(key)) {
                            tempQueue.push(key);
                            tempMap.set(key, 1);
                        }
                    }
                    // 左
                    if (tempJ - 1 >= 0 && grid[tempI][tempJ - 1] === '1') {
                        const key = `${tempI}#${tempJ - 1}`;
                        if (!tempMap.has(key)) {
                            tempQueue.push(key);
                            tempMap.set(key, 1);
                        }
                    }
                    // 右
                    if (tempJ + 1 < n && grid[tempI][tempJ + 1] === '1') {
                        const key = `${tempI}#${tempJ + 1}`;
                        if (!tempMap.has(key)) {
                            tempQueue.push(key);
                            tempMap.set(key, 1);
                        }
                    }
                }

                // 當前島嶼無法再次連線到任何陸地了。
                resCount++;
            }
            break;
        }
    }

    // 3)返回結果 resCount 。
    return resCount;
};

2 方案2

1)程式碼:

// 方案2 “自己。深度優先搜尋法”。
// 參考:
// 1)https://leetcode.cn/problems/number-of-islands/solution/dao-yu-shu-liang-by-leetcode/

// 思路:
// 1)狀態初始化:m = grid.length, n = grid[0].length; 。
// resCount = 0;
// 2)核心:遍歷 grid 。
// 2.1)若 當前的格子為 陸地,則 島嶼數量增加1 並 執行深度搜尋函式 —— dfs(i, j) 。
// 3)返回結果 resCount 。
var numIslands = function(grid) {
    // 深度搜尋(實現:遞迴)
    const dfs = (curI = 0, curJ = 0) => {
        // 1)遞迴出口:其實被隱藏起來 —— 當座標超出格子範圍 或 座標不符合條件時。

        // 2)遞迴主體。
        // 2.1)當前陸地置為 水 。
        grid[curI][curJ] = '0';

        // 2.2)上下左右,四個方向執行深度搜尋。
        if (curI - 1 >= 0 && grid[curI - 1][curJ] === '1') {
            dfs(curI - 1, curJ);
        }
        if (curI + 1 < m && grid[curI + 1][curJ] === '1') {
            dfs(curI + 1, curJ);
        }
        if (curJ - 1 >= 0 && grid[curI][curJ - 1] === '1') {
            dfs(curI, curJ - 1);
        }
        if (curJ + 1 < n && grid[curI][curJ + 1] === '1') {
            dfs(curI, curJ + 1);
        }
    };

    // 1)狀態初始化:m = grid.length, n = grid[0].length; 。
    // resCount = 0;
    const m = grid.length,
        n = grid[0].length;
    let resCount = 0;

    // 2)核心:遍歷 grid 。
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            // 2.1)若 當前的格子為 陸地,則 島嶼數量增加1 並 執行深度搜尋函式 —— dfs(i, j) 。
            if (grid[i][j] === '1') {
                resCount++;
                dfs(i, j);
            }
        }
    }

    // 3)返回結果 resCount 。
    return resCount;
};

3 方案3

1)程式碼:

// 方案3 “廣度優先搜尋法(本質:跟方案1差不多)”。
// 參考:
// 1)https://leetcode.cn/problems/number-of-islands/solution/dao-yu-shu-liang-by-leetcode/

// 思路:
// 1)狀態初始化:m = grid.length, n = grid[0].length;
// resCount = 0; 。
// 2)核心:2層迴圈的遍歷。
// 2.1)若 當前位置為 陸地 ,特殊處理,
// 並 進行 廣度優先(使用佇列 queue )的遍歷處理。
// 3)返回結果 resCount 。
var numIslands = function(grid) {
    // 1)狀態初始化:m = grid.length, n = grid[0].length;
    // resCount = 0; 。
    const m = grid.length,
        n = grid[0].length;
    let resCount = 0;

    // 2)核心:2層迴圈的遍歷。
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            // 2.1)若 當前位置為 陸地 ,特殊處理,
            // 並 進行 廣度優先(使用佇列 queue )的遍歷處理。
            if (grid[i][j] === '1') {
                grid[i][j] = '0';
                resCount++;
                let queue = [[i, j]];
                
                while (queue.length !== 0) {
                    const [tempI, tempJ] = queue.shift();
                    // 上下左右,4個方向。
                    if (tempI - 1 >= 0 &&grid[tempI - 1][tempJ] === '1') {
                        queue.push([tempI - 1, tempJ]);
                        grid[tempI - 1][tempJ] = '0';
                    }
                    if (tempI + 1 < m &&grid[tempI + 1][tempJ] === '1') {
                        queue.push([tempI + 1, tempJ]);
                        grid[tempI + 1][tempJ] = '0';
                    }
                    if (tempJ - 1 >= 0 &&grid[tempI][tempJ - 1] === '1') {
                        queue.push([tempI, tempJ - 1]);
                        grid[tempI][tempJ - 1] = '0';
                    }
                    if (tempJ + 1 < n &&grid[tempI][tempJ + 1] === '1') {
                        queue.push([tempI, tempJ + 1]);
                        grid[tempI][tempJ + 1] = '0';
                    }
                }
            }
        }
    }

    // 3)返回結果 resCount 。
    return resCount;
}

4 方案4

1)程式碼:

// 方案4 “並查集法”。
// 參考:
// 1)https://leetcode.cn/problems/number-of-islands/solution/dao-yu-shu-liang-by-leetcode/

// 注:有問題,通過 12 / 49 。TODO:排查問題 && 重新手撕。
var numIslands = function(grid) {
    class Unionfind {
        constructor() {
            let count = 0,
                parent = [],
                rank = [];
            
            for (let i = 0; i < m; i++) {
                for (let j = 0; j < n; j++) {
                    const tempIndex = n * i + j;
                    if (grid[i][j] === '1') {
                        parent[tempIndex] = tempIndex;
                        count++;
                    }
                    // ?
                    rank[tempIndex] = 0;
                }
            }

            this.count = count;
            this.parent = parent;
            this.rank = rank;
        }

        find(index) {
            const {parent} = this;
            if (parent[index] !== index) {
                // 找到該座標最開始的“祖先”?
                parent[index] = this.find(parent[index]);
            }
            
            this.parent = parent;
            return parent[index];
        }

        union(index_1, index_2) {
            let {rank, parent, count} = this;

            const root_1 = this.find(index_1),
                root_2 = this.find(index_2);
            
            if (root_1 !== root_2) {
                if (rank[root_1] > rank[root_2]) {
                    parent[root_2] = root_1;
                }
                else if (rank[root_1] < rank[root_2]) {
                    parent[root_1] = root_2;
                }
                else {
                    parent[root_2] = root_1;
                    // ?
                    rank[root_1] += 1;
                }
                count--;
            }
            
            this.count = count;
            this.parent = parent;
            this.rank = rank;
        }

        getCount() {
            const {count} = this;

            return count;
        }
    }

    const m = grid.length,
        n = grid[0].length;
    let unionfind = new Unionfind();

    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (grid[i][j] === '1') {
                grid[i][j] = '0';
            }

            // 上下左右,4個方向。
            const tempIndex = n * i + j;
            if (i - 1 >= 0 && grid[i - 1][j] === '1') {
                unionfind.union(tempIndex, n * (i - 1) + j);
            }
            if (i + 1 < m && grid[i + 1][j] === '1') {
                unionfind.union(tempIndex, n * (i + 1) + j);
            }
            if (j - 1 >= 0 && grid[i][j - 1] === '1') {
                unionfind.union(tempIndex, n * i + (j - 1));
            }
            if (j + 1 < n && grid[i][j + 1] === '1') {
                unionfind.union(tempIndex, n * i + (j + 1));
            }
        }
    }

    return unionfind.getCount();
}

四 資源分享 & 更多

1 歷史文章 - 總覽

文章名稱解法閱讀量
1. 兩數之和(Two Sum)共 3 種2.7 k+
2. 兩數相加 (Add Two Numbers)共 4 種2.7 k+
3. 無重複字元的最長子串(Longest Substring Without Repeating Characters)共 3 種2.6 k+
4. 尋找兩個正序陣列的中位數(Median of Two Sorted Arrays)共 3 種2.8 k+
5. 最長迴文子串(Longest Palindromic Substring)共 4 種2.8 k+
6. Z 字形變換(ZigZag Conversion)共 2 種1.9 k+
7. 整數反轉(Reverse Integer)共 2 種2.4 k+
8. 字串轉換整數 (atoi)(String to Integer (atoi))共 3 種4.2 k+
9. 迴文數(Palindrome Number)共 3 種4.3 k+
11. 盛最多水的容器(Container With Most Water)共 5 種4.0 k+
12. 整數轉羅馬數字(Integer to Roman)共 3 種3.2 k+
13. 羅馬數字轉整數(Roman to Integer)共 3 種3.8 k+
14. 最長公共字首(Longest Common Prefix)共 4 種3.0 k+
15. 三數之和(3Sum)共 3 種60.7 k+
16. 最接近的三數之和(3Sum Closest)共 3 種4.7 k+
17. 電話號碼的字母組合(Letter Combinations of a Phone Number)共 3 種3.1 k+
18. 四數之和(4Sum)共 4 種11.5 k+
19. 刪除連結串列的倒數第 N 個結點(Remove Nth Node From End of List)共 4 種1.2 k+
20. 有效的括號(Valid Parentheses)共 2 種1.8 k+
21. 合併兩個有序連結串列(Merge Two Sorted Lists)共 3 種1.2 k+
22. 括號生成(Generate Parentheses)共 4 種1.1 k+
23. 合併K個升序連結串列(Merge k Sorted Lists)共 4 種0.9 k+
24. 兩兩交換連結串列中的節點(Swap Nodes in Pairs)共 3 種0.5 k+
25. K 個一組翻轉連結串列(Reverse Nodes in k-Group)共 5 種1.3 k+
26. 刪除有序陣列中的重複項(Remove Duplicates from Sorted Array)共 4 種1.3 k+
27. 移除元素(Remove Element)共 4 種0.4 k+
28. 實現 strStr()(Implement strStr())共 5 種0.8 k+
29. 兩數相除(Divide Two Integers)共 4 種0.6 k+
30. 串聯所有單詞的子串(Substring with Concatenation of All Words)共 3 種0.6 k+
31. 下一個排列(Next Permutation)共 2 種0.8 k+
32. 最長有效括號(Longest Valid Parentheses)共 2 種1.4 k+
33. 搜尋旋轉排序陣列(Search in Rotated Sorted Array)共 3 種1.0k+
34. 在排序陣列中查詢元素的第一個和最後一個位置(Find First and Last Position of Element in Sorted Array)共 3 種0.5 k+
35. 搜尋插入位置(Search Insert Position)共 3 種0.3 k+
36. 有效的數獨(Valid Sudoku)共 1 種0.6 k+
38. 外觀數列(Count and Say)共 5 種1.1 k+
39. 組合總和(Combination Sum)共 3 種1.4 k+
40. 組合總和 II(Combination Sum II)共 2 種1.6 k+
41. 缺失的第一個正數(First Missing Positive)共 3 種1.2 k+
53. 最大子陣列和(Maximum Subarray)共 3 種0.3k+
88. 合併兩個有序陣列(Merge Sorted Array)共 3 種0.4 k+
102. 二叉樹的層序遍歷(Binary Tree Level Order Traversal)共 3 種0.4 k+
146. LRU 快取(LRU Cache)共 2 種0.5 k+
206. 反轉連結串列(Reverse Linked List)共 3 種1.0 k+
215. 陣列中的第K個最大元素(Kth Largest Element in an Array)共 3 種0.5 k+
236. 二叉樹的最近公共祖先(Lowest Common Ancestor of a Binary Tree)共 3 種0.1 k+

刷題進度 - LeetCode:527 / 2662 、《劍指offer》:66 / 66

2 博主簡介

碼農三少 ,一個致力於編寫 極簡、但齊全題解(演算法) 的博主。
專注於 一題多解、結構化思維 ,歡迎一起刷穿 LeetCode ~