LC1713. 得到子序列的最少操作次数(java - 动态规划)

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

题目描述

难度 - 困难
LC1713.得到子序列的最少操作次数
给你一个数组 target ,包含若干 互不相同 的整数,以及另一个整数数组 arr ,arr 可能 包含重复元素。

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

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

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

输入:target = 5,1,3, arr = 9,4,2,3,4

输出:2

解释:你可以添加 5 和 1 ,使得 arr 变为 5,9,4,1,2,3,4 ,target 为 arr 的子序列。
示例 2:

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

输出:3
提示:

1 <= target.length, arr.length <= 10^5

1 <= targeti, arri <= 10^9

target 不包含任何重复元素。

LIS 动态规划 + 二分法

为了方便,我们令 target 长度为 n,arr 长度为 m,target 和 arr 的最长公共子序列长度为 max,不难发现最终答案为 n−max。

因此从题面来说,这是一道最长公共子序列问题(LCS)。

但朴素求解 LCS 问题复杂度为 O(n∗m),使用状态定义「fij 为考虑 a 数组的前 i 个元素和 b 数组的前 j 个元素的最长公共子序列长度为多少」进行求解。

而本题的数据范围为 10^5,使用朴素求解 LCS 的做法必然超时。

一个很显眼的切入点是 target 数组元素各不相同,当 LCS 问题增加某些条件限制之后,会存在一些很有趣的性质。

其中一个经典的性质就是:当其中一个数组元素各不相同时,最长公共子序列问题(LCS)可以转换为最长上升子序列问题(LIS)进行求解。同时最长上升子序列问题(LIS)存在使用「维护单调序列 + 二分」的贪心解法,复杂度为 O(nlog⁡n)。

因此本题可以通过「抽象成 LCS 问题」->「利用 target数组元素各不相同,转换为 LIS 问题」->「使用 LIS 的贪心解法」,做到 O(nlog⁡n) 的复杂度。
朴素的 LIS 问题求解,我们需要定义一个 fi 数组代表以 numsi 为结尾的最长上升子序列的长度为多少。

对于某个 fi 而言,我们需要往回检查 0,i−1 区间内,所有可以将 numsi 接到后面的位置 jjj,在所有的 fj+1中取最大值更新 fi。因此朴素的 LIS 问题复杂度是 O(n^2) 的。

LIS 的贪心解法则是维护一个额外 ggg 数组,glen=x 代表上升子序列长度为 lenlenlen 的上升子序列的「最小结尾元素」为 x。

整理一下,我们总共有两个数组:

  1. f 动规数组:与朴素 LIS 解法的动规数组含义一致。fififi 代表以 numsi 为结尾的上升子序列的最大长度;
  2. g 贪心数组:glen=x代表上升子序列长度为 len 的上升子序列的「最小结尾元素」为 x。

由于我们计算 fi 时,需要找到满足 numsj<numsi,同时取得最大 fj 的位置 j。
我们期望通过 g 数组代替线性遍历。

显然,如果 g 数组具有「单调递增」特性的话,我们可以通过「二分」找到符合 gidx<numsi 分割点 idxi(下标最大),即利用 O(log⁡n) 复杂度找到最佳转移位置。

代码演示

java 复制代码
class Solution {
     public int minOperations(int[] target , int[] arr) {
        Map<Integer, Integer> map = new HashMap();
        int Tlen = target.length, Alen = arr.length;
        //1:target的元素和对应下标 装入map
        for(int i = 0; i < Tlen; i++) 
            map.put(target[i], i);
        
        //2:在arr中寻找相等的值的下标装入下标数组
        int[] index = new int[Alen];
        int p = 0;
        for(int i = 0; i < Alen; i++){
            if(map.containsKey(arr[i])) 
                index[p++] = map.get(arr[i]);
        }
        //3:直接调用处理最长递增公共子串代码(之前做过,这里赋值过来,偷懒)
        int uplen = lengthOfLIS(index,p);
        return Tlen - uplen;
    }
     public int lengthOfLIS(int[] nums,int n){
        if(n == 0) return 0;
        int res = 1;
        int[] dp = new int[n];
        dp[0] = nums[0];
        for(int num:nums){
            int i = 0,j = res;
            while(i < j){
                int mid = (i + j)>>1;
                if(dp[mid] >= num) j = mid;
                else i = mid + 1;
            }
            dp[i] = num;
            if(j == res) res++;
        }
        return res;
    }

}
相关推荐
一杯奶茶¥28 分钟前
基于springboot的失物招领管理系统带万字文档 校园失物招领管理系统 失物认领管理系统java springboot vue
java·vue.js·spring boot·java项目
在放️28 分钟前
Python 爬虫 · 第三方代理接入与合规使用
开发语言·爬虫·python
不能只会打代码31 分钟前
边缘视频分析平台的架构设计与性能优化——从750ms到190ms的调优之路
java·spring boot·redis·性能优化·边缘计算·物联网竞赛
小刘|32 分钟前
Spring AI Alibaba 集成和风天气 API 实战
java·服务器·前端
KANGBboy35 分钟前
java知识五(继承)
java·开发语言
c++之路37 分钟前
Bazel C++ 构建系列文档(三):构建第一个 C++ 项目
开发语言·c++
AI人工智能+电脑小能手39 分钟前
【大白话说Java面试题 第117题】【并发篇】第17题:线程有几种状态,之间如何转换?
java·开发语言·面试
DIY源码阁42 分钟前
JavaSwing饮品管理系统 - MySQL版
java·数据库·mysql·eclipse
开源Z43 分钟前
LeetCode 42 · 接雨水:从暴力到双指针的三步优化
算法·leetcode
旖-旎1 小时前
《LeetCode 695 岛屿的最大面积 FloodFill DFS 解法》
c++·算法·力扣·深度优先遍历·floodfill