801.使序列递增的最小交换次数 | 31.下一个排列

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

我们有两个长度相等且不为空的整型数组 nums1 和 nums2 。在一次操作中,我们可以交换 nums1[i] 和 nums2[i]的元素。

  • 例如,如果 nums1 = [1,2,3,8] , nums2 =[5,6,7,4] ,你可以交换 i = 3 处的元素,得到 nums1 =[1,2,3,4] 和 nums2 =[5,6,7,8] 。

返回 使 nums1 和 nums2 严格递增 所需操作的最小次数 。

数组 arr 严格递增 且  arr[0] < arr[1] < arr[2] < ... < arr[arr.length - 1] 。

注意:

  • 用例保证可以实现操作。

示例 1:

输入: nums1 = [1,3,5,4], nums2 = [1,2,3,7]
输出: 1
解释: 
交换 A[3] 和 B[3] 后,两个数组如下:
A = [1, 3, 5, 7] , B = [1, 2, 3, 4]
两个数组均为严格递增的。

示例 2:

输入: nums1 = [0,3,5,8,9], nums2 = [2,1,4,6,9]
输出: 1

 思路:动态规划

每一次我们都只需要考虑当前位置是否可以和他的前面一个位置构成严格递增,针对每一个位置都如此考虑,最终我们会得到一整个数组都是严格递增 

     /*
     * 解题思路:
     * 题目说到,我们只可以交换这两个数组在同一索引位置的两个数,
     * 最终使得两个数组都呈现递增,假设输入总是有效的。
     * 这句话的意思是说,题目的输入总是至少存在一种方法可以使两个数组变成递增数组,所以不存在如下的数组:
     *  A :  4 3
     *  B :  1 2
     *
     *  上述的输入无论怎么样尝试,按照题目的方法是没办法将其转化成一个两个递增数组的,所以肯定是不存在的
     *  对于
     *  A: a1,a2,a3,a4...an
     *  B: b1.b2.b3,b4...bn
     *
     *  其实我们可以根据上面的描述知道,对于任意一个位置i,必然有A[i] > A[i-1] || A[i] > B[i-1]
     *  因为A[i] > A[i-1]时,递增,无需交换;A[i] > B[i-1]时,交换A[i-1]和B[i-1],也递增。
     *  同理,对于B也成立,即B[i] > B[i-1] || B[i] > A[i-1]。
     *
     *  组合A,B的情况,共有四种,合乎题意的只有两种 
     *  1. A[i] > A[i-1] && B[i] > B[i-1] 
     *  2. B[i] > A[i-1] && A[i] > B[i-1]
     *  对于A[i] > A[i-1] && B[i] > A[i-1] 和 A[i] > B[i-1] && B[i] > B[i-1]  
     *  这两个都被 1 和 2 包含了,以第一个为例:
     *  对于B[i-1]的情况我们无从知晓,而题目又说输入一定是有效的
     *  那无外乎A[i]>A[i-1]>B[i-1] 或者 A[i] > B[i-1] > A[i-1] 而这又退化成了我们列举的两种情况
     *  综上只需要讨论两种情况即可。
     *  对于某个索引i,如果满足A[i] > A[i-1] && B[i] > B[i-1] 就认为在i位置时,是满足递增的要求的。
     *  对于某个索引i,如果满足A[i] > B[i-1] && B[i] > A[i-1] 就认为需要进行交换才可以满足递增的要求。
     *
     *  在考虑时,每一次我们都只需要考虑当前位置是否可以和他的前面一个位置构成严格递增
     *  针对每一个位置都如此考虑,最终我们会得到一整个数组都是严格递增。
     *
     *  在考虑每一个位置时,需要判断在当前位置交换的次数和不交换的次数。
     *
     *  当 A[i] > A[i-1] && B[i] > B[i-1] 时
     *  dp[i][0]=dp[i-1][0]; //上一位不交换,那么这一位也不能交换
     *  dp[i][1]=dp[i-1][1]+1; //上一位交换了,那么这一位必须交换
     *
     *  当 A[i] > B[i-1] && B[i] > A[i-1] 时
     *  因为存在既满足上一要求,也满足这一要求的情况,所以要判断哪种的交换次数少,要最小的那个
     *  dp[i][0]=min(dp[i][0],dp[i-1][1]); //这一位不变,那么上一位必须变
     *  dp[i][1]=min(dp[i][1],dp[i-1][0]+1); //这一位变了,那么上一位就得是不变
     *  因为对于每一个位置都有两种方法使其呈现递增,那么最终的结果就是两者中的最小值。
     *  即min(dp[n-1][0] , dp[n-1][1])
     */

 dp [i][0] 表示不交换下标为 i 的两数,让两数组[0......i]范围内的数,严格递增所需操作的最小次数
 dp [i][1] 表示交换下标为 i 的两数,让两数组[0......i]范围内的数,严格递增所需操作的最小次数。

class Solution {
public:
    int minSwap(vector<int>& nums1, vector<int>& nums2) {
        int n=nums1.size();
        int dp[n][2];
        memset(dp,0x3f,sizeof(dp));
        dp[0][0]=0;//base case
        dp[0][1]=1;//base case
        for(int i=1;i<n;i++)
        {
            if(nums1[i]>nums1[i-1]&&nums2[i]>nums2[i-1])//case 1
            {
                dp[i][0]=dp[i-1][0];
                dp[i][1]=dp[i-1][1]+1;
            }
            if(nums1[i]>nums2[i-1]&&nums2[i]>nums1[i-1])//case 2
            {
                dp[i][0]=min(dp[i][0],dp[i-1][1]);
                dp[i][1]=min(dp[i][1],dp[i-1][0]+1);
            }
        }
        return min(dp[n-1][0],dp[n-1][1]);
    }
};

 

31. 下一个排列

整数数组的一个 排列  就是将其所有成员以序列或线性顺序排列。

  • 例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3][1,3,2][3,1,2][2,3,1] 。

整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

  • 例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
  • 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
  • 而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。

给你一个整数数组 nums ,找出 nums 的下一个排列。

必须 原地 修改,只允许使用额外常数空间。

示例 1:

输入:nums = [1,2,3]
输出:[1,3,2]

示例 2:

输入:nums = [3,2,1]
输出:[1,2,3]

示例 3:

输入:nums = [1,1,5]
输出:[1,5,1]

 思路:
    下一个排列的实质其实就是找到所有排列中恰好比nums大的那个;
    如何得到比nums大的排列呢?将nums中靠后的较大的数和nums中靠前的较小的数交换即可得到;而如何选择两个数进行交换就是重点,以2,6,3,5,4,1为例:
        1. 从后往前遍历,找到第一对nums[i]>nums[i-1]的相邻两数,要交换的靠前的较小的数就是nums[i-1]即3;这其实是因为,从后往前遍历,每一次都判断 “当前数 <= 它的前一个数”,最后就会得到一个单调递减的序列,即5,4,1;面对这样一个单调递减的序列,已经是最大的了,就无需交换,所以靠前的要交换的数就是3;
       2.然后寻找靠后的较大的数,方法是从后往前遍历,找到比nums[i-1]大的第一个数,即4。这是因为要找到恰好比之前的nums大的排列,所以往前交换的那个数要尽可能的小一些。
       3.交换这两个数,得到2,6,4,5,3,1,再把nums[i-1]后面的序列5,3,1反转,从降序变成升序,让这个序列更小,得到2,6,4,1,3,5,这就是下一个排列。

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int n=nums.size();
        int index1=-1;
        for(int i=n-1;i>=1;i--)//要交换的靠前的较小的数
        {
            if(nums[i]>nums[i-1])
            {
                index1=i-1;//该数的下标
                break;
            }
        }
        if(index1==-1)//如果下标index没变,还是-1,说明nums中的排列递减,已经是最大的排列了
        {
            reverse(nums.begin(),nums.end());//反转,得到最小的排列
            return ;
        }
        int index2=0;
        for(int i=n-1;i>index1;i--)//要交换的靠后的较大的数
        {
            if(nums[i]>nums[index1])
            {
                index2=i;//该数的下标
                break;
            }
        }
        swap(nums[index1],nums[index2]);//交换
        reverse(nums.begin()+index1+1,nums.end());//将index1后面的递减序列反转,变成递增序列
        return ;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值