给你一个数组 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] 不是子序列。
示例 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 <= 105 1 <= target[i], arr[i] <= 109 target 不包含任何重复元素。
【解题思路1】动态规划 最长递增子序列
会超时
预处理: 由于 target 的元素互不相同,我们可以用一个哈希表记录 target 的每个元素所处的下标,并将 arr 中的元素映射到下标上,对于不存在于 target 中的元素,由于其必然不会在最长公共子序列中,可将其忽略。 示例 2 来说,将 arr 中的元素转换成该元素在 target 中的下标(去掉不在 target 中的元素 7),可以得到一个新数组arr ′ =[1,0,5,4,2,0,3]
动态规划找最长上升子序列: 若将 target 也做上述转换,这相当于将每个元素变为其下标,得target ′ =[0,1,2,3,4,5] 则求原数组的最长公共子序列等价于求上述转换后的两数组的*300. 最长上升子序列。
class Solution {
public int minOperations(int[] target, int[] arr) {
int n = target.length;
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < n; i++) {
map.put(target[i], i);
}
List<Integer> list = new ArrayList<>();
for (int val : arr) {
if (map.containsKey(val)) {
list.add(map.get(val));
}
}
int m = list.size();
int[] dp = new int[m];
int max = 0;
for (int i = 0; i < m; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (list.get(i) > list.get(j)) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
max = Math.max(dp[i], max);
}
return n - max;
}
}
【解题思路2】贪心 + 二分
预处理部分同上。
求dp 贪心 + 二分: 设当前已求出的最长上升子序列的长度为 len(初始时为 1),从前往后遍历数组 nums,在遍历到 nums[i] 时:
- 如果 nums[I] > dp[len] ,则直接加入到 dp 数组末尾,并更新 len=len+1;
- 否则,在 dp 数组中二分查找,找到第一个比 nums[i] 小的数 dp[k] ,并更新 dp[k+1] = nums[i]。
以输入序列 [0,8,4,12,2] 为例: 第一步插入 d=[0]; 第二步插入 d=[0,8]; 第三步插入 d=[0,4]; 第四步插入 d=[0,4,12]; 第五步插入 d=[0,2,12]。 最终得到最大递增子序列长度为 3。
class Solution {
public int minOperations(int[] target, int[] arr) {
int n = target.length;
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < n; i++) {
map.put(target[i], i);
}
List<Integer> list = new ArrayList<>();
for (int val : arr) {
if (map.containsKey(val)) {
list.add(map.get(val));
}
}
int m = list.size();
if (m == 0) {
return n;
}
int[] dp = new int[m];
int len = 1;
dp[0] = list.get(0);
for (int i = 1; i < m; i++) {
int num = list.get(i);
if (num > dp[len - 1]) {
len++;
dp[len - 1] = num;
} else {
int index = binarySearch(dp, len, num);
dp[index] = num;
}
}
return n - len;
}
public int binarySearch(int[] dp, int len, int target) {
int left = 0, right = len - 1;
while (left < right) {
int mid = (left + right) / 2;
if (dp[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
}
用list代替dp数组稍微精简一下步骤
class Solution {
public int minOperations(int[] target, int[] arr) {
int n = target.length;
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < n; i++) {
map.put(target[i], i);
}
List<Integer> dp = new ArrayList<>();
for (int val : arr) {
if (map.containsKey(val)) {
int num = map.get(val);
if (dp.size() == 0 || dp.get(dp.size() - 1) < num) {
dp.add(num);
} else {
int index = binarySearch(dp, num);
dp.set(index, num);
}
}
}
return n - dp.size();
}
public int binarySearch(List<Integer> list, int target) {
int len = list.size();
int left = 0, right = len - 1;
while (left < right) {
int mid = (left + right) / 2;
if (list.get(mid) < target) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
}
|