问题转化-step1
- 注意这里的递增并不是严格递增,而是单调不减
- 首先显然可以将数组arr进行分组,将其分组为:
- arr[0],arr[k],arr[2k]…
- arr[1],arr[k+1],arr[2k+1]…
- …
- arr[k-1],arr[2k-1],arr[3k-1]…
- 显然组与组之间是相互独立的,即只需要使得每组递增即可
- 原问题变为求解:对于每组,求得使其递增的最小操作次数。然后再将所有组别的结果累加即可
- 最后,我们的问题变为:给定一个数组,可以将每个值任意改变(记为一次操作),求使得其递增的最小操作次数。
问题转化-step2
现在的问题是:给定一个数组,可以将每个值任意改变(记为一次操作),求使得其递增的最小操作次数。
- 核心思路:关心那些没有被修改元素的个数,而不是修改的元素。要使得没有被修改的元素的个数尽量多
- 最长递增子序列!
命题: 使得该数组递增的最小操作次数恰好等于数组长度减去该数组的最长递增(单调不减)子序列长度。注意这里说的是子序列(元素任意选取)而不是字串(元素必须连续选取)
[证明]:(有点类似于剪切-粘贴技术)
- 设数组长度为n。最优解操作次数为a
- 一方面,将数组中每个不在这个最长递增子序列中的元素修改为在这个最长递增子序列中距离其最近的相邻元素(如果有两个则任意选取一个即可),则最后可以使得整个数组满足递增,操作的次数为b次。所以修改b次是一个可行解使得数组递增,最长递增子序列的长度为n - a,且由于这是一个可行解,因此 a ≤ b a \leq b a≤b
- 另一方面,假设存在操作次数 a a a,其中 a < b a < b a<b的解。即操作次数a次即可以使得数组递增,则有 n − a n - a n−a个数是未被修改(操作)的。这n-a个数构成了长度为n-a的递增子序列,这与最长递增子序列长度为n - b矛盾。从而 a ≥ b a \geq b a≥b
- 从而a = b,即证。
最长递增子序列-问题求解
首先考虑单调递增序列的求解,而不是单调不减序列的求解:
方法1-DP时间复杂度( O ( n 2 ) O(n^2) O(n2))
-
记DP[i]为以nums[i]结尾的最长递增子序列长度。则由状态转移方程
D P [ i ] = m a x j < i a n d n u m s [ j ] < n u m s [ i ] { D P [ j ] + 1 } DP[i] = max_{j<i \ and \ nums[j] < nums[i]} \{DP[j] + 1\} DP[i]=maxj<i and nums[j]<nums[i]{DP[j]+1} -
由于在数组的每个点都要遍历前面所有的点,时间复杂度为 O ( n 2 ) O(n^2) O(n2)
方法2-二分优化( O ( n l o g n ) O(nlogn) O(nlogn))
- BASIC IDEA:如果要使得上升的序列尽可能长,则需要使得序列上升尽可能地慢
- 定义
d
[
i
]
d[i]
d[i],为当前数组遍历到的所有元素中,长度为i的上升子序列的末尾元素的最小值
- 由于可能存在多个同样长度的上升子序列,我们取这些个上升子序列最后一个元素的最小值
- 当然在遍历数组的过程中,有可能并没有找到很长的上升子序列。故使用变量len来指示当前数组d[i]中的元素个数(这里的元素个数是除去d[0]的元素个数)
- 我们在遍历数组的过程中不断维护 d [ i ] d[i] d[i]的定义成立即可。len初始值为0,当数组d被加入新的元素时,我们将len加1;
- 算法退出后,len的值显然就是我们要找的最长上升子序列的大小。
命题: 根据定义,数组d是单调递增的。(不会用到d[0])
证明: 设
i
<
j
i < j
i<j我们证明
d
[
i
]
<
d
[
j
]
d[i] < d[j]
d[i]<d[j].假设
d
[
i
]
≥
d
[
j
]
d[i] \geq d[j]
d[i]≥d[j],则
d
[
j
]
d[j]
d[j]对应的上升子序列的第i个元素(d[j]对应的上升子序列的前i个元素也是一个长度为i的上升子序列)一定要比d[i]本身小,这与d[i]的定义相矛盾。
- 如何在遍历数组的过程中维护d数组的定义?
- 设当前遍历到的数组元素为nums[k]:
- 如果nums[k] > d[len],由于d数组的单调性并由d数组的定义,nums[k]不能成为d数组中任何索引小于等于len的元素。但是,我们找到了一个新的长度为len+1的上升子序列,它由原先d[len]对应的上升子序列加上nums[k]得到。于是我们在d中插入nums[k]
- 如果
n
u
m
s
[
k
]
<
d
[
l
e
n
]
nums[k] < d[len]
nums[k]<d[len],首先在d数组中找到最后一个小于nums[k]的数(可以采用二分查找),记索引为q,如果找不到q=0。 显然nums[k]不能更新d数组中这个数及其之前的结果。但是这个数的下一个数,可能大于nums[k],也可能等于nums[k];记这个下一个数在d中的索引为q + 1;
- 考虑 d [ q + 1 ] > n u m s [ k ] d[q + 1] > nums[k] d[q+1]>nums[k],应该 将d[q + 1]更新为nums[k];否则d[q + 1]就不符合定义了。这是因为可以将d[q]对应的上升序列连同nums[k]组成一个长度为q+1的上升序列,其末尾元素比d[q+1]小。当相等时,即使不更新,也可以认为是做了更新。
- 同时,d中已有的大于q+1索引的所有数都不应该被更新为 n u m s [ k ] nums[k] nums[k]。
- 上述的更新维护了d数组的定义。
本题的变化
- 考虑将 d [ i ] d[i] d[i]的定义更改为:长度为i的最长单调不减子序列末尾元素的最小值。则这里的 d [ i ] d[i] d[i]应该是单调不减的。如何维护其定义?
- 当 n u m s [ k ] ≥ d [ l e n ] nums[k] \geq d[len] nums[k]≥d[len], d [ l e n + 1 ] ← n u m s [ k ] d[len+1] \leftarrow nums[k] d[len+1]←nums[k]
- e l s e : 找 d [ i ] 中 最 后 一 个 小 于 等 于 n u m s [ k ] 的 索 引 q 。 如 果 找 不 到 令 q = 0 , 并 令 d [ q + 1 ] = n u m s [ k ] else: 找d[i]中最后一个小于等于nums[k]的索引q。如果找不到令q = 0,并令d[q+1] = nums[k] else:找d[i]中最后一个小于等于nums[k]的索引q。如果找不到令q=0,并令d[q+1]=nums[k]
- 可以证明,上述思路维护了最小值。即证明只有 d [ q + 1 ] d[q+1] d[q+1]需要更改, d [ q + 2 ] 到 d [ l e n ] d[q+2] 到 d[len] d[q+2]到d[len]都不需要更改。可以使用反证法证明。假设 d [ l e n ] d[len] d[len]需要被更改,递推得到 d [ l e n − 1 ] d[len-1] d[len−1]的矛盾,以此类推。
class Solution {
public int findlastminequal(List<Integer> d,int len,int num)
{
int low = 0;
int high = len;
while(low < high)
{
int mid = (low + high + 1) / 2;
if(d.get(mid) > num)
{
high = mid - 1;
}
else
{
low = mid;
}
}
return low;
}
public int kIncreasing(int[] arr, int k) {
int a = 0;
int ans;
for(int i = 0;i<=k - 1;i++)
{
List<Integer> d = new ArrayList<>();
d.add(-1);
d.add(arr[i]);
int len = 1;
for(int j = i + k;j<arr.length;j+=k)
{
if(arr[j] >= d.get(len))
{d.add(arr[j]);
len++;
}
else
{
d.set(findlastmin(d,len,arr[j]) + 1,arr[j]);
}
}
a+=len;
}
return arr.length - a;
}
}