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;
}
}
方法四:数学:评论区提出高斯求和有溢出风险
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;
}
}