赞
踩
地址:
力扣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
a | b | c | d | e | f | |
a | 1 | 1 | 1 | 0 | 0 | 0 |
b | 1 | 1 | 0 | 1 | 0 | 0 |
c | 1 | 0 | 1 | 0 | 0 | 0 |
d | 0 | 1 | 0 | 1 | 0 | 0 |
e | 0 | 0 | 0 | 0 | 1 | 1 |
f | 0 | 0 | 0 | 0 | 1 | 1 |
直接连接或者间接连接都算作连接,这些城市就划定为一个省,依据二维数组来求得省份数量
这类题目可以变换为朋友圈数量,亲戚数量,换个名字而已,只要有关系就是一个单位
方法一、深度优先 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的根节点是自己
Idx | 0 | 1 | 2 | 3 | 4 |
Val | 0 | 1 | 2 | 2 | 3 |
我们还需要构建一个表,用于表明层级,因为存在合并树的情况,为了加快寻找最终根节点的速度,我们需要把 小一点的树 合并到 大一点的树
初始值都是 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:
初始值:
Idx | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Val | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
最终值:
Idx | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Val | 1 | 9 | 2 | 5 | 9 | 9 | 9 | 1 | 9 | 9 | 9 | 9 | 12 | 9 | 9 |
level:
初始值:
Idx | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Val | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
最终值:
Idx | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Val | 2 | 2 | 1 | 2 | 2 | 3 | 1 | 1 | 1 | 3 | 1 | 1 | 1 | 1 | 1 |
通过遍历 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;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。