Leetcode——第七天记录(划 并查集 模板及优化

547. 朋友圈
朋友圈rank优化1
朋友圈路径压缩优化

200. 岛屿个数
305. 岛屿数Ⅱ🔒

130.被围绕的区域
这个力扣系列都可以作为我的日记了233==
昨天姐姐来家里了就一起去爬山玩玩,晚上一起吃饭聊天也无心做leetcode,leetcode对我小白来说还是很需要脑子的呀(笑,所以就开始学简单的shell编程,到今天下午把shell入门的课上完了,送姐姐回家再玩一玩刷刷b站就现在这个点啦。
好像除了大学其他都确定开学时间了,那么我们应该也快了,最多也就只能在家待一个月了吧,要恢复早起生物钟了,决定明天开始早起跑步🏃‍之后找工作强度大大的有个好身体很要紧的吼⛽ 好啦,现在已经快九点半了,就意思一下入门个union find,然后把前七天的记录都做个整理:在前面做个目录跳转,方便之后复习。
最后尝试用shell实现简单的俄罗斯方块吧给shell基础做个小小总结。

什么时候用union find

找连通结构的时候
看题:

547. 朋友圈

在这里插入图片描述

思路:

连通问题基本思路模板:①先把每一个节点都处理成一个独立的群体,就是把父节点设置成它自己;②每个父节点是一个群体的代表,如果碰到两个节点可以合并(两个节点是联通的),就调用uni,③先找到这两个点对应的父节点,然后把它们的父节点一个指向另一个,也就是说让它们share同一个父节点,这样就等于说把两个群体合并成了一个群体

  1. 初始化每个节点一个群体,就是fathers[i]就是自己
  2. 遍历,找连通,找到之后分别看这两个点的父节点是谁,合并父节点(就是一个父节点的值变成另一个父节点的值)先find后uni
  3. 返回值:有几个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 迷宫问题

太棒了,后面的三道题都错了呢,也调不出来,睡一觉还是调不出来,算辽,🙂

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 二分法 5 1.1. 什么是二分查找 5 1.2. 如何识别二分法 5 1.3. 二分法模板 6 1.3.1. 模板一 6 1.3.1.1. 模板代码 6 1.3.1.2. 关键属性 7 1.3.1.3. 语法说明 7 1.3.1.4. Lc69:x的平方根 8 1.3.1.5. Lc374:猜数大小 9 1.3.1.6. Lc33:搜索旋转数组 11 1.3.2. 模板二 13 1.3.2.1. 模板代码 13 1.3.2.2. 关键属性 14 1.3.2.3. 语法说明 14 1.3.2.4. Lc278:第一个错误版本 14 1.3.2.5. Lc162:寻找峰值 16 1.3.2.6. Lc153:寻找旋转排序数组最小值 19 1.3.2.7. Lc154:寻找旋转排序数组最小值II 20 1.3.3. 模板三 22 1.3.3.1. 模板代码 22 1.3.3.2. 关键属性 23 1.3.3.3. 语法说明 23 1.3.3.4. LC-34:在排序数组中查找元素的第一个和最后一个 23 1.3.3.5. LC-658:找到K个最接近的元素 25 1.3.4. 小结 28 1.4. LeetCode中二分查找题目 29 2. 双指针 30 2.1. 快慢指针 31 2.1.1. 什么是快慢指针 31 2.1.2. 快慢指针模板 31 2.1.3. 快慢指针相关题目 32 2.1.3.1. LC-141:链表是否有环 32 2.1.3.2. LC-142:环形链表入口 34 2.1.3.3. LC-876:链表的中间节点 37 2.1.3.4. LC-287:寻找重复数 40 2.2. 滑动窗口 43 2.2.1. 什么是滑动窗口 43 2.1.4. 常见题型 44 2.1.5. 注意事项 45 2.1.6. 滑动窗口模板 45 2.1.7. 滑动窗口相关题目 46 2.1.7.1. LC-3:无重复字符的最长子串 47 2.1.7.2. LC-76:最小覆盖子串 49 2.1.7.3. LC-209:长度最小的子数组 54 2.1.7.4. LC-239:滑动窗口最大值 57 2.1.7.5. LC-395:至少有K个重复字符的最长子串 60 2.1.7.6. LC-567:字符串排列 62 2.1.7.7. LC-904:水果成篮 64 2.1.7.8. LC-424:替换后的最长重复字符 66 2.1.7.9. LC-713:乘积小于K的子数组 67 2.1.7.10. LC-992:K个不同整数的子数组 70 2.3. 左右指针 73 2.3.1. 模板 73 2.3.2. 相关题目 73 2.3.2.1. LC-76:删除倒数第N个节点 74 2.3.2.2. LC-61:旋转链表 76 2.3.2.3. LC-80:删除有序数组中的重复项 79 2.3.2.4. LC-86:分割链表 80 2.3.2.5. LC-438:找到字符串中所有字母的异位词 82 3. 模板 85 2.3.2.6. LC-76:删除倒数第N个节点 85
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值