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;
}
}