算法分析与设计
情侣牵手
题目描述
选题原因
本周学习了图,因此选择了图算法专项练习,之前做了中等难度的题目,较为轻松,因此本次选了一道困难难度的题目。本来选择了
684.冗余连接
。
但是题目本身有歧义:题目要求当出现多条边可以删除时,删除最先出现的边,而在例子中给的却是删除了后出现的边,在完成代码后,发现结果总会有一个过不去。(测试样例中有两题,一题答案选择了先出现的边,而另一个选择了后出现的边)。
因此,选了另一题,即情侣牵手
。
题目分析
怎么求最短步骤
本题并没有直接用到有关图的算法,却使用了图的思想。
题目中包含了一条迷惑信息:交换最少的步骤
。在最开始考虑的时候我一直在想会不会交换的顺序也会导致结果的差异,但是结合图算法思想,给了我思路的提示:交换座位的时候要按照链表一样,一个接着一个交换。那么我们就看一下为什么这样交换的步骤是最少的。
如果我们把每相邻的两个座位
(例如0,1这样可以把情侣结合在一起而不是1,2这样的)看作是一个节点
,而当出现两个人需要换座位来实现情侣坐在一起时,看作这两个节点有联系
,可以通过一条边
连接在一起。
很容易发现,最后,会构成很多条单链
。而每一条边
就是一次交换
。(当换过去的人恰好坐在了自己的情侣旁边则说明到了终点)
为什么这样可以求出最短步骤
首先,我们需要知道两个规律。
1.一条链从不同顺序开始会怎么样?
当我们单独的拿出来一条单链出来看,如果我们通过不同的节点开始,那么最后会在哪个节点终止呢?例如:
- 原单链
- 新的单链(如果从C开始)
很容易验证,但是却不太容易证明事实上,可以加一条首尾相连的虚线,便于理解。数学水平有限,就暂时不解释为什么会这样。哭哭。
2.如果交换不同两条链上的座位会怎么样?
我们来看两条不相交的链。为了方便理解,我们把与A配对的数字记为
a1 a2
,同理,还有b1 b2\c1 c2\d1 d2
。(实际上,只需要首、尾、中间块即可表示,其他的部分都可以在交换的过程中忽略)
按照我们之前的记法,B中的组合应当为
<a2, c2>
,而E中的组合应当为<d2, f2>
。现在我们交换B和E,假设交换a2
与d2
。结果会是这样。
变成了一条单链。与原先相比,变成了5条边,加上置换的那一次,一共需要6次操作。(事实上,这样无端的交换只会将多条单链合并成一条单链:因为当交换两条不同的链的时候,就会使得本条链中的元素交换到别的链中,如果我们将每一条链看作一个大的元素(事实上,他是可以形成闭环的,可以看作一个强连通部件),当两个部件建立联系了之后,就会形成更大的回环)
解题思路
1.维护一个数组,里面记录了每一个旅客的座位号。(可以根据座位表转换而来)用以根据序号查询旅客座位(在
row
中的index)
int max = row.size();
int pos[max] = {0};
//存储每个人座位号
for (int i = 0; i < max; i++) {
pos[row[i]] = i;
}
2.从开始时遍历,如果当前座位上是情侣,则跳过。(这也对应构造的图中的
单节点链表
)
int i = 0;
while (i < max) {
if (row[i] / 2 == row[i + 1] / 2) {
i = i + 2;
}
3.如果当前座位上的不是情侣,则说明找到了一条单链的某个节点,根据第一条性质,可以直接将这一个节点作为开始节点,开始调整这一条单链上的所有座位,规则如下:
- 让当前的邻座和情侣换位置。(沿
边
走)- 邻座到了新地方,以同样的规则对待自己的邻座。(继续走)
- 如果旁边就是自己的情侣,则终止。(到达尾部节点)
else {
//当前序号及座位号
int me = row[i];
int my_site = pos[i];
//情侣序号及座位号
int lover;
if (me % 2 == 0) {
lover = me + 1;
} else {
lover = me - 1;
}
int lover_site = pos[lover];
//当前旁边旅客序号及座位号
int beside = row[i + 1];
int beside_site = pos[beside];
//每次配对,让当前座位的情侣过来,与旁边的人交换,旁边的人交换过去后,再次循环
//如果交换过去的人旁边就是自己的情侣,则循环结束
while (my_site / 2 != lover_site / 2) {
sum++;
row[beside_site] = lover; //情侣过来
pos[beside] = lover_site;
row[lover_site] = beside; //原座位走开
pos[lover] = beside_site;
//计算走了的旅客现在的情侣,旁边旅客序号、座位号,并再次循环
me = beside;
my_site = pos[me];
if (me % 2 == 0) {
lover = me + 1;
} else {
lover = me - 1;
}
lover_site = pos[lover];
if (my_site % 2 == 0) {
beside = row[my_site + 1];
} else {
beside = row[my_site - 1];
}
beside_site = pos[beside];
}
i = i + 2;
}
结果
源代码
class Solution {
public:
int minSwapsCouples(vector<int>& row) {
int max = row.size();
int pos[max] = {0};
//存储每个人座位号
for (int i = 0; i < max; i++) {
pos[row[i]] = i;
}
int sum = 0;
int i = 0;
while (i < max) {
//座位上是一对情侣
if (row[i] / 2 == row[i + 1] / 2) {
i = i + 2;
} else {
//当前序号及座位号
int me = row[i];
int my_site = pos[i];
//情侣序号及座位号
int lover;
if (me % 2 == 0) {
lover = me + 1;
} else {
lover = me - 1;
}
int lover_site = pos[lover];
//当前旁边旅客序号及座位号
int beside = row[i + 1];
int beside_site = pos[beside];
//每次配对,让当前座位的情侣过来,与旁边的人交换,旁边的人交换过去后,再次循环
//如果交换过去的人旁边就是自己的情侣,则循环结束
while (my_site / 2 != lover_site / 2) {
sum++;
row[beside_site] = lover; //情侣过来
pos[beside] = lover_site;
row[lover_site] = beside; //原座位走开
pos[lover] = beside_site;
//计算走了的旅客现在的情侣,旁边旅客序号、座位号,并再次循环
me = beside;
my_site = pos[me];
if (me % 2 == 0) {
lover = me + 1;
} else {
lover = me - 1;
}
lover_site = pos[lover];
if (my_site % 2 == 0) {
beside = row[my_site + 1];
} else {
beside = row[my_site - 1];
}
beside_site = pos[beside];
}
i = i + 2;
}
}
return sum;
}
};