得到子序列的最少操作次数


得到子序列的最少操作次数

给你一个数组 target ,包含若干 互不相同 的整数,以及另一个整数数组 arr ,arr 可能 包含重复元素。

每一次操作中,你可以在 arr 的任意位置插入任一整数。比方说,如果 arr = [1,4,1,2] ,那么你可以在中间添加 3 得到 [1,4,3,1,2] 。你可以在数组最开始或最后面添加整数。

请你返回 最少 操作次数,使得 target 成为 arr 的一个子序列。

一个数组的 子序列 指的是删除原数组的某些元素(可能一个元素都不删除),同时不改变其余元素的相对顺序得到的数组。比方说,[2,7,4] 是 [4,2,3,7,2,1,4] 的子序列(加粗元素),但 [2,4,2] 不是子序列

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-operations-to-make-a-subsequence

示例一

输入:target = [5,1,3], arr = [9,4,2,3,4]
输出:2
解释:你可以添加 5 和 1 ,使得 arr 变为 [5,9,4,1,2,3,4] ,target 为 arr 的子序列。

示例二

输入:target = [6,4,8,1,3,2], arr = [4,7,6,2,3,8,6,1]
输出:3

数据范围

  • 1 <= target.length, arr.length <= 105
  • 1 <= target[i], arr[i] <= 109
  • target 不包含任何重复元素。

题解

该问题可转换为求最长公共子序列,找到两个数组的最长公共子序列的长度,然后target数组长度 - 公共序列长度即为所求
求解最长公共子序列:

  • 1.我们可以先遍历target数组一次,记录target元素及其下标,
  • 2.遍历arr数组,依次记录数组中出现target元素的次序【即arr当前元素在target数组中的下标】,结果存在index数组中
  • 3.然后问题转为求index数组的最长递增子序列,动态规划即可解决,但本题数据规模较大,须采用贪心+二分查找
  • 4.创建一个d数组,存放最长递增子序列,贪心即为,总是查找index中大于d[len]的最小值,将其纳入d数组中,遇到小于d[len]的值,则通过二分查找,找到第一个不大于index[i]的数,将index[i]放在该数的下一项.

AC代码

class Solution {
public:
    int minOperations(vector<int>& target, vector<int>& arr) {
        int m = target.size(), n = arr.size();
        unordered_map <int, int> map;
        vector <int> index;
        for(int i = 0; i < m; i++)
            map[target[i]] = i+1;//i+1是为了避免下标为0时造成的漏判
        for(int i = 0; i < n; i++)
            if(map[arr[i]])
                index.push_back(map[arr[i]]);//将arr中出现的target元素依次记录下来
        if(index.size() == 0)
            return m;//特殊情况判断,没有公共子序列,直接返回target数组的长度

        // 二分查找 arr 的最长递增子序列
        //如果index[i] > d[len] ,则直接加入到 d 数组末尾,并更新 d[len+1]=index[i]
        //否则,在 d 数组中二分查找,找到第一个比index[i]小的数d[k],并更新d[k + 1]=index[i]。这一步的意思是长度为k+1的公共子序列的末尾元素的最小值更新为index[i]

        n = index.size();
        int len = 1;
        vector<int> d(n + 1, 0);
        d[len] = index[0];
        for (int i = 1; i < n; ++i) 
        {
            if (index[i] > d[len])
                d[++len] = index[i];
            else 
            {
                int l = 1, r = len, pos = 0; // 如果找不到说明所有的数都比 index[i] 大,此时要更新 d[1],所以这里将 pos 设为 0
                while (l <= r) 
                {
                    int mid = (l + r) >> 1;
                    if (d[mid] < index[i]) 
                    {
                        pos = mid;
                        l = mid + 1;
                    } 
                    else
                        r = mid - 1;
                }
                d[pos + 1] = index[i];//在第一个比index[i]小的元素后面插入index[i]
            }
        }
        return m - len;
    }
};

文章作者: 曹毅
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 曹毅 !
  目录