LeetCode801.  使序列递增的最小交换次数

LeetCode801. 使序列递增的最小交换次数

题目描述

image.png

方法1 动态规划

说一下底层逻辑吧,这道题的状态转移方程还是比较有意思的,对于我来说不太好想。您也可以直接跳过错解的分析,直接到正解部分浏览

原始版本(错误示例)

不知道读者在做这道题的时候会不会跟我一样,我在最初的方案只考虑A[i]>A[i-1] && B[i]>B[i-1]这个条件,满足则不交换i位置元素,否则则交换;这种方案还是比较朴素的,然后考虑的条件其实并不全面,我们一会举个例子解释一下;这里先看一下这个版本的代码

// 75 / 102 个通过测试用例
class Solution {
    public int minSwap(int[] A, int[] B) {
        int[] dp = new int[A.length];
        dp[0] = 0;
        for(int i = 1;i<A.length;i++){
            if(A[i]>A[i-1] && B[i]>B[i-1]){
                dp[i] = dp[i-1];
            }else{
                int t = A[i];
                A[i] = B[i];
                B[i] = t;
                dp[i] = dp[i-1] + 1;
            }
        }

        return dp[A.length - 1];
    }
}

这个版本错误在哪呢,举个例子来说:

A = [0,4,4,5,9]
B = [0,1,6,8,10]

image.png
按这个版本代码的逻辑 在遍历到i=2时 判断到A[i]>A[i-1] //A[2]=4 > A[1]=4不成立,所以进行了一次A[2]B[2]的交换后,A=[0,4,6,5,9],B=[0,1,4,8,10],同样的遍历i=3时由于A[3]=5 < A[2]=6也会进行一次交换,所以总共交换了2次;

但实际上,我们会发现在这个例子中,我们可以直接交换i=1位置达到目的
image.png

所以实际上是因为我们的状态转移条件和方程都考虑的不充分导致这种漏解的现象,其实在这里我们可以感知到一点突破口了,在某些情况下,我们可以选择交换i位置,也可以选择交换i-1位置!

改正后的版本

首先题目保证了所有的输入用例都是有效的,也就是说一定存在某种交换方法可以使得A[i]B[i]最终严格递增,因此对于任意位置i,必定满足以下3种情况:
1、A[i]>A[i-1] && B[i] > B[i-1]
2、A[i]>B[i-1] && B[i] > A[i-1]
3、同时满足条件1和2

条件2可以简单理解成i位置交换后 可以实现{i-1,i}序列的无序到有序
例如A=[3,2],B=[1,4]就是满足条件2的一个例子~
(也就是说我们满足条件1时排除掉条件2,满足条件2时排除条件1,条件3为同时满足)

所以有状态转移方程如下:

  • 满足条件1(局部序列有序):
    • 交换i时必须同时交换i-1
    • 不交换i时也必须同时不交换i-1
  • 满足条件2(局部序列无序,交换1次可变为有序):
    • 只交换i并且不交换i-1
    • 只交换i-1并且不交换i
  • 满足条件3:
    • 交换i位置,i-1可以选择交换或不交换,选择最优情况
    • 不交换i位置,i-1可以选择交换或不交换,选择最优情况

这里给条件1和条件2的例子 供读者自己体会
条件1:A=[1,4,8,5],B=[4,7,6,9]
条件2:A=[1,4,6,5],B=[4,7,8,9]

有了状态转移方程,剩下的看代码就可以了,关键部分已注释

class Solution {
    public int minSwap(int[] A, int[] B) {
        int n = A.length;

        //keep[i]表示不交换i的情况下 使得0~i有序的最小交换次数
        int[] keep = new int[n];
        //swap[i]表示交换i的情况下 使得0~i有序的最小交换次数
        int[] swap = new int[n];

        keep[0] = 0;
        swap[0] = 1;

        for(int i = 1;i<n;i++){
		// 满足条件3
            if((A[i]>A[i-1]&&B[i]>B[i-1])&&(A[i]>B[i-1]&&B[i]>A[i-1])){
                // i不交换,i-1可以交换也可以不交换 择优选择
                keep[i] = Math.min(keep[i-1],swap[i-1]);
                // i 交换,i-1可以交换也可以不交换 择优选择
                swap[i] = Math.min(keep[i-1],swap[i-1]) + 1;
            }else if(A[i] > A[i-1] && B[i] > B[i-1]){
		// 满足条件1的情况下(局部有序)
		// 必须同时交换或同时不交换以保证局部有序
                // i不交换,i-1也不交换
                keep[i] = keep[i-1];
                // i交换,i-1也交换
                swap[i] = swap[i-1] + 1;
            }else{
		// 局部无序 i、i-1中只有一个位置交换
		// i不交换,交换i-1
                keep[i] = swap[i-1];
		// i交换,不交换i-1
                swap[i] = keep[i-1] + 1;
            }
        }
	//择优选择
        return Math.min(keep[n-1],swap[n-1]);
    }
}