LeetCode156--岛屿数量(L200)、课程表II(L210)

1、岛屿数量

//给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
//
// 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
//
// 此外,你可以假设该网格的四条边均被水包围。
//
//
//
// 示例 1:
//
//
//输入:grid = [
// [“1”,“1”,“1”,“1”,“0”],
// [“1”,“1”,“0”,“1”,“0”],
// [“1”,“1”,“0”,“0”,“0”],
// [“0”,“0”,“0”,“0”,“0”]
//]
//输出:1
//
//
// 示例 2:
//
//
//输入:grid = [
// [“1”,“1”,“0”,“0”,“0”],
// [“1”,“1”,“0”,“0”,“0”],
// [“0”,“0”,“1”,“0”,“0”],
// [“0”,“0”,“0”,“1”,“1”]
//]
//输出:3
//
//
//
//
// 提示:
//
//
// m == grid.length
// n == grid[i].length
// 1 <= m, n <= 300
// grid[i][j] 的值为 ‘0’ 或 ‘1’
//
// Related Topics 深度优先搜索 广度优先搜索 并查集 数组 Matrix

所谓岛屿就是四面都是’0’的一个’1’的组合,所以需要找到这种组合的数量,往往找这种组合,我们一般都是可以通过深度优先遍历和广度优先遍历来找,注意这里我们每遍历一个网格,当他为’1’时我们就需要将其设置为’0’,所以最后直到所有的网格都是’0’的时候我们结束遍历,然后数一数一共遍历了多少次。
方法一:前序遍历的方式

class Solution {
    int[][] dn = {{1,0}, {-1,0}, {0,1}, {0,-1}};
    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'){
                    dfs(i,j,grid);
                    count++;
                }
            }
        }
        return count;
    private void dfs(int i, int j, char[][]grid){
        grid[i][j] = '0';
        for (int k = 0; k < dn.length; k++) {
            if(i+dn[k][0] < grid.length && i+dn[k][0] >= 0 && j+dn[k][1] < grid[0].length &&
                    j+dn[k][1] >=0 && grid[i+dn[k][0]][j+dn[k][1]] == '1'){
                dfs(i+dn[k][0], j+dn[k][1], grid);
            }
        }
    }
}

方法二:层序遍历的方式

class Solution {
    int[][] dn = {{1,0}, {-1,0}, {0,1}, {0,-1}};
    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'){
                    count++;
                    grid[i][j] = '0';
                    Queue<Integer> queue = new LinkedList<>();
                    //这里比较巧妙的是list中存的是j*grid.length+i
                    queue.add(j*grid.length + i);
                    while(!queue.isEmpty()){
                        int id = queue.remove();
                        int r = id % grid.length;
                        int c = id / grid.length;
                        for (int k = 0; k < dn.length; k++) {
                            if(r+dn[k][0] < grid.length && r+dn[k][0] >= 0 && c+dn[k][1] < grid[0].length &&
                                    c+dn[k][1] >=0 && grid[r+dn[k][0]][c+dn[k][1]] == '1'){
                                queue.add((c+dn[k][1])*grid.length+r+dn[k][0]);
                                grid[r+dn[k][0]] [c+dn[k][1]] = '0';
                            }
                        }
                    }
                }
            }
        }
        return count;
    }
}

方法三:并查集的方式
并查集是最近新了解的一个概念,其主要会用在元素分组中,它的主要思想是将一个元素作为一堆元素的代表,然后不断合并。具体可以看这个链接:
https://zhuanlan.zhihu.com/p/93647900/

class Solution {
    int[][] dn = {{1,0}, {-1,0}, {0,1}, {0,-1}};
    class UnionFind{
        int count;
        int[] parent;
        int[] rank;
        public UnionFind(char[][] grid){
            count = 0;
            int m = grid.length;
            int n = grid[0].length;
            parent = new int[m*n];
            rank = new int[m*n];
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    if(grid[i][j] == '1'){
                        parent[i*n+j] = i*n+j;
                        count++;
                    }
                    rank[i*n+j] = 0;
                }
            }
        }
        //查找父节点
        public int find(int i){
            //路径压缩
            if(parent[i] != i)parent[i] = find(parent[i]);
            return parent[i];
        }
        //融合两个并查集
        public void union(int x, int y){
            int rootx = find(x);
            int rooty = find(y);
            if(rootx != rooty){
                if(rank[rootx] > rank[rooty]){
                    parent[rooty] = rootx;
                }else if(rank[rooty] > rank[rootx]){
                    parent[rootx] = rooty;
                }else{
                    //试想当两个节点的秩都是一样时,一个节点连在了另一个节点后边,那么秩确实得加1
                    parent[rootx] = rooty;
                    rank[rooty]++;
                }
                count--;
            }
        }
        //返回并查集的数量
        public int getCount(){
            return count;
        }
    }
    public int numIslands(char[][] grid) {
        if(grid == null || grid.length == 0){
            return 0;
        }
        int nr = grid.length;
        int nc = grid[0].length;
        int nums_islands = 0;
        UnionFind uf = new UnionFind(grid);
        for (int i = 0; i < nr; i++) {
            for (int j = 0; j < nc; j++) {
                if(grid[i][j] == '1'){
                    grid[i][j] = '0';
                    if(i-1 >= 0 && grid[i-1][j] == '1'){
                        uf.union(i*nc+j, (i-1)*nc+j);
                    }if (i + 1 < nr && grid[i+1][j] == '1') {
                        uf.union(i * nc + j, (i+1) * nc + j);
                    }
                    if (i - 1 >= 0 && grid[i][j-1] == '1') {
                        uf.union(i * nc + j, i * nc + j - 1);
                    }
                    if (i + 1 < nc && grid[i][j+1] == '1') {
                        uf.union(i * nc + j, i * nc + j + 1);
                    }
                }
            }
        }
        return uf.getCount();
    }
}

2、课程表II

//现在你总共有 n 门课需要选,记为 0 到 n-1。
//
// 在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
//
// 给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。
//
// 可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
//
// 示例 1:
//
// 输入: 2, [[1,0]]
//输出: [0,1]
//解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。
//
// 示例 2:
//
// 输入: 4, [[1,0],[2,0],[3,1],[3,2]]
//输出: [0,1,2,3] or [0,2,1,3]
//解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
// 因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。
//
//
// 说明:
//
//
// 输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
// 你可以假定输入的先决条件中没有重复的边。
//
//
// 提示:
//
//
// 这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
// 通过 DFS 进行拓扑排序 - 一个关于Coursera的精彩视频教程(21分钟),介绍拓扑排序的基本概念。
//
// 拓扑排序也可以通过 BFS 完成。
//
//
// Related Topics 深度优先搜索 广度优先搜索 图 拓扑排序

拓扑排序中很重要的一个概念就是有向图,一个n个节点的有向图,对于任意一条有向边(u,v),u在排列中都出现在 v 的前面。我们的主要任务就是判断G中是否存在环。也是两种做法,一种深度优先遍历,还有一种广度优先遍历,具体的看官方题解:
https://leetcode-cn.com/problems/course-schedule-ii/solution/ke-cheng-biao-ii-by-leetcode-solution/
这两种方法还是有较大不同的,深度优先遍历会先遍历到每个叶子节点,在判断的过程中观察是否有环的出现。而广度优先遍历则是从所有没有入边的节点开始判断,然后去掉这些节点,直到最后可以将所有的节点都去掉。
这里只放深度优先遍历的代码:

class Solution {
    //存储有向图
    List<List<Integer>> edges;
    //用于标记每个节点的状态,0=未搜索,1=搜索中,2=已完成
    int[] visited;
    //用数组来模拟栈,下标n-1为栈底,0为栈顶
    int[] result;
    //判断有向图中是否有环
    boolean valid = true;
    //栈下标
    int index;
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        edges = new ArrayList<List<Integer>>();
        for (int i = 0; i < numCourses; i++) {
            edges.add(new ArrayList<Integer>());
        }
        visited = new int[numCourses];
        result = new int[numCourses];
        index = numCourses - 1;
        for (int[] info : prerequisites) {
            edges.get(info[1]).add(info[0]);
        }
        //每次挑选一个未搜索节点,开始进行深度优先搜索
        for (int i = 0; i < numCourses && valid; i++) {
            if(visited[i] == 0){
                dfs(i);
            }
        }
        if(!valid){
            return new int[0];
        }
        return result;
    }
    private void dfs(int u){
        //将节点记为搜索中
        visited[u] = 1;
        //搜索其相邻的节点
        //只要发现环,立刻停止搜索
        for (int v :edges.get(u)) {
            if(visited[v] == 0){
                dfs(v);
            }else if(visited[v] == 1){
                valid = false;
                return;
            }
        }
        visited[u] = 2;
        result[index--] = u;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值