You are given a string s
, and an array of pairs of indices in the string pairs
where pairs[i] = [a, b]
indicates 2 indices(0-indexed) of the string.
You can swap the characters at any pair of indices in the given pairs
any number of times.
Return the lexicographically smallest string that s
can be changed to after using the swaps.
Example 1:
Input: s = "dcab", pairs = [[0,3],[1,2]]
Output: "bacd"
Explaination:
Swap s[0] and s[3], s = "bcad"
Swap s[1] and s[2], s = "bacd"
Example 2:
Input: s = "dcab", pairs = [[0,3],[1,2],[0,2]]
Output: "abcd"
Explaination:
Swap s[0] and s[3], s = "bcad"
Swap s[0] and s[2], s = "acbd"
Swap s[1] and s[2], s = "abcd"
Example 3:
Input: s = "cba", pairs = [[0,1],[1,2]]
Output: "abc"
Explaination:
Swap s[0] and s[1], s = "bca"
Swap s[1] and s[2], s = "bac"
Swap s[0] and s[1], s = "abc"
Constraints:
1 <= s.length <= 10^5
0 <= pairs.length <= 10^5
0 <= pairs[i][0], pairs[i][1] < s.length
s
only contains lower case English letters.
這道題給了一個字符串s,又給了一系列的 pair 對兒,里面是可以交換的坐標,這里的同一個交換對兒可以多次使用,當然也可以不使用,現在讓求可以得到的字母順序最小的字符串。博主最先拿到這道題的時候,覺得就是個類似於圖遍歷的題目,每種不同排列的字符串就是個不同的結點,需要找到那個字母順序最小的那個結點。所以博主的做法就是使用 BFS 來做,用個 HashSet 來記錄出現過的排列順序,並且維護一個全局最小的排列順序,就類似迷宮遍歷中的上下左右四個新方向一樣,只不過這里是根據 pair 對兒來交換某兩個字母的位置,形成的新的排列順序,用這個新的排列順序來更新全局最小,並且只有當這個排列之前沒有出現過,才加入隊列 queue 中繼續遍歷。但是這樣搞下來還是超時了 Time Limit Exceeded,看來這道題還得需要更加巧妙的解法才行啊。這里如果把s中的每個字母都看作一個結點的話,那么這里的 pair 對兒就相當於連接結點的邊,若所有的結點都可以通過邊來連通,那么結點值就可以任意調換,相當於直接給字符串排序,比如例子2和3。但若並不是任意兩個結點都是連通時,比如例子1的情況,有兩個獨立的連通部分,則其相對的位置的關系還是得保留,每個連通內部是可以任意排序的。
所以這道題的一個關鍵點是在於對於每個連通部分單獨處理,即最主要一點是要找到所有相連的結點,這可以用 DFS 來找,找到了所有相連結點的坐標值,放到一個數組中,同時也要將其對應的字母也都取出,分別進行排序,則此時最小的字母就可以對應到坐標最小的位置了。下面來看代碼實現,首先是要建立圖的結構,這里用一個二維數組g來以鄰接鏈表的形式存儲這個圖的結構,對於每個 pair 對兒,由於是無向圖,所以正反都要保存。接下來就要遍歷每個字母結點了,為了避免重復遍歷,這里用一個 visited 數組來記錄訪問過的結點,對於沒有訪問過的結點,新建一個數組 pos,用來保存所有和當前結點相連通的結點的位置坐標,然后調用遞歸函數。在遞歸函數中,首先將 visited 數組對應位置位置賦值為 true,同時把當前位置加入 pos 數組,然后就遍歷和當前位置直接相連的結點了,若沒有訪問過,再對其調用遞歸函數即可。遞歸結束后,得到了 pos 數組,需要將對應位置上的字母都按順序取出來放到字符串t中,分別給 pos 和字符串t排序,最后根據排序后的 pos 數組和字符串t來更新字符串s中對應位置的字母即可,參見代碼如下:
解法一:
class Solution {
public:
string smallestStringWithSwaps(string s, vector<vector<int>>& pairs) {
int n = s.size();
vector<vector<int>> g(n);
vector<bool> visited(n);
for (auto &pair : pairs) {
g[pair[0]].push_back(pair[1]);
g[pair[1]].push_back(pair[0]);
}
for (int i = 0; i < n; ++i) {
if (visited[i]) continue;
vector<int> pos;
helper(g, i, pos, visited);
string t;
for (int idx : pos) t += s[idx];
sort(pos.begin(), pos.end());
sort(t.begin(), t.end());
for (int j = 0; j < pos.size(); ++j) {
s[pos[j]] = t[j];
}
}
return s;
}
void helper(vector<vector<int>>& g, int i, vector<int>& pos, vector<bool>& visited) {
visited[i] = true;
pos.push_back(i);
for (int next : g[i]) {
if (visited[next]) continue;
helper(g, next, pos, visited);
}
}
};
既然是結點連通問題,而且還可能分為不同的群組,那么就可以考慮是否可以用聯合查找(又稱並查集) Union Find 來做。LeetCode 上有很多可以使用該方法的題目,比如 Friend Circles 和 Redundant Connection 等等。這是一種給每個結點都初始化一個不同的 root 值,當兩個結點相連時,將二者 root 值賦值為相同,最后對於同一個群組內的所有結點,它們的 root 值最后都是相同的。這里用一個大小為n的 root 數組,初始化均為 -1(也可以初始化為不同的值),然后就是更新 root 數組了,遍歷每個 pair 對兒,通過 find 函數分別找出相連的兩個結點的 root 值,若不相等,則將其賦值為相同。對於 find 函數,首先判斷給定結點i的 root 值是否小於0,因為初始化為 -1,若仍是 -1,直接返回i(這樣就省了將每個結點的 root 值都初始化為i的步驟)。若 root 值大於0了,則對 root[i] 再次調用 find 函數,將返回值更新 root[i],更新的好處時減少了之后的調用遞歸的次數。更新完 root 數組之后,接下來就要將同一個群組的結點歸類了,這里用一個二維數組g表示群組,遍歷每個結點,調用 find 函數查找其群組號(即 root 值),將該結點加入該群組。然后就是遍歷每個群組了,之后的邏輯就跟上面的 DFS 解法中的一樣了,取出對應位置的字母,排序,再按順序賦值回字符串s即可,參見代碼如下:
解法二:
class Solution {
public:
string smallestStringWithSwaps(string s, vector<vector<int>>& pairs) {
int n = s.size();
vector<int> root(n, -1);
vector<vector<int>> g(n);
for (auto &pair : pairs) {
int x = find(root, pair[0]), y = find(root, pair[1]);
if (x != y) {
root[x] = y;
}
}
for (int i = 0; i < n; ++i) {
g[find(root, i)].push_back(i);
}
for (auto &a : g) {
string t;
for (int idx : a) t += s[idx];
sort(t.begin(), t.end());
for (int i = 0; i < a.size(); ++i) {
s[a[i]] = t[i];
}
}
return s;
}
int find(vector<int>& root, int i) {
return root[i] < 0 ? i : root[i] = find(root, root[i]);
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/1202
類似題目:
Minimize Hamming Distance After Swap Operations
參考資料:
https://leetcode.com/problems/smallest-string-with-swaps/