题目描述
给你两个整数数组 arr1
和 arr2
,返回使 arr1
严格递增所需要的最小「操作」数(可能为 0)。
每一步「操作」中,你可以分别从 arr1
和 arr2
中各选出一个索引,分别为 i
和 j
,0 <= i < arr1.length
和 0 <= j < arr2.length
,然后进行赋值运算 arr1[i] = arr2[j]
如果无法让 arr1
严格递增,请返回 -1。
不会解,看题解之前没有可靠的思路
学习总结
思路总结
- 提炼关键信息
- 使数组
arr1
严格递增,可以得到- 目的是替换之后使数组
arr1
严格递增,所以不能有重复元素 - 选择数组
arr2
中的元素进行替换,所以选择过程中不需要选择重复的元素进行替换 - 总结:可以对数组
arr2
进行排序去重,方便操作
- 目的是替换之后使数组
- 以小窥大进行推导
n = arr1.length
对于第[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EjuDr1zj-1682003137788)(null#card=math&code=i&id=urrlT)]个元素,有两种选择- 替换
- 不替换
- 如何去思考?
- 为了使数组严格递增那么,必须保证
arr1[i] > arr1[i-1]
if arr1[i] > arr1[i-1]
那么可以保留当前元素- 例外地:
if arr1[i] > arr1[i-1] + 1
- 当前元素可以被替换成严格大于
arr1[i-1]
的元素 - ex:
arr1 = [1, 5, 3, 6, 7] arr2 = [4, 3]
5替换成3、3替换成4为满足题意的最优结果
- 当前元素可以被替换成严格大于
- 换言之,为了使数组严格递增,对于每一元素都需要进行两步操作
- 是否大于其前一个元素
- 是否能从已排序数组
arr2
(从小到大)中找到一个小于当前元素且大于arr1[i-1]
的元素
- 为了使数组严格递增那么,必须保证
- 需要返回最小操作数
- 直观地,对于每一个元素都进行替换的话,那么操作数是
n = arr1.length
- 但是不一定有足够多的元素来提供使用
- 最多能够替换几次?
- 假设
n = 10 m = 20
那么最多能够替换 10 次,因为有10个元素等待被替换 - 假设
n = 20 m = 11
那么最多能够替换 11 次,因为有 11 次元素可以用来被替换 - 所以 最多的替换次数 是
j = Math.min(n, m)
- 假设
- 直观地,对于每一个元素都进行替换的话,那么操作数是
- 使数组
- 如何求解最终结果
- 通过学习题解了解到使用动态规划的方法
- 自己为什么没想起来?
- 还是太菜了,多学,多总结,多练
动态规划
思考角度——三个要素:状态、状态转移方程、边界确定
- 状态
- 从第一个元素开始,其是否选择交换是一个状态,每一个元素都会面临相同的情况
- 参数确定:定义
dp[i][j]
表示第i
个元素,经过j
次替换,得到当前元素值- 最终结果:
dp[n][j] j <= Math.(m, n) 输出替换次数:j
- 最终结果:
- 什么时候选择不替换
arr1[i] > dp[i-1][j]
当前元素大于第i-1
个元素经过j
次替换之后的结果- 有:
dp[i][j] = dp[i-1][j] if arr1[i] > dp[i-1][j]
- 有:
- 替换
- 从数组
arr2
中找到元素arr2[k]
使arr2[k] > dp[i-1][j-1]
dp[i-1][j-1]
表示第i-1
个元素经过j-1
次替换之后的结果- 有:
dp[i][j] = arr2[k]
- 从数组
- 状态转移
, & \text{if
a
r
r
1
[
i
]
>
d
p
[
i
−
1
]
[
j
]
arr_1[i]>dp[i-1][j]
arr1[i]>dp[i−1][j]}\
dp[i][j] = min(dp[i][j],&arr_2[k]), & \text{if arr_2[k]>dp[i-1][j-1]}
\end{cases}&id=X5oVX)
- 边界确定
- 为了方便计算
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cALoixEN-1682003137881)(null#card=math&code=dp[i][j]&id=hcEOV)]初始值都设置为
Integer.MAX_VALUE
- 初始令
dp[0][0] = -1
表示最小值
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cALoixEN-1682003137881)(null#card=math&code=dp[i][j]&id=hcEOV)]初始值都设置为
- 为了方便计算
代码
public int authorityWayOne(int[] arr1, int[] arr2) {
// 对 数组2 进行排序
Arrays.sort(arr2);
// 去重
List<Integer> list = new ArrayList<>();
int prev = -1;
for(int num : arr2) {
if(num != prev) {
list.add(num);
prev = num;
}
}
int n = arr1.length;
int m = list.size();
int[][] dp = new int[n+1][Math.min(m, n) + 1];
// 初始化填充数据,方便计算
for(int i = 0;i <= n;i++) {
Arrays.fill(dp[i], Integer.MAX_VALUE);
}
dp[0][0] = -1;
for(int i = 1;i <= n;i++) {
for(int j = 0;j <= Math.min(m, n);j++) {
if(arr1[i-1] > dp[i-1][j]) {
// 这里不要搞混了
dp[i][j] = arr1[i-1];
}
if(j > 0 && dp[i-1][j-1] != Integer.MAX_VALUE) {
// 查找严格大于 dp[i-1][j-1] 的最小元素
int idx = binary_search(list, j-1, dp[i-1][j-1]);
if(idx != m) {
dp[i][j] = Math.min(dp[i][j], list.get(idx));
}
}
if(i == n && dp[i][j] != Integer.MAX_VALUE) {
return j;
}
}
}
return -1;
}
private int binary_search(List<Integer> list, int low, int target) {
int high = list.size();
// 左闭右开区间
while(low < high) {
int mid = low + ((high - low) >> 1);
if(list.get(mid) > target) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
优化一
- 数组原地去重
public int makeArrayIncreasing(int[] arr1, int[] arr2) {
// 右边数组中选取元素,赋值给左边数组元素
// 数组严格递增,所以数组中不能存在相同的元素
// 对于 arr1 中的每一个元素都面临两种选择,换或者不换
// --那么最终可以得到最终结果
// --最小操作数一定存在这个过程中,现在问题是代码如何写
// 首先 arr2 中的元素每一个只能用一次,为什么只能用一次?
// 因为 要保证数组 arr1 严格递增
// 所以可以对数组 arr2 进行排序
Arrays.sort(arr2);
// 那么现在问题是需要从 第一个元素开始判断换不换,
// 定义 dp[i][j] 表示前 i 个元素进行 j 次替换之后末尾元素的最大值
int n = arr1.length, m = 0;
// arr2 中的重复元素不需要使用,所以进行去重
for(int i = 1;i < arr2.length;i++) {
if(arr2[m] != arr2[i]) {
arr2[++m] = arr2[i];
}
}
// 拿到不重复数组的长度
m++;
// 拿到之后呢?
// 最大的交换次数是多少?- 交换所有元素
int changeNum = Math.min(m, n);
// 那么接下来呢? 定义dp 一维:元素个数,二维:交换次数
int[][] dp = new int[n+1][changeNum+1];
// 初始化最大值
for(int i = 0;i <= n;i++) {
Arrays.fill(dp[i], Integer.MAX_VALUE);
}
dp[0][0] = -1;
for(int i = 1;i <= n;i++) {
for(int j = 0;j <= Math.min(i, m);j++) {
// 如果当前元素大于前一个元素
if(arr1[i - 1] > dp[i-1][j]) {
dp[i][j] = arr1[i - 1];
}
// 尝试交换.
if(j > 0 && dp[i-1][j-1] != Integer.MAX_VALUE) {
// 查找严格大于 dp[i-1][j-1] 的元素
// 这里涉及到二分查找,如何才能找到 严格大于 dp[i-1][j-1] 的元素
int idx = binary_search(arr2, j-1, dp[i-1][j-1], m);
if(idx != m) {
dp[i][j] = Math.min(dp[i][j], arr2[idx]);
}
}
if(i == n && dp[i][j] != Integer.MAX_VALUE) {
return j;
}
}
}
return -1;
}
private int binary_search(int[] arr, int j, int prev, int m) {
// 左闭右开区间查找
int left = j,right = m;
while(left < right) {
int mid = left + ((right - left) >> 1);
if(arr[mid] > prev) {
right = mid;
} else {
left = mid+1;
}
}
return left;
}
参考
官方题解
https://leetcode.cn/problems/make-array-strictly-increasing/solution/zui-chang-di-zeng-zi-xu-lie-de-bian-xing-jhgg/