LeetCode.1713.得到子序列的最少操作数
0 前言
刷算法第四天打卡,困难题。通过查阅资料最终解答完的感受是:分析问题,联系所学基础知识与题中细节的匹配去转化问题是解决问题的核心。
一、理论思路
1、HashMap
该题的target数组元素映射的下标顺序是在arr数组中寻找最大公共序列的顺序标准,所以我们用HashMap去存target数组中的元素以及下标作为<key,value>。
Map<Integer, Integer> m = new HashMap<>();
for (int i = 0; i < target.length; i++) {
m.put(target[i], i);
}
2、寻找最大公共序列
1. ArrayList
通过匹配arr中的元素去寻找最大公共数组,所以我们申请一个ArrayList作为最大公共序列的接收。
List<Integer> results = new ArrayList<>();
2. 替代与添加
在匹配arr数组元素的时候,会匹配出很多子序列,而我们只需要最大那个子序列。通过ArrayList元素的添加来获取子序列的大小;通过ArrayList元素的替代来寻找其它子序列。而最终ArrayList被撑到多大,就是最大公共子序列的长度。即size()。
for (int val : arr) {
if (m.containsKey(val)) {
int index = m.get(val);
int positon = binarySearch(index, results);
//这里根据二分查找回来的值来选择是替换还是添加
if (positon == results.size()) {
results.add(index);
} else{
results.set(positon, index);}
}
}
3. return
此时我们得到了撑到最大子序列的size(),可以在arr的任意位置插入。所以返回的插入大小为target.length-size()。
return target.length - results.size();
4. 快速查找有序表–二分法
public static int binarySearch(int index, List<Integer> results) {
//1. 当results没值时或者index>results.get(results.size()-1)时则表示这个元素可以直接加在results后面
if (results.size() == 0 || index > results.get(results.size() - 1))
return results.size();
//2. 当上面的情况不成立时,则表示这个元素虽然在arr数组中,但是它不能加在results序列的后面,因为它不是比前面的元素都大
//2. 此时的results的size认为是当前最大size,需要去得到新的results,此时用小的index去替代results中的元素
//2. 直到把results的size撑到更大,或者根本就撑不大,然后又有更大的元素了。
int low = 0, high = results.size() - 1;
//有序表就可以二分查找,这样很快
while (low < high) {
int mid = (low + high) / 2;//为什么要+1?这里取整时默认取下,而+1操作相当于取上,这与下面判断大小的方向有关,没写好会陷入死循环
if (index > results.get(mid)) {//存在一个问题是,把1插到0和4之间,到底该返回那个位置呐?我们应该往后撑起来,所以应该返回4这个位置
low = mid + 1;
} else
high = mid;
}
return low;
}
5. 算法评估
- 时间复杂度:O(n+mlogm),O(n):HashMap存值处;O(mlogm)遍历加二分查找。
- 空间复杂度:O(n+m),O(n):HashMap存值处;O(m)ArrayList获取最大公共子序列处。
二、完整源代码
public static int minOperations(int[] target, int[] arr) {
//1. 公共序列要按target数组排序,用HashMap存target元素以及对应的下标
Map<Integer, Integer> m = new HashMap<>();
for (int i = 0; i < target.length; i++) {
m.put(target[i], i);
}
//2. 通过这个这个HashMap存的下标序列去匹配arr,然后得出一个最大序列用ArrayList存起来
List<Integer> results = new ArrayList<>();
for (int val : arr) {
if (m.containsKey(val)) {
int index = m.get(val);
int positon = binarySearch(index, results);
//这里根据二分查找回来的值来选择是替换还是添加
if (positon == results.size()) {
results.add(index);
} else{
results.set(positon, index);}
}
}
//3. 返回的插入数值就是target的长度减去这个公共最大序列的size
return target.length - results.size();
}
public static int binarySearch(int index, List<Integer> results) {
//1. 当results没值时或者index>results.get(results.size()-1)时则表示这个元素可以直接加在results后面
if (results.size() == 0 || index > results.get(results.size() - 1))
return results.size();
//2. 当上面的情况不成立时,则表示这个元素虽然在arr数组中,但是它不能加在results序列的后面,因为它不是比前面的元素都大
//2. 此时的results的size认为是当前最大size,需要去得到新的results,此时用小的index去替代results中的元素
//2. 直到把results的size撑到更大,或者根本就撑不大,然后又有更大的元素了。
int low = 0, high = results.size() - 1;
//有序表就可以二分查找,这样很快
while (low < high) {
int mid = (low + high) / 2;//为什么要+1?这里取整时默认取下,而+1操作相当于取上,这与下面判断大小的方向有关,没写好会陷入死循环
if (index > results.get(mid)) {//存在一个问题是,把1插到0和4之间,到底该返回那个位置呐?我们应该往后撑起来,所以应该返回4这个位置
low = mid + 1;
} else
high = mid;
}
return low;
}
1、注意事项
注释把思想写的很清楚,而且二分法还有两处小细节,我也标注出来了。