130.被围绕的区域
这个力扣系列都可以作为我的日记了233==
昨天姐姐来家里了就一起去爬山玩玩,晚上一起吃饭聊天也无心做leetcode,leetcode对我小白来说还是很需要脑子的呀(笑,所以就开始学简单的shell编程,到今天下午把shell入门的课上完了,送姐姐回家再玩一玩刷刷b站就现在这个点啦。
好像除了大学其他都确定开学时间了,那么我们应该也快了,最多也就只能在家待一个月了吧,要恢复早起生物钟了,决定明天开始早起跑步🏃之后找工作强度大大的有个好身体很要紧的吼⛽ 好啦,现在已经快九点半了,就意思一下入门个union find,然后把前七天的记录都做个整理:在前面做个目录跳转,方便之后复习。
最后尝试用shell实现简单的俄罗斯方块吧给shell基础做个小小总结。
什么时候用union find
找连通结构的时候
看题:
547. 朋友圈
思路:
连通问题基本思路模板:①先把每一个节点都处理成一个独立的群体,就是把父节点设置成它自己;②每个父节点是一个群体的代表,如果碰到两个节点可以合并(两个节点是联通的),就调用uni,③先找到这两个点对应的父节点,然后把它们的父节点一个指向另一个,也就是说让它们share同一个父节点,这样就等于说把两个群体合并成了一个群体
- 初始化每个节点一个群体,就是fathers[i]就是自己
- 遍历,找连通,找到之后分别看这两个点的父节点是谁,合并父节点(就是一个父节点的值变成另一个父节点的值)先find后uni
- 返回值:有几个fathers[i] == i,就有几个群体
代码:
class Solution {
public:
vector<int> fathers;
int find(int i)
{
while(fathers[i]!=i){
i = fathers[i];
} //要去找它的父节点
return i;
}
//找到然后开始合并
void uni(int i, int j)
{
int f1 = find(i);
int f2 = find(j);
fathers[f2] = f1;
}
int findCircleNum(vector<vector<int>>& M) {
int n = M.size();
if(n == 0){
return 0;
}
for(int i = 0; i<n; ++i){//先初始化每个节点的父节点是自己,每个节点是一个群体
fathers.push_back(i);
}
//开始找有没有现在连通的,就是遍历,但是矩阵是对称的,所以只要一半就可以了
for(int i = 0; i<n-1; ++i){
for(int j = i+1; j<n; ++j )//这个ij循环的停止条件可以直接反应出来好厉害
{
if(M[i][j])//如果发下有连通的就去调用uni函数,合并成一个群体
{
uni(i,j);//union是C++中自带的所以不可以直接叫union函数,
}
}
}
int count = 0;
for(int i = 0; i<n; ++i)
{
if(fathers[i] == i)
{
count++;
}
}
return count;
}
};
问题:
可以将朋友圈问题类比到树的问题,这样用方法1的问题就是有可能最坏情况时间复杂度会是O(n²),5和4连通然后合并的时候5跟了4的父节点,fathers[5] = fathers[4],然后下一个3又和5连通,于是先分别去找3和5的父节点,发现5的父节点是4,那么3和4去合并,结果4跟了3的父节点……这样每次找父节点都会从头遍历到尾O(n²),但是树的问题我们一般会优化到log
向树优化:在挂的时候改变谁挂谁
在每次合并的时候看看合并之后树的高度,向3挂在4上走路径(高度)就是2,而3挂在5上路径就是1了,高度更小,所以我们挂在5上
或者就是不断该路径,压缩路径,还是以rank为标准来说的,1来了,1发现和5可以连通,就往上找,发现5 3 2的父节点都是2,就把5的路径改掉,把5挂到2上,rank就减少了1,就是这样树就是不断压缩
优化1比较rank代码
加rank比较深度
1,初始化为1,别忘了
2,在合并的时候根据rank决定谁挂谁
class Solution {
public:
vector<int> fathers;
vector<int> rank;
int find(int i)
{
while(fathers[i]!=i){
i = fathers[i];//因为挂的时候又可能5的父节点是4,4的父节点是3,3的是2这样,2是自己了,ok那就是刚刚3挂2上了
} //要去找它的父节点
return i;
}
//找到然后开始合并
void uni(int i, int j)
{
int f1 = find(i);
int f2 = find(j);
//fathers[f2] = f1;
if(rank[f1] == rank[f2])
{
fathers[f2] = f1;
}else if(rank[f1]>rank[f2]){
fathers[f2] = f1;
}else{
fathers[f1] = f2;
}
}
int findCircleNum(vector<vector<int>>& M) {
int n = M.size();
if(n == 0){
return 0;
}
for(int i = 0; i<n; ++i){//先初始化每个节点的父节点是自己,每个节点是一个群体
fathers.push_back(i);
rank.push_back(1);
}
//开始找有没有现在连通的,就是遍历,但是矩阵是对称的,所以只要一半就可以了
for(int i = 0; i<n-1; ++i){
for(int j = i+1; j<n; ++j )//这个ij循环的停止条件可以直接反应出来好厉害
{
if(M[i][j])//如果发下有连通的就去调用uni函数,合并成一个群体
{
uni(i,j);//union是C++中自带的所以不可以直接叫union函数,
}
}
}
int count = 0;
for(int i = 0; i<n; ++i)
{
if(fathers[i] == i)
{
count++;
}
}
return count;
}
};
优化2路径压缩代码
就在原始上改了find,这个优化在大数据量下有效
第一个循环,一路找到唯一的能代表群体的父节点 ,但第一个循环其实i是已经变了,我们只能知道它的父节点是谁
第二个循环,保留了j=i,所以直接把i的父节点设为刚刚找到的值,rank迅速减少,下次也不用这样一直往下递归着找了。
class Solution {
public:
vector<int> fathers;
int find(int i)
{
int j = i;
while(fathers[i]!=i){
i = fathers[i];
}
while(fathers[j]!=i)
{
fathers[j] = i;
}
return i;
}
//找到然后开始合并
……
200.岛屿个数
题目
思路:
先将每个岛屿初始化成独立的岛屿,连通就合并成一个岛屿,模板:
1,初始化,fathers都是自己
2,找连通,找连通点的父节点,写uni函数和find函数
3,返回值
代码:
class Solution {
public:
vector<int> fathers;
int find(int i)
{
int j = i;
while(fathers[i] != i)
{
i = fathers[i];
}
return i;
}
void uni(int i, int j)
{
int f1 = fathers[i];
int f2 = fathers[j];
fathers[f1] =f2;
}
int numIslands(vector<vector<char>>& grid) {
if(!grid.size() || !grid[0].size()){
return 0;
}
int m = grid.size();
int n = grid[0].size();
//vector<int> fathers;
//先初始化,每个父节点是自己
for(int i =0; i<m*n;i++)
{
fathers.push_back(i);
}
//先遍历每一个点,如果是0就直接跳过,如果是1的话看看它左边和下边就ok
for(int i = 0; i<m;i++)
{
for(int j = 0; j<n; j++)
{
if(grid[i][j] == '0')
{
continue;
}
int cur_pos = i*n+j;
//找到1了,去看看周边有没有可以连通的,就是四周有没有1,左边上边
if(j>0 && grid[i][j-1] == '1')//找到就连通
{
uni(cur_pos, cur_pos-1);//当前位置的左边的
}
if(i>0 && grid[i-1][j] == '1')
{
uni(cur_pos, cur_pos-n);//上一行
}
if(i<m-1 && grid[i+1][j] == '1')
{
uni(cur_pos, cur_pos+n);//下
}
if(j<n-1 && grid[i][j+1] == '1')
{
uni(cur_pos, cur_pos+1);//右
}
}
}
int count = 0;//返回结果就是有多少个父节点,如果连通就会合并父节点
for(int i = 0; i<m; ++i)
{
for(int j = 0; j<n; ++j)
{
int cur_pos = i*n+j;
if(grid[i][j] == '1' && fathers[cur_pos] == cur_pos){
count++;
}
}
}
return count;
}
};
问题:思路就感觉是这个给思路但是一直报错??
305. 岛屿数Ⅱ🔒是锁住的!!
题目:
一个一个点告诉你,,先加第一个点有多少个岛屿,再加第二个点有多少个岛屿……有一个addLand的操作,一个一个点的加进来,、
输入:m=3, n=3,放入的位置依次[[0,0],[0,1],[1,2],[2,1]],输出[1,1,2,3]
思路:
读题目:一开始都是水,每次都给一个坐标,在那个坐标放一个陆地,看看有没有和之前放的陆地可连通的,输出每一次addLand操作之后的结果,还是unifind的,不同的地方就是uni的时候要加个判断,每次有新的父节点产生即新合并的话个数要减1,要知道分别结果里push_back的原因
还是先写模板:
1,是先给了一个矩阵尺寸,就先把每一个点初始化都置为0vector<vectr<char>> grid(m,vector<char>(n,'0');
,然后常规初始化父节点为自己
2,开始找连通,因为给了一组放1的位置点,所以要依次对每一个坐标点做处理,放进去的位置是1
3,有连通的点,分别去找它们的父节点,父节点如果相同就告诉主函数个数不用减1,不相同就合并并且个数减1
★★★是什么时候对点进行赋值的呢,又分别两次 push_back(count)
是什么意思??
嗷嗷,我们不用管赋值的情况,因为这个就是一个它们放进去的operation,不用在意。第一个push_back是避免重复用的
代码:
class Solution{
public:
int find(int i)
{
while(fathers[i]!=i)
{
i = fathers[i];
}
return i;
}
int uni(int i, intj)
{
int f1 = find(i);
int f2 = find(j);
if(f1==f2)
{return 0;}//就是同一个节点的话就不用更新,因为涉及到count个数
fathers[f2]=f1;
return 1;
}
vector<int> fathers;
vector<int> numIsland2(int m, int n, vector<vector<int>>& positions)
{
vector<vectr<char>> grid(m,vector<char>(n,'0');//初始化矩阵
for(int i = 0; i<m*n;i++)
{
fathers.push_back(i);
}
//要定义一个结果集,是一个一维矩阵,内容是每次岛屿的个数
vector<int> result;
int count = 0;//一开始是一个点都没有
//对每一个加入的节点进行grid的遍历,找联通点
for(vector<int> position:positions)//position是一个坐标
{
int i = position[0];
int j = position[1];
if(grid[i][j] == '1')//这个是为了避免重复输入同一个数进行的判断
{
result.push_back(count);//重复无意义直接现在的count压入结果栈
continue;
}
count++;
int cur_pos = i*n+j;
if(i>0 && grid[i-1][j] == '1')
{
if(uni(cur_pos, cur_pos-n))
{
count--;//如果合并成功,个数就要减1
}
}
if(i<m-1 && grid[i+1][j] == '1')
{
if(uni(cur_pos, cur_pos+n))
{
count--;//如果合并成功,个数就要减1
}
}
if(j>0 && grid[i][j-1] == '1')
{
if(uni(cur_pos, cur_pos-1))
{
count--;//如果合并成功,个数就要减1
}
}
if(i>n-1 && grid[i][j+1] == '1')
{
if(uni(cur_pos, cur_pos+1))
{
count--;//如果合并成功,个数就要减1
}
}
result.push_back(count);
}
return result;
}
}
130. 被围绕的区域
题目
思路
设置一个虚拟点dummy,将在边缘的点与这个dummy点化为一个区域,其余不和它们在一个区域的点就是需要覆盖的点,所以
1,初始化fathers点,加一个dummy点
2,遍历board,如果o在边缘就和dummy去uni
3,避免四周o的中间的o被遗漏的情况,如下所示,在找到其他o点时,去找她四周有没有o点,同化成和那个o点一样的父节点。虽然我想到了这个点,但是并没有实际避免它,很gg
代码
//四周有X的话就同化,设置一个dummy节点(参考链表的dummy用法
class Solution {
public:
int find(int i)
{
while(fathers[i]!=i)
{
i = fathers[i];
}
return i;
}
void uni(int i, int j)
{
int f1 = find(i);
int f2 = find(j);
fathers[f1] =f2;
}
vector<int> fathers;
void solve(vector<vector<char>>& board) {
if(board.size()== 0 || board[0].size() == 0)
{
return;
}
//还是先按着模板,初始化fathers节点,每个都是自己
int m= board.size();
int n = board[0].size();
int dummy_pos = m*n;
for(int i = 0; i <= m*n;++i)
{
fathers.push_back(i);
}
//开始找0
for(int i = 0; i<m;i++)
{
for(int j = 0; j<n; j++)
{
if(board[i][j] == 'X')
{
continue;//不管
}
//是o
int cur_pos = i*n+j;//对不能同化的o点进行 dummy大法,其余的o一定是要被同化的
if(i == 0 || i == m-1|| j == 0 ||j == n-1)
{
uni(cur_pos, dummy_pos);
continue;//是边缘上的o和dummmy同化完就退出循环,开始下一个节点的判断处理
}
//是0点但不在边缘上
if(board[i-1][j] == 'o')
{
uni(cur_pos, cur_pos-n);
}
if(board[i+1][j] == 'o')
{
uni(cur_pos, cur_pos+n);
}
if(board[i][j-1] == 'o')
{
uni(cur_pos, cur_pos-1);
}
if(board[i][j+1] == 'o')
{
uni(cur_pos, cur_pos+1);
}
}
}
int dummy_father = fathers[dummy_pos];
for(int i = 0; i<m; ++i)
{
for(int j = 0; j<n; ++j)
{
if(board[i][j] == 'X')
{
continue;
}
int cur_pos = i*n+j;
if(find(cur_pos)!=dummy_father)
{
board[i][j] ='X';
}
}
}
}
};
问题
没通过,完全不觉得写错了,明明中间的o也是和边缘的在一个区域了……
下午shell编程一下,再接着做之前的200题系列,虽然很想把塔罗的直播刷完,今天下午栈和队列明天哈希表,然后字符串,数组与矩阵,图(算是接触过一点了),位运算之后就开始难一点的算法系列了⛽
但是
0201 155最小栈232用栈实现队列84柱状图张最大矩阵654最大二叉树(pq)
0207 dfs 子集ⅠⅡ 全排列ⅠⅡ √
0214 dfs2 22生成左右括号 79 字符搜索 51N皇后 37 数独 ×
0215 并查集uniofind本篇
0221 双指针(排前)
0222 树 110 平衡二叉树 236 二叉树的最近公共祖先 124最短路径 144 94 145 前中后序遍历
0229 股票系列(dp)紧凑2h(must-have)
0307 bfs 二叉树层序遍历 选课(拓扑)ⅠⅡ 网络延迟时间? 最便宜机票?(排前)
0313 迷宫问题
太棒了,后面的三道题都错了呢,也调不出来,睡一觉还是调不出来,算辽,🙂