We have two integer sequences A
and B
of the same non-zero length.
We are allowed to swap elements A[i]
and B[i]
. Note that both elements are in the same index position in their respective sequences.
At the end of some number of swaps, A
and B
are both strictly increasing. (A sequence is strictly increasing if and only if A[0] < A[1] < A[2] < ... < A[A.length - 1]
.)
Given A and B, return the minimum number of swaps to make both sequences strictly increasing. It is guaranteed that the given input always makes it possible.
Example: Input: A = [1,3,5,4], B = [1,2,3,7] Output: 1 Explanation: Swap A[3] and B[3]. Then the sequences are: A = [1, 3, 5, 7] and B = [1, 2, 3, 4] which are both strictly increasing.
Note:
A, B
are arrays with the same length, and that length will be in the range[1, 1000]
.A[i], B[i]
are integer values in the range[0, 2000]
.
這道題給了我們兩個長度相等的數組A和B,說是可以在任意位置i交換A[i]和B[i]的值,我們的目標是讓數組A和B變成嚴格遞增的數組,讓我們求出最少需要交換的次數。博主最先嘗試了貪婪算法,就是遍歷數組,如果當前數字小於等於前面一個數字,那么就交換一下,但是問題就來了,到底是交換當前位置的數字,還是前一個位置的數字呢,比如下面這個例子:
0 4 4 5
0 1 6 8
我們到底是交換4和1呢,還是4和6呢,雖然兩種方法都能讓前三個數字嚴格遞增,但是如果交換了4和6,那么第一個數組的最后一個5就又得交換了,那么就要多交換一次,所以這個例子是交換4和1,但是對於下面這個例子:
0 4 4 7
0 1 6 5
那么此時我們就要交換4和6了,這樣只要交換一次就能使兩個數組都變成嚴格遞增的數組了。所以這道題用貪婪算法不work,我們必須使用別的方法,那么此時動態規划Dynamic Programming就閃亮登場了。
像這種求極值的題目,不是Greedy就是DP啊,一般難題偏DP的比較多。而DP解法的第一步就是要確定dp數組該怎么定義,如果我們定義一個一維數組dp,其中dp[i]表示使得范圍[0, i]的子數組同時嚴格遞增的最小交換次數,這樣的話狀態轉移方程就會十分的難寫,因為我們沒有解耦合其內在的關聯。當前位置i是否交換,只取決於和前面一位是否是嚴格遞增的,而前一位也有交換或者不交換兩種狀態,那么前一位的不同狀態也會影響到當前是否交換,這跟之前那道Best Time to Buy and Sell Stock with Transaction Fee就十分到類似了,那道題的股票也有保留或者賣出兩種狀態不停的切換。那么這里我們也需要維護兩種狀態,swap和noSwap,那么swap[i]表示范圍[0, i]的子數組同時嚴格遞增且當前位置i需要交換的最小交換次數,noSwap[i]表示范圍[0, i]的子數組同時嚴格遞增且當前位置i不交換的最小交換次數,兩個數組里的值都初始化為n。由於需要跟前一個數字比較,所以我們從第二個數字開始遍歷,那么就需要給swap和noSwap數組的第一個數字提前賦值,swap[0]賦值為1,因為其表示i位置需要交換,noSwap[0]賦值為0,因為其表示i位置不需要交換。
好,下面來分析最難的部分,狀態轉移方程。由於這道題限制了一定能通過交換生成兩個同時嚴格遞增的數組,那么兩個數組當前位置和前一個位置之間的關系只有兩種,一種是不用交換,當前位置的數字已經分別大於前一個位置,另一種是需要交換后,當前位置的數字才能分別大於前一個數字。那么我們來分別分析一下,如果當前位置已經分別大於前一位置的數了,那么講道理我們是不需要再進行交換的,但是swap[i]限定了我們必須要交換當前位置i,那么既然當前位置要交換,那么前一個位置i-1也要交換,同時交換才能繼續保證同時遞增,這樣我們的swap[i]就可以賦值為swap[i-1] + 1了。而noSwap[i]直接賦值為noSwap[i-1]即可,因為不需要交換了。第二種情況是需要交換當前位置,才能保證遞增。那么swap[i]正好也是要交換當前位置,而前一個位置不能交換,那么即可以用noSwap[i-1] + 1來更新swap[i],而noSwap[i]是不能交換當前位置,那么我們可以通過交換前一個位置來同樣實現遞增,即可以用swap[i-1]來更新noSwap[i],當循環結束后,我們在swap[n-1]和noSwap[n-1]中返回較小值即可,參見代碼如下:
解法一:
class Solution { public: int minSwap(vector<int>& A, vector<int>& B) { int n = A.size(); vector<int> swap(n, n), noSwap(n, n); swap[0] = 1; noSwap[0] = 0; for (int i = 1; i < n; ++i) { if (A[i] > A[i - 1] && B[i] > B[i - 1]) { swap[i] = swap[i - 1] + 1; noSwap[i] = noSwap[i - 1]; } if (A[i] > B[i - 1] && B[i] > A[i - 1]) { swap[i] = min(swap[i], noSwap[i - 1] + 1); noSwap[i] = min(noSwap[i], swap[i - 1]); } } return min(swap[n - 1], noSwap[n - 1]); } };
我們可以優化上面解法的空間復雜度,由於當前位置的值只跟前一個位置相關,所以我們並不需要保存整個數組,用四個變量來分別表示當前位置和前一個位置的各兩種狀態,可以實現同樣的效果,參見代碼如下:
解法二:
class Solution { public: int minSwap(vector<int>& A, vector<int>& B) { int n1 = 0, s1 = 1, n = A.size(); for (int i = 1; i < n; ++i) { int n2 = INT_MAX, s2 = INT_MAX; if (A[i - 1] < A[i] && B[i - 1] < B[i]) { n2 = min(n2, n1); s2 = min(s2, s1 + 1); } if (A[i - 1] < B[i] && B[i - 1] < A[i]) { n2 = min(n2, s1); s2 = min(s2, n1 + 1); } n1 = n2; s1 = s2; } return min(n1, s1); } };
類似題目:
Best Time to Buy and Sell Stock with Transaction Fee
參考資料:
https://leetcode.com/problems/minimum-swaps-to-make-sequences-increasing/solution/