N couples sit in 2N seats arranged in a row and want to hold hands. We want to know the minimum number of swaps so that every couple is sitting side by side. A swap consists of choosing any two people, then they stand up and switch seats.
The people and seats are represented by an integer from 0
to 2N-1
, the couples are numbered in order, the first couple being (0, 1)
, the second couple being (2, 3)
, and so on with the last couple being (2N-2, 2N-1)
.
The couples' initial seating is given by row[i]
being the value of the person who is initially sitting in the i-th seat.
思路:
题意大致为:给你N对数,通过一系列swap之后,使得在每对数中,一个数为偶数,另一个数比它大1。求需要的swap的次数。
题目归类为graph,然而直接的解题方法比图的方法更容易想到:用队列的思想,如果队头的一对数满足要求,直接pop,否则找到一个合适的数对,与之swap(可能swap之后两对数都满足要求,也可能只有对头满足要求),计数加一,再pop。代码如下:
class Solution {
public:
int minSwapsCouples(vector<int>& row) {
int count = 0;
while(!row.empty()) {
if(row[0]%2==0 && row[1]==row[0]+1 || row[1]%2==0 && row[0]==row[1]+1) {
pop(row, 0);
} else {
if(row[0]%2 == 0) {
int i;
for(i = 0; i < row.size(); i++)
if(row[i] == row[0]+1)
break;
swap(1, i, row);
pop(row,0);
count++;
} else {
int i;
for(i = 0; i < row.size(); i++)
if(row[i] == row[0]-1)
break;
swap(1, i, row);
pop(row,0);
count++;
}
}
}
return count;
}
void pop(vector<int>& row, int p) {
auto i = row.begin()+p;
row.erase(i);
row.erase(i);
}
void swap(int a, int b, vector<int>& row) {
int t = row[a];
row[a] = row[b];
row[b] = t;
}
};
那么如何用图的思路来做呢?首先将原始的每个数对看做一个结点,如果两个数对通过交换能够使得其中的一个或者两个数对满足条件,结点之间连一条边,最终形成一个无向图。swap的次数即为边的个数减去环的个数。(环中的边数比处理环中的数对需要的次数多一)。
Two strings X
and Y
are similar if we can swap two letters (in different positions) of X
, so that it equals Y
.
For example, "tars"
and "rats"
are similar (swapping at positions 0
and 2
), and "rats"
and "arts"
are similar, but "star"
is not similar to "tars"
, "rats"
, or "arts"
.
Together, these form two connected groups by similarity: {"tars", "rats", "arts"}
and {"star"}
. Notice that "tars"
and "arts"
are in the same group even though they are not similar. Formally, each group is such that a word is in the group if and only if it is similar to at least one other word in the group.
思路:每个字符串作为一个结点,如果两个字符串是“相似”的,那么两个结点相连。题目也就是让我们求其中连通图的数量。常规的方法就是构建邻接表,DFS出所有连通图(效率比较低,beat 15),代码如下:
class Solution {
public:
int numSimilarGroups(vector<string>& A) {
map<int, vector<int>> adj;
int size = A.size();
set<int> s;
for(int i = 0; i < size-1; i++) {
s.insert(i);
for(int j = i+1; j < size; j++) {
if(connect(A[i], A[j])) {
adj[i].push_back(j);
adj[j].push_back(i);
}
}
}
s.insert(size-1);
int times = 0;
while(!s.empty()) {
auto t = s.begin();
DFS(*t, s, adj);
times++;
}
return times;
}
bool connect(string& a, string& b) {
int count = 0;
for(int i = 0; i < a.length(); i++)
if(a[i] != b[i])
count++;
return count==2||count==0;
}
void DFS(int node, set<int>& s, map<int, vector<int>>& adj) {
if(s.find(node) == s.end())
return;
s.erase(node);
for(auto&i:adj[node]) {
DFS(i, s, adj);
}
}
};
另外,这一题可以使用并查集(disjoint-set或union-find)来解决问题。
并查集:
一种数据结构,将一个集合分为不相交的子集,在添加新的子集,合并现有子集,判断元素所在集合时时间复杂度接近常数级。常用于求连通子图和最小生成树的Kruskal算法。
操作:
makeSet: 初始化,给每个元素分配一个特定的id,以及一个指向自己的指针,表示每个元素都在一个大小为1的集合当中。
find: 查找某个元素的根元素。当两个元素拥有同样的根元素时,说明他们在同一个集合当中。为了使得时间复杂度接近常数级,在查找的过程中,可以更新指针指向根元素(代码中使用递归方法),有人称其为路径压缩。
Union: 满足条件则合并两个子集。如果没有特殊要求,将一个集合的根指向另一个集合的根即可。也可根据秩或者集合的大小来指定。
所以对于这一题,我们建立一个并查集,子集的数量就是连通子图的数量,也就是需要的group的数量。效率得到提高(beat 75)
代码如下:
class Solution {
public:
int numSimilarGroups(vector<string>& A) {
int size = A.size();
vector<int> next(size);
makeSet(next);
int s = size;
for(int i = 0; i < s-1; i++) {
for(int j = i+1; j < s; j++) {
if(connect(A[i], A[j])) {
unionSet(i, j, next, size);
}
}
}
return size;
}
bool connect(string& a, string& b) {
int count = 0;
for(int i = 0; i < a.length(); i++)
if(a[i] != b[i])
count++;
return count==2||count==0;
}
void makeSet(vector<int>& next) {
int j = 0;
for(auto& i:next)
i = j++;
}
void unionSet(int i, int j, vector<int>& next, int& size) {
int root_i = find(i, next), root_j = find(j, next);
if(root_i != root_j) {
next[root_i] = root_j;
size--;
}
}
int find(int i, vector<int>& next) {
if(i != next[i])
next[i] = find(next[i], next);
return next[i];
}
};
Strings A
and B
are K
-similar (for some non-negative integer K
) if we can swap the positions of two letters in A
exactly K
times so that the resulting string equals B
.
Given two anagrams A
and B
, return the smallest K
for which A
and B
are K
-similar.
思路:尝试使用贪心算法。策略为:替换只在不符合的字母之间进行,如果一次替换使得两个不符合的数同时符合,这个替换优先执行。然而,行不通,只通过了一半的测例。如"aabbccddee" "cdacbeebad"就无法通过。显然,某次替换会对之后的替换产生潜在影响。那……就用DFS把所有情况找出来,找出最小K的方案吧(暴力求解)。代码如下:
class Solution {
private:
int size;
int new_size;
int result;
public:
int kSimilarity(string A, string B) {
this->size = A.length();
string a, b;
for(int i = 0; i < this->size; i++) {
if(A[i] != B[i]) {
a.push_back(A[i]);
b.push_back(B[i]);
}
}
this->new_size = a.length();
if(this->new_size == 0)
return 0;
this->result = INT_MAX;
DFS(a, b, 0, 0);
return this->result;
}
void DFS(string a, string b, int p, int k) {
if(p == this->new_size-1) {
this->result = min(this->result, k);
return;
}
if(a[p] == b[p]) {
DFS(a, b, p+1, k);
return;
}
for(int j = p+1; j < this->new_size; j++) {
if(a[p] == b[j]) {
swap(b[p], b[j]);
DFS(a, b, p+1, k+1);
swap(b[p], b[j]);
}
}
}
void swap(char& a, char& b) {
char t = a;
a = b;
b = t;
}
};
另外,还可以通过存储已经计算过的经过某些交换后的串的K值来避免重复计算,进而提高效率。