547. 省份数量

地址:

力扣https://leetcode-cn.com/problems/number-of-provinces/

题目:

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。

示例 1:

输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2


示例 2:


输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
输出:3

提示:

1 <= n <= 200
n == isConnected.length
n == isConnected[i].length
isConnected[i][j] 为 1 或 0
isConnected[i][i] == 1
isConnected[i][j] == isConnected[j][i]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-provinces
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

题目有些多字,我们举个例子:

城市个数:6

城市关系值:城市之间如果直接连接,值=1,否则=0

abcdef
a111000
b110100
c101000
d010100
e000011
f000011

 直接连接或者间接连接都算作连接,这些城市就划定为一个省,依据二维数组来求得省份数量

这类题目可以变换为朋友圈数量,亲戚数量,换个名字而已,只要有关系就是一个单位

方法一、深度优先 DFS

以上图为例,起点就选用第一个城市 a,依据 a 的城市关系 [1,1,1,0,0,0] ,相连的是城市 b

继续以 b 为起点,依据 b 的城市关系 [1,1,0,1,0,0],相连的是城市 d

继续以 d 为起点,依据 d 的城市关系,没有新的城市相连,那么这条线

a->b->d 就截止,开始继续找 a 城市关系相连的下一个新城市 c

所有走过的城市我们会标注,这样寻找时不会走回头路,整个以 a 为起点的连接我们就找完了,标注也打完了,剩下没有打标注的表明不属于 a 代表的省会

继续以第二个城市 b 为起点,如此大循环,因为前序有些已结打了标注,所以,这样的遍历可能不会有标注

遍历完所有的城市,我们的标注也就全部打完了,每一次以某点开始的遍历,完成后表明一个省会的确认

void dfsFindCircle(int** isConnected, int cityNum, int *markArr, int start)
{
	int i;
	for(i=0; i<cityNum; i++)
	{
		//printf("in....start=%d, i=%d, markArr[%d]=%d, isConnected[%d][%d]=%d\n", start, i, i, markArr[i], start, i, isConnected[start][i]);
		
		if(markArr[i] != 1)
		{
			if(isConnected[start][i] == 1)
			{
				markArr[i] = 1;
				dfsFindCircle(isConnected, cityNum, markArr, i);
			}
		}
	}
}

int findCircleNum(int** isConnected, int isConnectedSize, int* isConnectedColSize){
	int i,j;
	int cityNum = *isConnectedColSize;
	int findNum = 0;
	
	int markLen = cityNum;
	int *markArr = (int *)malloc(sizeof(int) * markLen);
	
	for(i=0; i<markLen; i++)
		markArr[i] = 0;
		
	for(i=0; i<cityNum; i++)
	{
		//printf("out....%d, findNum=%d\n", i, findNum);
		if(markArr[i] != 1)
		{
			// DFS
			dfsFindCircle(isConnected, cityNum, markArr, i);
			findNum++;
		}
	}
	
	free(markArr);
	return findNum;
}

方法二、广度优先 BFS

仔细观察城市图,其实与之前的二叉树很像,不过这里可能是多叉树

我们在二叉树的层序遍历时(二叉树遍历之层序遍历111. 二叉树的最小深度),用队列来进行。

这里找城市的连接也是一样

以城市 a 为起点,依据城市关系,与之直接连接的是城市 b,c,这是第一圈城市

分别找以 b,c 为起点的直接连接城市,找到了 d,这是第二圈城市

没有第三圈城市,后续e,f一样的逻辑

入队顺序一样:

1. a 入队

2. 队列不为空时循环处理

        2.1 出队 a

        2.2 直接连接城市 b,c,入队b,c

        2.3 出队 b,直接连接城市 d

        2.4 出队 c,没有直接连接城市

        2.5 出队 d,没有直接连接城市

一次循环完成,这个就是找到的省份,开始下一个城市为起点依据上面的逻辑

int enqueue(int *queue, int len, int *rear, int cityIdx)
{
    //printf("enqueue..rear=%d, len=%d, cityIdx=%d\n", *rear, len, cityIdx);
	if(*rear == len)
		return -1;
		
	queue[*rear] = cityIdx;
	(*rear)++;
	
	return 0;
}

int dequeue(int *queue, int len, int *front, int *rear)
{
	if(*front == *rear)
		return -1;
		
	int idx = *front;
	if(idx == len - 1)
	{
		*front = 0;
		*rear = 0;
	}
	else
		(*front)++;
		
    //printf("dequeue..cityIdx=%d\n", queue[idx]);    
	return queue[idx];
}

int isEmpty(int front, int rear)
{
	return front == rear ? 1:0;
}

void checkMarkArr(int *markArr, int markLen)
{
	int i;
	printf("---checkMarkArr---\n");
	for(i=0; i<markLen; i++)
		printf("markArr[%d]=%d\n", i, markArr[i]);
}

int findCircleNum(int** isConnected, int isConnectedSize, int* isConnectedColSize){
	int i,j;
	int cityNum = *isConnectedColSize;
	int findNum = 0;
	
	/* mark array */
	int markLen = cityNum;
	int *markArr = (int *)malloc(sizeof(int) * markLen);
	for(i=0; i<markLen; i++)
		markArr[i] = 0;
	
	/* queue */
	int front = 0;
	int rear = 0;	
	int queueLen = cityNum*5;
	int *queueArr = (int *)malloc(sizeof(int) * queueLen);
	for(i=0; i<queueLen; i++)
		queueArr[i] = 0;
		
	for(i=0; i<cityNum; i++)
	{
		if(markArr[i] != 1)
		{
			enqueue(queueArr, queueLen, &rear, i);
			
			while(!isEmpty(front, rear))
			{
				int cityIdx = dequeue(queueArr, queueLen, &front, &rear);
				markArr[cityIdx] = 1;
				
				for(j=0; j<cityNum; j++)
				{
					if(isConnected[cityIdx][j] == 1 && markArr[j] != 1)
						enqueue(queueArr, queueLen, &rear, j);
				}
			}
			// printf("checkMarkArr----%d\n", i);
			// checkMarkArr(markArr, markLen);
			findNum++;
		}
	}
	
	free(markArr);
	free(queueArr);
	return findNum;
}

注意:C语言自己造轮子就没有其他高级语言那么爽,这里队列的长度不一定是城市的数量

可能存在闭环的省会样式,这样某个城市对其直接连接城市入队,其他城市可能对同样的城市入队,队列长度就不够了。

以力扣的批量测试,2倍队列长度都不够,干脆直接上5倍,过了也没有细研了

方法三、并查集

城市的关联如同一棵树,多叉树

如果一个城市和一个城市是直接相连的,那么可以认定其中一个为树的根节点

再来一个城市

        如果直接相连根节点,那么也算作根节点的子节点

        如果与根节点的某一个子节点相连,那么与根节点就是间接相连,自然把其归入该树

        或者这个城市本来就是另一棵树的节点,那么就是两棵树的合并

这样的逻辑关系遍历所有城市,构建出来的树的集合,就是省份的个数

我们需要构建一个表,用于表明每个节点的直接根节点:

Idx 表示城市节点

Val 表示城市节点的直接根节点

例如:城市 4 的直接根节点是 3,3 的直接跟节点是 2, 2的根节点是自己

Idx01234
Val01223

我们还需要构建一个表,用于表明层级,因为存在合并树的情况,为了加快寻找最终根节点的速度,我们需要把 小一点的树 合并到 大一点的树

初始值都是 1,当合并同level时,选定为根节点的值++

 有新节点(树),比较level值,小值需要并入大树

 示例:

有 0 ~ 14 共 15 个城市,关系图如下

int isConnected[][]=
{
// 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14
  {1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0},  // 0
  {1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},  // 1
  {0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},  // 2
  {0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},  // 3
  {0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0},  // 4
  {0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0},  // 5
  {0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0},  // 6
  {1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0},  // 7
  {0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0},  // 8
  {0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1},  // 9
  {0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0},  // 10
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0},  // 11
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0},  // 12
  {0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0},  // 13
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}   // 14
};

手动画出关系图:

如果 head 相同不做动作,图示也不进行说明

 节点 0:连接 1,7

 节点 2:孤立

节点 3:连接 5,6

节点 4:连接 9,10

节点 5:连接 10

        5 这颗树与 9这棵树产生了联系,因为树大小一样,合并后level值是要增加的

 节点 6:连接 8,13

        6 属于 9 这棵树,所以 8,13须合入 9 这棵树

 节点 7:连接 8

        7 属于 1 这棵树,小于 9 这棵树,所以须合入 1 到 9

 节点 9:连接 11, 14

        合入 11,14

 节点 12:孤立

 最后生成的就是 2,9,12 三棵树,对应的构建关系:

Head:

                        初始值:

Idx01234567891011121314
Val01234567891011121314

                        最终值:

Idx01234567891011121314
Val1925999199991299


level:

                        初始值:

Idx01234567891011121314
Val111111111111111

                        最终值:

Idx01234567891011121314
Val221223111311111

通过遍历 head数组,如果 Idx = Val,那么就表明这是一个树的根节点,否则就是某个树的子节点

所以只需要找到根节点个个数即可

代码实现:

int findHead(int cityIdx, int *headArr)
{
	int idx;
	
	if(headArr[cityIdx] == cityIdx)
		return cityIdx;
		
	idx = findHead(headArr[cityIdx], headArr);
	return idx;
}

void updateHead(int cityOne, int cityTwo, int *headArr, int *levelArr)
{
	int i = findHead(cityOne, headArr);
	int j = findHead(cityTwo, headArr);
	
	if(i == j)
		return;
		
	if(levelArr[i] >= levelArr[j])
		headArr[j] = i;
	else
		headArr[i] = j;
	
	if(levelArr[i] == levelArr[j])
	{
		levelArr[i]++;
		levelArr[j]++;
	}
}

int findCircleNum(int** isConnected, int isConnectedSize, int* isConnectedColSize){
	int i,j;
	int cityNum = *isConnectedColSize;
	int findNum = 0;
	
	int headArrLen = cityNum;
	int *headArr = (int *)malloc(sizeof(int) * headArrLen);
	
	int levelArrLen = cityNum;
	int *levelArr = (int *)malloc(sizeof(int) * levelArrLen);
	
	for(i=0; i<cityNum; i++)
	{
		headArr[i] = i;
		levelArr[i] = 1;
	}
	
	for(i=0; i<cityNum; i++)
	{
		for(j=i+1; j<cityNum; j++)
		{
			if(isConnected[i][j] == 1)
				updateHead(i, j, headArr, levelArr);
		}
	}
	
	for(i=0; i<cityNum; i++)
	{
		if(headArr[i] == i)
			findNum++;
	}
	
	free(headArr);
	free(levelArr);
	return findNum;
}

查看更多刷题笔记

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值