leetcode刷题记录day025:200和68

200、难度中等:代码注释中有详细解释

方法一:深度优先搜索:
评论区:题解中这里修改了原数组,大家如果面试的时候,要问清楚面试官是否能修改原数组,不能的话就得加入标记数组

思路:不要被题给的二维数组吸引注意力,题目让我们求出岛屿的数量,而构成岛屿的条件:
(1)当前项为1
(2)当前项的左右上下四项值均未0或者无值
于是我们通过下标运算将二维数组网格化,并根据上面的(1)(2)条件:
只有遍历到的当前项为 1 时开始记录岛屿数(把构成当前岛屿的 1 换为 0,原因在后面),不为 1 继续遍历。若为1,再判断上下左右是否为0,只要遇到 1 就把它换为 0,再判断这个新出现的 1 四周是否全为0,直到四周全为 0 或无值时才返回。并遍历二维数组的下一项元素。
当前岛屿被记录下后,我们还应让当前岛屿不会被重复计数,解决措施:
把遍历到的 1 全转换为 0。

class Solution {
    // 化 0 方法
    void dfs(char[][] grid, int r, int c) {
        // 获取二维数组信息
        int nr = grid.length;
        int nc = grid[0].length;
		// 只有当前项为溢出项或值为 0 时才返回
        if (r < 0 || c < 0 || r >= nr || c >= nc || grid[r][c] == '0') {
            return;
        }
		// 上面 if 没通过说明当前项是 1,那就化 0 并检测当前项的四周四个元素是否为 0。 
        grid[r][c] = '0';
        dfs(grid, r - 1, c);
        dfs(grid, r + 1, c);
        dfs(grid, r, c - 1);
        dfs(grid, r, c + 1);
    }

    public int numIslands(char[][] grid) {
        if (grid == null || grid.length == 0) {
            return 0;
        }
		
        // 假设数组[3][6]
        int nr = grid.length; // 3
        int nc = grid[0].length; // 6
        int num_islands = 0; // 岛屿总数
        // 遍历二维数组
        for (int r = 0; r < nr; ++r) {
            for (int c = 0; c < nc; ++c) {
                // 若当前项为1,则开始岛屿计数(把构成当前岛屿的字符1全替换为0)
                if (grid[r][c] == '1') {
                    // 遇到 1 了那肯定是一个岛屿
                    ++num_islands;
                    // 开始化0,传入二维数组和当前元素位置
                    dfs(grid, r, c);
                }
            }
        }

        return num_islands;
    }
}
方法二:广度优先搜索:
评论区:题解中这里修改了原数组,大家如果面试的时候,要问清楚面试官是否能修改原数组,不能的话就得加入标记数组

原理:为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 1,则将其加入队列,开始进行广度优先搜索。在广度优先搜索的过程中,每个搜索到的 1 都会被重新标记为 0。直到队列为空,搜索结束。
最终岛屿的数量就是我们进行广度优先搜索的次数。

class Solution {
    public int numIslands(char[][] grid) {
        if (grid == null || grid.length == 0) {
            return 0;
        }

        int nr = grid.length;
        int nc = grid[0].length;
        int num_islands = 0;
		
        // 遍历所有项
        for (int r = 0; r < nr; ++r) {
            for (int c = 0; c < nc; ++c) {
                // 若当前项为 1 
                if (grid[r][c] == '1') {
                    // 总岛屿数 +1
                    ++num_islands;
                    // 当前项化0
                    grid[r][c] = '0';
                    Queue<Integer> neighbors = new LinkedList<>();
                    // 为什么要传入 r * nc + c:为了一项值记录两个数据。具体在下面有描述
                    neighbors.add(r * nc + c);
                    // 只要队列不为空就一直循环,原因:
                    // 只要队列里有值,就说明四周中有一项元素不为 0 
                    // 具体看下面描述
                    while (!neighbors.isEmpty()) {
                        int id = neighbors.remove();
                        // row 行:
                        // 因为队列弹出的值是r * nc + c,所以除以 nc 后必得r(c作为余数被舍弃掉)
                        // 为什么不担心 c 大于 nc 导致除后得到r+1:
                        // 因为 c 是列数并且是以0~n-1形式来描述 nc 的1~n,所以 c 永远小于 nc
                        int row = id / nc;
                        // col = column 列:
                        // 同理,得到的是余数 c
                        int col = id % nc; 
                        // 在不溢出前提下判断四周元素是否为 1,
                        // 是 1 则加入队列,这说明还需继续化 0,没达到四周全 0 
                        if (row - 1 >= 0 && grid[row-1][col] == '1') {
                            neighbors.add((row-1) * nc + col);
                            // 加入完队列就化 0
                            grid[row-1][col] = '0';
                        }
                        if (row + 1 < nr && grid[row+1][col] == '1') {
                            neighbors.add((row+1) * nc + col);
                            grid[row+1][col] = '0';
                        }
                        if (col - 1 >= 0 && grid[row][col-1] == '1') {
                            neighbors.add(row * nc + col-1);
                            grid[row][col-1] = '0';
                        }
                        if (col + 1 < nc && grid[row][col+1] == '1') {
                            neighbors.add(row * nc + col+1);
                            grid[row][col+1] = '0';
                        }
                    }
                }
            }
        }

        return num_islands;
    }
}
方法三:并查集:

原理:使用并查集代替搜索。
为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 1,则将其与相邻四个方向上的 1 在并查集中进行合并。
最终岛屿的数量就是并查集中连通分量的数目。
并查集的具体实现和代码解释:https://blog.csdn.net/m0_52937388/article/details/120755588

class Solution {
    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[rootx] < rank[rooty]) {
                    parent[rootx] = rooty;
                } else {
                    parent[rooty] = rootx;
                    rank[rootx] += 1;
                }
                --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 num_islands = 0;
        UnionFind uf = new UnionFind(grid);
        for (int r = 0; r < nr; ++r) {
            for (int c = 0; c < nc; ++c) {
                if (grid[r][c] == '1') {
                    grid[r][c] = '0';
                    if (r - 1 >= 0 && grid[r-1][c] == '1') {
                        uf.union(r * nc + c, (r-1) * nc + c);
                    }
                    if (r + 1 < nr && grid[r+1][c] == '1') {
                        uf.union(r * nc + c, (r+1) * nc + c);
                    }
                    if (c - 1 >= 0 && grid[r][c-1] == '1') {
                        uf.union(r * nc + c, r * nc + c - 1);
                    }
                    if (c + 1 < nc && grid[r][c+1] == '1') {
                        uf.union(r * nc + c, r * nc + c + 1);
                    }
                }
            }
        }

        return uf.getCount();
    }
}

268、难度简单:

方法一:原创:排序:

原理:序列范围为[0,n]总元素数是nums数组的长度+1,而我们要找出这个缺失的+1
所以我们先排序数组,让其达到和序列几乎相同的形式。然后我们使用循环,遍历次数和数组长度等长,如果该+1项在[0,n-1]内就可以在循环中被检测出并return
若该+1项是n,由于循环触及不到它,那么我们就直接在for循环外return它

class Solution {
    public int missingNumber(int[] nums) {
        int len = nums.length;
        Arrays.sort(nums);
		
        for(int i = 0; i < len; i++){
            if(nums[i] != i){
                return i;
            }
        }
        
        return len;
    }
}
方法二:哈希表:

原理类似:排序是给数组排完序,遍历序列,然后对比当前项和当前数组元素是否相等。
而哈希表则是无需排序数组,将数组所有值存入哈希表中,遍历序列,查找当前项是否存在于数组中

class Solution {
    public int missingNumber(int[] nums) {
        Set<Integer> numSet = new HashSet<Integer>();
        for (int num : nums) numSet.add(num);

        int expectedNumCount = nums.length + 1;
        for (int number = 0; number < expectedNumCount; number++) {
            if (!numSet.contains(number)) {
                return number;
            }
        }
        return -1;
    }
}
方法三:位运算

原理:异或运算(XOR)满足结合律,并且对一个数进行两次完全相同的异或运算会得到原来的数
所以我们让序列 0~n 的全部元素和数组的全部元素进行异或,得到的值就是确实的数字
例如n为3时,缺失数为2时:(012^3) ^ (031) 左侧为序列里的,右侧为数组里的,相同元素异或完得到0,而 0^x = x。,所以运算结果得到2。
而for循环的下标正好可以看做是序列里的元素,但数组元素个数注定比序列元素个数少一个,而循环次数是和数组元素个数相同的,所以我们直接把循环缺少的(也就是循环 [0,n) 达不到序列里的那一项元素 n)直接作为用来异或的起始元素(也就是最重要返回的变量)这样我们可以实现一次循环得到结果。

class Solution {
    public int missingNumber(int[] nums) {
        // 将循环达不到的数字 n 直接作为起始元素
        int missing = nums.length;
        for (int i = 0; i < nums.length; i++) {
            missing ^= i ^ nums[i];
        }
        return missing;
    }
}
方法四:数学:评论区提出高斯求和有溢出风险

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QnpTHTne-1634143398906)(268. 丢失的数字.assets/image-20211013133243872.png)]

class Solution {
    public int missingNumber(int[] nums) {
        // 将循环达不到的数字 n 直接作为起始元素
        int missing = nums.length;
        for (int i = 0; i < nums.length; i++) {
            missing ^= i ^ nums[i];
        }
        return missing;
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeYello

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值