Hard
题目描述
有 n 对情侣坐在连续排列的 2n 个座位上,想要牵到对方的手。
人和座位用一个整数数组 row 表示,其中 row[i] 是坐在第 i 个座位上的人的 ID。情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2n-2, 2n-1)。
返回最少交换次数,使得每对情侣可以相邻而坐。一次交换中,你可以选择任意两个人,让他们站起来交换座位。
示例 1:
输入:row = [0,2,1,3]
输出:1
解释:只需要交换 row[1] 和 row[2] 的位置。
示例 2:
输入:row = [3,2,0,1]
输出:0
解释:无需交换,所有的情侣都已经可以手牵手了。
提示:
2n == row.length2 <= n <= 300 <= row[i] < 2nrow中所有元素均不相同
解题思路
这道题可以用图论的思路来解决,将其转化为寻找图中连通分量的问题。
核心思路: 将每个沙发(两个相邻座位)看作一个节点。如果一对情侣分别坐在不同的沙发上,就在这两个沙发之间连一条边。这样就构成了一个图。
关键观察:
- 每个连通分量中如果有 k 个节点(沙发),那么需要 k-1 次交换才能让这个连通分量中的所有情侣都坐到一起
- 总的交换次数就是所有连通分量的 (节点数-1) 之和
算法步骤:
- 遍历每个沙发(每两个相邻座位),检查坐在上面的两个人是否为情侣
- 如果不是情侣,就在对应的沙发之间建立连边关系
- 使用并查集或DFS找出所有连通分量
- 计算每个连通分量需要的交换次数并累加
贪心解法(推荐): 也可以用更直接的贪心方法:从左到右遍历每个沙发,如果当前沙发上的两个人不是情侣,就找到其中一个人的伴侣,与另一个位置的人交换。
代码实现
class Solution {
public:
int minSwapsCouples(vector<int>& row) {
int n = row.size() / 2;
vector<int> parent(n);
// 初始化并查集
for (int i = 0; i < n; i++) {
parent[i] = i;
}
function<int(int)> find = [&](int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
};
auto unite = [&](int x, int y) {
int px = find(x), py = find(y);
if (px != py) {
parent[px] = py;
}
};
// 建立连通关系
for (int i = 0; i < row.size(); i += 2) {
int couch1 = row[i] / 2; // 第一个人所属的情侣组
int couch2 = row[i + 1] / 2; // 第二个人所属的情侣组
unite(couch1, couch2);
}
// 统计连通分量
unordered_map<int, int> groups;
for (int i = 0; i < n; i++) {
groups[find(i)]++;
}
int swaps = 0;
for (auto& [root, size] : groups) {
swaps += size - 1;
}
return swaps;
}
};
class Solution:
def minSwapsCouples(self, row: List[int]) -> int:
n = len(row) // 2
parent = list(range(n))
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
px, py = find(x), find(y)
if px != py:
parent[px] = py
# 建立连通关系
for i in range(0, len(row), 2):
couch1 = row[i] // 2 # 第一个人所属的情侣组
couch2 = row[i + 1] // 2 # 第二个人所属的情侣组
union(couch1, couch2)
# 统计连通分量
from collections import defaultdict
groups = defaultdict(int)
for i in range(n):
groups[find(i)] += 1
# 计算总交换次数
swaps = 0
for size in groups.values():
swaps += size - 1
return swaps
public class Solution {
public int MinSwapsCouples(int[] row) {
int n = row.Length / 2;
int[] parent = new int[n];
// 初始化并查集
for (int i = 0; i < n; i++) {
parent[i] = i;
}
int Find(int x) {
if (parent[x] != x) {
parent[x] = Find(parent[x]);
}
return parent[x];
}
void Union(int x, int y) {
int px = Find(x), py = Find(y);
if (px != py) {
parent[px] = py;
}
}
// 建立连通关系
for (int i = 0; i < row.Length; i += 2) {
int couch1 = row[i] / 2; // 第一个人所属的情侣组
int couch2 = row[i + 1] / 2; // 第二个人所属的情侣组
Union(couch1, couch2);
}
// 统计连通分量
Dictionary<int, int> groups = new Dictionary<int, int>();
for (int i = 0; i < n; i++) {
int root = Find(i);
groups[root] = groups.GetValueOrDefault(root, 0) + 1;
}
int swaps = 0;
foreach (var size in groups.Values) {
swaps += size - 1;
}
return swaps;
}
}
var minSwapsCouples = function(row) {
const n = row.length / 2;
const parent = Array.from({length: n}, (_, i) => i);
function find(x) {
if (parent[x] !== x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
function union(x, y) {
const px = find(x), py = find(y);
if (px !== py) {
parent[px] = py;
}
}
// 建立连通关系
for (let i = 0; i < row.length; i += 2) {
const couch1 = Math.floor(row[i] / 2); // 第一个人所属的情侣组
const couch2 = Math.floor(row[i + 1] / 2); // 第二个人所属的情侣组
union(couch1, couch2);
}
// 统计连通分量
const groups = new Map();
for (let i = 0; i < n; i++) {
const root = find(i);
groups.set(root, (groups.get(root) || 0) + 1);
}
let swaps = 0;
for (const size of groups.values()) {
swaps += size - 1;
}
return swaps;
};
复杂度分析
| 复杂度类型 | 并查集解法 |
|---|---|
| 时间复杂度 | O(n·α(n)) |
| 空间复杂度 | O(n) |
其中 n 是情侣对数,α(n) 是阿克曼函数的反函数,在实际应用中可以看作常数。
相关题目
. First Missing Positive (Hard)
. Missing Number (Easy)
. K-Similar Strings (Hard)