看到题,首先判断题目类型,是一道什么题。常见的题型有DFS, BFS, DP, Greedy, Math, Recursion等。分析这道题使用DFS, DP的可能性较大。
DP题目,难点是状态转移方程。如果能够弄清楚要记录的状态以及转移原则,DP就很简单了。
这道题需要记录的状态有两个:
- 交换
A[i]
,B[i]
,使两个从0
到当前索引i
的子序列A[0:i]
,B[0:i]
单调递增的,最小交换次数swap
。 - 不交换
A[i]
,B[i]
,使两个从0
到当前索引i
的子序列A[0:i]
,B[0:i]
单调递增的,最小交换次数keep
。
有了状态,我们考虑状态转移问题。当序列中只有一个数字的时候,是严格单调递增的,这时swap[0] = 1
, keep[0] = 0
,我们从索引为1
的数字开始。如果当前A[i]
, B[i]
使两个序列都是递增的,不需要交换,keep[i] = keep[i-1]
,对于swap
而言,就是swap[i] = swap[i-1] + 1
,因为swap[i]
记录的是交换数字A[i]
,B[i]
使两个子序列严格单调递增的交换次数,所以我们除了交换索引i
上的数字,还要交换前面的数字(极端情况是交换两个序列);如果当前数字使某个序列不再递增了,那么有两种可能的操作:1.交换当前数字保留前一组数字:swap[i] = keep[i-1] + 1
,2.保留当前数字交换前一组数字keep[i] = swap[i-1]
。注意,即使当前数字使两个序列都递增,如果交换后仍能保持递增,那么也是可以交换的,所以两个if
是并列的,并不是if-else
的形式。
完整代码如下
static int x = [] () {ios::sync_with_stdio(false); cin.tie(0); return 0;} ();
class Solution {
public:
int minSwap(vector<int>& A, vector<int>& B) {
const int n = A.size();
vector<int> keep (n, INT_MAX), swap (n, INT_MAX);
keep[0] = 0;
swap[0] = 1;
for (int i=1; i<n; ++i) {
int j = i - 1;
if (A[i] > A[j] && B[i] > B[j]) {
keep[i] = keep[j];
swap[i] = swap[j] + 1;
}
if (B[i] > A[j] && A[i] > B[j]) {
swap[i] = min(swap[i], keep[j] + 1);
keep[i] = min(keep[i], swap[j]);
}
}
return min(swap.back(), keep.back());
}
};
同大多数DP题目一样,我们可以只保留前一位状态来转移,使空间复杂度从 $\mathcal{O}(n)$ 减小到 $\mathcal{O}(1)$,代码如下。
static int x = [] () {ios::sync_with_stdio(false); cin.tie(0); return 0;} ();
class Solution {
public:
int minSwap(vector<int>& A, vector<int>& B) {
int k1 = 0, s1 = 1;
for (int i=1, n=A.size(); i<n; ++i) {
int j = i - 1, k2 = INT_MAX, s2 = INT_MAX;
if (A[i] > A[j] && B[i] > B[j]) {
k2 = k1;
s2 = s1 + 1;
}
if (B[i] > A[j] && A[i] > B[j]) {
s2 = min(s1, k1 + 1);
k2 = min(k2, s1);
}
s1 = s2; k1 = k2;
}
return min(s1, k1);
}
};