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.html
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.數組
Example 1:app
Input: row = [0, 2, 1, 3] Output: 1 Explanation: We only need to swap the second (row[1]) and third (row[2]) person.
Example 2:ide
Input: row = [3, 2, 0, 1] Output: 0 Explanation: All couples are already seated side by side.
Note:函數
len(row)
is even and in the range of [4, 60]
.row
is guaranteed to be a permutation of 0...len(row)-1
.
這道題給了咱們一個長度爲n的數組,裏面包含的數字是 [0, n-1] 範圍內的數字各一個,讓咱們經過調換任意兩個數字的位置,使得相鄰的奇偶數靠在一塊兒。由於要兩兩成對,因此題目限定了輸入數組必須是偶數個。咱們要明確的是,組成對兒的兩個是從0開始,每兩個一對兒的。好比0和1,2和3,像1和2就不行。並且檢測的時候也是兩個數兩個數的檢測,左右順序無所謂,好比2和3,或者3和2都行。當咱們暫時對如何用代碼來解決問題沒啥頭緒的時候,一個很好的辦法是,先手動解決問題,意思是,假設這道題不要求你寫代碼,就讓你按照要求排好序怎麼作。咱們隨便舉個例子來講吧,好比:post
[3 1 4 0 2 5]url
咱們如何將其從新排序呢?首先明確,咱們交換數字位置的動機是要湊對兒,若是咱們交換的兩個數字沒法組成新對兒,那麼這個交換就毫無心義。來手動交換吧,咱們兩個兩個的來看數字,前兩個數是3和1,咱們知道其不成對兒,數字3的老相好是2,不是1,那麼怎麼辦呢?咱們就把1和2交換位置唄。好,那麼如今3和2牽手成功,度假去了,再來看後面的:spa
[3 2 4 0 1 5]code
咱們再取兩數字,4和0,互不認識!4跟5有一腿兒,不是0,那麼就把0和5,交換一下吧,獲得:
[3 2 4 5 1 0]
好了,再取最後兩個數字,1和0,兩口子,不用動!前面都成對的話,最後兩個數字必定成對。並且這種方法所用的交換次數必定是最少的,不要問博主怎麼證實,博主也不會|||-.-~明眼人應該已經看出來了,這就是一種貪婪算法Greedy Algorithm。思路有了,代碼就很容易寫了,注意這裏在找老伴兒時用了一個trick,一個數‘異或’上1就是其另外一個位,這個不難理解,若是是偶數的話,最後位是0,‘異或’上1等於加了1,變成了能夠的成對奇數。若是是奇數的話,最後位是1,‘異或’上1後變爲了0,變成了能夠的成對偶數。參見代碼以下:
解法一:
class Solution { public: int minSwapsCouples(vector<int>& row) { int res = 0, n = row.size(); for (int i = 0; i < n; i += 2) { if (row[i + 1] == (row[i] ^ 1)) continue; ++res; for (int j = i + 1; j < n; ++j) { if (row[j] == (row[i] ^ 1)) { row[j] = row[i + 1]; row[i + 1] = row[i] ^ 1; break; } } } return res; } };
下面咱們來看一種使用聯合查找Union Find的解法。該解法對於處理羣組問題時很是有效,好比島嶼數量有關的題就常用UF解法。核心思想是用一個root數組,每一個點開始初始化爲不一樣的值,若是兩個點屬於相同的組,就將其中一個點的root值賦值爲另外一個點的位置,這樣只要是相同組裏的兩點,經過find函數會獲得相同的值。 那麼若是總共有n個數字,則共有 n/2 對兒,因此咱們初始化 n/2 個羣組,咱們仍是每次處理兩個數字。每一個數字除以2就是其羣組號,那麼屬於同一組的兩個數的羣組號是相同的,好比2和3,其分別除以2均獲得1,因此其組號均爲1。那麼這對解題有啥做用呢?做用忒大了,因爲咱們每次取的是兩個數,且計算其羣組號,並調用find函數,那麼若是這兩個數的羣組號相同,那麼find函數必然會返回一樣的值,咱們不用作什麼額外動做,由於自己就是一對兒。若是兩個數不是一對兒,那麼其羣組號必然不一樣,在兩者沒有歸爲一組以前,調用find函數返回的值就不一樣,此時咱們將兩者歸爲一組,而且cnt自減1,忘說了,cnt初始化爲總羣組數,即 n/2。那麼最終cnt減小的個數就是交換的步數,仍是用上面講解中的例子來講明吧:
[3 1 4 0 2 5]
最開始的羣組關係是:
羣組0:0,1
羣組1:2,3
羣組2:4,5
取出前兩個數字3和1,其羣組號分別爲1和0,帶入find函數返回不一樣值,則此時將羣組0和羣組1連接起來,變成一個羣組,則此時只有兩個羣組了,cnt自減1,變爲了2。
羣組0 & 1:0,1,2,3
羣組2:4,5
此時取出4和0,其羣組號分別爲2和0,帶入find函數返回不一樣值,則此時將羣組0&1和羣組2連接起來,變成一個超大羣組,cnt自減1,變爲了1。
羣組0 & 1 & 2:0,1,2,3,4,5
此時取出最後兩個數2和5,其羣組號分別爲1和2,由於此時都是一個大組內的了,帶入find函數返回相同的值,不作任何處理。最終交換的步數就是cnt減小值,爲2,參見代碼以下:
解法二:
class Solution { public: int minSwapsCouples(vector<int>& row) { int res = 0, n = row.size(), cnt = n / 2; vector<int> root(n, 0); for (int i = 0; i < n; ++i) root[i] = i; for (int i = 0; i < n; i += 2) { int x = find(root, row[i] / 2); int y = find(root, row[i + 1] / 2); if (x != y) { root[x] = y; --cnt; } } return n / 2 - cnt; } int find(vector<int>& root, int i) { return (i == root[i]) ? i : find(root, root[i]); } };
下面這種使用HashMap的解法,本質其實也是聯合查找Union Find。咱們知道只有羣組裏面是數字,才能使用root數組,有些非數字的狀況,好比字符串,就要使用HashMap了,固然數字也是可使用HashMap的。咱們這裏的helper子函數至關於同時包括了連接羣組和find查找兩部分,在主函數中,咱們仍是兩個兩個處理,而且把羣組號帶入helper函數,在helper函數中,咱們將較小數和較大數區分出來,若是兩者相同,代表是同一個羣組的,不作任何處理,直接返回。不然的話,創建兩者的映射,這就是上面解法中的連接羣組操做,這樣看出來了吧,兩者的本質實際上是同樣的,參見代碼以下:
解法三:
class Solution { public: int minSwapsCouples(vector<int>& row) { unordered_map<int, int> m; for (int i = 0; i < row.size(); i += 2) { helper(m, row[i] / 2, row[i + 1] / 2); } return m.size(); } void helper(unordered_map<int, int>& m, int x, int y) { int c1 = min(x, y), c2 = max(x, y); if (c1 == c2) return; if (m.count(c1)) helper(m, m[c1], c2); else m[c1] = c2; } };
這道題的一個Follow up就是fun4LeetCode大神的帖子中討論的N整數問題 N Integers Problems,簡單來講就是最少使用幾步能夠將全部的數字移回其正確位置,好比數組 [0 3 1 2] 變回 [0 1 2 3] 須要幾步,兩步就夠了,先交換3和2,變成 [0 2 1 3],再交換2和1,變回 [0 1 2 3]。怎麼作呢?實際上在遍歷某一個位置i,若是發現 i != rows[i],咱們就不一樣的經過交換i和rows[i],而後讓 row[i] 等於 row[row[i]],使其最終相等,是否是也有點Union Find的影子在裏面呢?真是頗有趣呢~面白以~
相似題目:
參考資料:
https://leetcode.com/problems/couples-holding-hands/solution/