【Leetcode】827. Making A Large Island

記錄演算法發表於2020-10-21

題目地址:

https://leetcode.com/problems/making-a-large-island/

給定一個二維 0 − 1 0-1 01矩陣,允許將其中的某一個 0 0 0變為 1 1 1,問能得到的最大的 1 1 1連通塊的面積(即 1 1 1的數量)。

法1:並查集。這個問題的關鍵在於,當遍歷到 0 0 0的時候,將其變為 1 1 1之後連通了其的四個鄰居,但不知道這四個鄰居是屬於不同連通塊的還是屬於相同連通塊的,如果屬於相同連通塊,就會造成重複計算的問題。這時候並查集可以解決這個問題,當兩個節點在並查集的樹的祖宗節點相等,就說明它們是屬於同一個連通塊的。這樣就能避免重複計算。同時也需要注意矩陣全是 1 1 1的情況,這時直接返回矩陣的面積即可。程式碼如下:

import java.util.*;

public class Solution {

	class Pair {
        int x, y;
    
        public Pair(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        @Override
        public boolean equals(Object o) {
            Pair pair = (Pair) o;
            return x == pair.x && y == pair.y;
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(x, y);
        }
    }
    
    class UnionFind {
        Map<Pair, Pair> parent;
        Map<Pair, Integer> size;
    
        public UnionFind() {
            parent = new HashMap<>();
            size = new HashMap<>();
        }
    
        public void add(Pair x) {
            if (!parent.containsKey(x)) {
                parent.put(x, x);
                size.put(x, 1);
            }
        }
        
        // 找到x的祖宗節點
        public Pair find(Pair x) {
            add(x);
            
            if (!x.equals(parent.get(x))) {
                parent.put(x, find(parent.get(x)));
            }
            
            return parent.get(x);
        }
        
        public void union(Pair x, Pair y) {
            Pair px = find(x), py = find(y);
            if (px.equals(py)) {
                return;
            }
            
            parent.put(px, py);
            size.put(py, size.get(px) + size.get(py));
        }
        
        public int getSize(Pair x) {
            return size.get(find(x));
        }
    }
    
    public int largestIsland(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        UnionFind uf = new UnionFind();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
            	// 接下來將1連通塊都union起來
                if (grid[i][j] == 1) {
                    Pair cur = new Pair(i, j);
                    uf.add(cur);
                    
                    int[] d = {1, 0, -1, 0, 1};
                    for (int k = 0; k < 4; k++) {
                        int nextI = i + d[k], nextJ = j + d[k + 1];
                        if (0 <= nextI && nextI < m && 0 <= nextJ && nextJ < n && grid[nextI][nextJ] == 1) {
                            Pair next = new Pair(nextI, nextJ);
                            uf.union(cur, next);
                        }
                    }
                }
            }
        }
    
        int res = 0;
        // 記錄有沒有找到0
        boolean found = false;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 0) {
                    found = true;
                    
                    Set<Pair> set = new HashSet<>();
                    int area = 1;
                    int[] d = {1, 0, -1, 0, 1};
                    for (int k = 0; k < 4; k++) {
                        int nextI = i + d[k], nextJ = j + d[k + 1];
                        if (0 <= nextI && nextI < m && 0 <= nextJ && nextJ < n && grid[nextI][nextJ] == 1) {
                            Pair next = new Pair(nextI, nextJ);
                            // 找到鄰居的祖宗,相同祖宗的鄰居的1連通塊面積只算1次
                            Pair ancestor = uf.find(next);
                            if (!set.contains(ancestor)) {
                                area += uf.getSize(ancestor);
                                set.add(ancestor);
                            }
                        }
                    }
                    
                    res = Math.max(res, area);
                }
            }
        }
        
        return found ? res : m * n;
    }
}

時間複雜度 O ( m n log ⁡ ∗ ( m n ) ) O(mn\log ^* (mn)) O(mnlog(mn)),空間 O ( m n ) O(mn) O(mn)

註解:這個方法雖然容易想到,但雜湊表的put非常耗時。陣列並查集的版本可以參考https://blog.csdn.net/qq_46105170/article/details/109178762

法2:BFS + 標記連通塊編號。思路是將每個 1 1 1連通塊編上號,並且用雜湊表存一下每個編號對應的 1 1 1連通塊的面積。這樣只需要保證相同編號的 1 1 1連通塊的面積不要累加兩次就行了。標記 1 1 1連通塊的過程可以用BFS來實現(當然DFS也可以,參考https://blog.csdn.net/qq_46105170/article/details/109178762)。程式碼如下:

import java.util.*;

public class Solution {
    public int largestIsland(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        int[][] ids = new int[m][n];
        boolean[][] visited = new boolean[m][n];
        
        // 存編號為key的1連通塊的面積
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0, id = 1; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (!visited[i][j] && grid[i][j] == 1) {
                    map.put(id, bfs(i, j, id, ids, grid, visited));
                    id++;
                }
            }
        }
    
        int res = 0;
        boolean found = false;
        int[] d = {1, 0, -1, 0, 1};
        
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 0) {
                    found = true;
                    
                    int area = 1;
                    Set<Integer> set = new HashSet<>();
                    for (int k = 0; k < 4; k++) {
                        int nextI = i + d[k], nextJ = j + d[k + 1];
                        if (inBound(nextI, nextJ, grid) && grid[nextI][nextJ] == 1) {
                            int id = ids[nextI][nextJ];
                            // 相同id的連通塊面積只累加一次
                            if (!set.contains(id)) {
                                area += map.get(id);
                                set.add(id);
                            }
                        }
                    }
                
                    res = Math.max(res, area);
                }
            }
        }
        
        return found ? res : m * n;
    }
    
    // 對(x, y)所在的1連通塊做BFS,返回其面積,同時將該連通塊的編號填充為id
    private int bfs(int x, int y, int id, int[][] ids, int[][] grid, boolean[][] visited) {
        visited[x][y] = true;
        ids[x][y] = id;
        
        int area = 0;
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{x, y});
        
        int[] d = {1, 0, -1, 0, 1};
        while (!queue.isEmpty()) {
            area++;
            int[] cur = queue.poll();
            for (int i = 0; i < 4; i++) {
                int nextX = cur[0] + d[i], nextY = cur[1] + d[i + 1];
                if (inBound(nextX, nextY, grid) && grid[nextX][nextY] == 1 && !visited[nextX][nextY]) {
                	// 填充id
                    ids[nextX][nextY] = id;

                    visited[nextX][nextY] = true;
                    queue.offer(new int[]{nextX, nextY});
                }
            }
        }
        
        return area;
    }
    
    private boolean inBound(int x, int y, int[][] grid) {
        return 0 <= x && x < grid.length && 0 <= y && y < grid[0].length;
    }
}

時空複雜度 O ( m n ) O(mn) O(mn)

相關文章