题目
描述
给定 n, m, 分别代表一个二维矩阵的行数和列数, 并给定一个大小为 k 的二元数组A. 初始二维矩阵全0. 二元数组A内的k个元素代表k次操作, 设第i个元素为 (A[i].x, A[i].y), 表示把二维矩阵中下标为A[i].x行A[i].y列的元素由海洋变为岛屿. 问在每次操作之后, 二维矩阵中岛屿的数量. 你需要返回一个大小为k的数组.
设定0表示海洋, 1代表岛屿, 并且上下左右相邻的1为同一个岛屿.
样例
样例 1:
输入: n = 4, m = 5, A = [[1,1],[0,1],[3,3],[3,4]]
输出: [1,1,2,2]
解释:
0. 00000
00000
00000
00000
1. 00000
01000
00000
00000
2. 01000
01000
00000
00000
3. 01000
01000
00000
00010
4. 01000
01000
00000
00011
样例 2:
输入: n = 3, m = 3, A = [[0,0],[0,1],[2,2],[2,1]]
输出: [1,1,2,2]
分析
看到这道题首先我想到了dfs的做法,就是每次添加之后都dfs判断一次岛屿的数量,但是每次dfs都会做很多重复的工作,所以这里我们处理这种集合的问题,然后频繁的做查询的工作,这里我们用并查集效率是最高的
大体思路:每新添加一座岛屿的时候我们就计数加一,然后判断四个方向是否跟别的岛屿连通,如果连通我们就把他们加入到一个集合中,然后计数减一,因为我们每连通一块岛屿就会少一座岛屿
代码部分
1.并查集部分
这里要跟正常的模板稍作改动,首先给定我们的是一个二维的数组,所以这里节点的编号是一个二维的,也就是一个二维的坐标,然后_size要初始化为0(这里我们要计算每次添加后岛屿的数量,而不是最后的数量)
构造函数
//行 列 给定数组长度
disjoinset(int n,int m,int len)
{
_size=0;
for(int i=0;i<n;i++)
{
vector<Point> tmp;
for(int j=0;j<m;j++)
{
Point p(i,j);
tmp.push_back(p);
}
father.push_back(tmp);
}
}
查找函数
Point find(Point p)
{
if(father[p.x][p.y].x==p.x&&father[p.x][p.y].y==p.y)
return p;
return father[p.x][p.y]=find(father[p.x][p.y]);
}
连接函数
void connect(Point a,Point b)
{
Point root_a=find(a);
Point root_b=find(b);
if(root_a.x==root_b.x&&root_a.y==root_b.y)
return ;
father[root_a.x][root_a.y]=root_b;
_size--;
}
2.具体实现(解题部分)
初始化并查集后,我们要建立一个二维数组代表地图,然后初始化一个一维数组存储每次更新完岛屿后的结果
vector<vector<int> > _map(n,vector<int>(m,0));
disjoinset dis(n,m,operators.size());
vector<int> ans;
循环的遍历给定的数组每次改变地图,但是要做一下特判,判断当前位置是否存在岛屿,如果已经存在岛屿就不用后续的操作,如果之前不存在,我们就先计数加一,然后对四个方向进行判断,判断四个方向是否合法,且四个方向是否可以连通,每经历一次岛屿的更新我们记录一次答案
for(int i=0;i<operators.size();i++)
{
int x=operators[i].x;
int y=operators[i].y;
if(_map[x][y]!=1)
{
_map[x][y]=1;
dis._size++;
}
for(int j=0;j<4;j++)
{
int nowx=x+dx[j];
int nowy=y+dy[j];
if(nowx>=0&&nowx<n&&nowy>=0&&nowy<m&&
_map[nowx][nowy]==1)
{
dis.connect(operators[i],Point(nowx,nowy));
}
}
ans.push_back(dis._size);
}
完整代码
#include <bits/stdc++.h>
using namespace std;
struct Point {
int x;
int y;
Point() : x(0), y(0) {}
Point(int a, int b) : x(a), y(b) {}
};
class disjoinset
{
public:
vector<vector<Point> > father;
int _size;
//行 列 给定数组长度
disjoinset(int n,int m,int len)
{
_size=0;
for(int i=0;i<n;i++)
{
vector<Point> tmp;
for(int j=0;j<m;j++)
{
Point p(i,j);
tmp.push_back(p);
}
father.push_back(tmp);
}
}
Point find(Point p)
{
if(father[p.x][p.y].x==p.x&&father[p.x][p.y].y==p.y)
return p;
return father[p.x][p.y]=find(father[p.x][p.y]);
}
void connect(Point a,Point b)
{
Point root_a=find(a);
Point root_b=find(b);
if(root_a.x==root_b.x&&root_a.y==root_b.y)
return ;
father[root_a.x][root_a.y]=root_b;
_size--;
}
};
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
class Solution {
public:
vector<int> numIslands2(int n, int m, vector<Point> &operators) {
vector<vector<int> > _map(n,vector<int>(m,0));
disjoinset dis(n,m,operators.size());
vector<int> ans;
for(int i=0;i<operators.size();i++)
{
int x=operators[i].x;
int y=operators[i].y;
if(_map[x][y]!=1)
{
_map[x][y]=1;
dis._size++;
}
for(int j=0;j<4;j++)
{
int nowx=x+dx[j];
int nowy=y+dy[j];
if(nowx>=0&&nowx<n&&nowy>=0&&nowy<m&&
_map[nowx][nowy]==1)
{
dis.connect(operators[i],Point(nowx,nowy));
}
}
ans.push_back(dis._size);
}
return ans;
}
};
int main (void)
{
int n=3,m=3;
vector<Point> A={ {Point(0,0)},{Point(0,1)},{Point(2,2)},{Point(2,1)} };
Solution s;
vector<int> ans=s.numIslands2(n,m,A);
for(int i=0;i<ans.size();i++)
cout<<ans[i]<<" ";
return 0;
}
总结
这道题如果用dfs的情况,时间复杂度会很高,代码量会比并查集少一些。
并查集就是一个不相交的集合合并到一起,然后查连通块的数量,或者查当前连通块中节点的数量,或者查两个节点是否相通
解决这种问题时并查集的效率是最高的