【Lintcode】860. Number of Distinct Islands

题目地址:

https://www.lintcode.com/problem/number-of-distinct-islands/description

给定一个 0 − 1 0-1 01矩阵,只含 1 1 1的连通块构成一个岛屿,如果两个岛屿可以通过平移重合则视为同一种。问这个矩阵里有多少个不同的岛屿。

思路是DFS,问题的关键在于如何记录岛屿的形状。我们可以在发现 1 1 1的时候,按照固定的顺序对这个岛屿的 1 1 1进行遍历,比如,按照“右下左上”的方式进行遍历,然后用一个StringBuilder记录每一步走的是哪个方向,这样就可以描述出这个岛屿的形状;同时需要注意,回溯的时候这个StringBuilder也需要有所记录,否则就会造成不同岛屿对应同一个字符串的现象。代码如下:

import java.util.HashSet;
import java.util.Set;

public class Solution {
    /**
     * @param grid: a list of lists of integers
     * @return: return an integer, denote the number of distinct islands
     */
    public int numberofDistinctIslands(int[][] grid) {
        // write your code here
        if (grid == null || grid.length == 0 || grid[0].length == 0) {
            return 0;
        }
        // 开个数组存四个方向,DFS的时候就按照这个方向进行遍历
        int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        // set存遍历过的岛屿的形状对应的字符串
        Set<String> set = new HashSet<>();
        // 记录哪些点已经被访问过
        boolean[][] visited = new boolean[grid.length][grid[0].length];
        // 按照每一行从左向右,然后从上向下遍历行的顺序对矩阵进行遍历
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
            	// 发现了未遍历过的岛屿,即进行遍历,同时用一个StringBuilder记录路径的走法
                if (!visited[i][j] && grid[i][j] == 1) {
                    StringBuilder sb = new StringBuilder();
                    dfs(i, j, grid, sb, visited, dirs);
                    set.add(sb.toString());
                }
            }
        }
        
        return set.size();
    }
    
    // 从grid[x][y]开始遍历一个岛屿,遍历的同时标记这个岛屿的所有1为已访问过;
    // 用一个StringBuilder遍历路径的走法,以此确定岛屿的形状
    private void dfs(int x, int y, int[][] grid, StringBuilder sb, boolean[][] visited, int[][] dirs) {
        visited[x][y] = true;
        for (int i = 0; i < dirs.length; i++) {
            int nextX = x + dirs[i][0], nextY = y + dirs[i][1];
            if (inBound(nextX, nextY, grid) && !visited[nextX][nextY] && grid[nextX][nextY] == 1) {
                sb.append(i);
                dfs(nextX, nextY, grid, sb, visited, dirs);
                // 回溯的时候加个#做一下标记
                sb.append('#');
            }
        }
    }
    
    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)

至于为什么上面存的字符串会和岛屿形状一一对应,可以用数学归纳法证明。首先如果岛屿只含有一个 1 1 1,那么对应的字符串是空串,结论成立;假设岛屿含有 1 1 1的个数小于等于 k k k的时候都成立,当岛屿含 1 1 1的个数是 k + 1 k+1 k+1时,第一个格子被遍历后,从接下来进入的下一个格子开始将所有与这个格子连通的 1 1 1看成一个岛屿,由归纳假设这个岛屿和算法算出的字符串一一对应,直到回溯到第一个格子的时候会新加一个 # \# #,接着再从第一个格子沿着另一个方向遍历,又由归纳假设这个岛屿和算法算出的字符串一一对应;以此类推,每个“分”岛屿会接到字符串后面,并且以 # \# #分隔,所以整个岛屿都是和字符串一一对应。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值