leetcode
leetcode 1851 ~ 1900
使数组 K 递增的最少操作次数

使数组 K 递增的最少操作次数

难度:

标签:

题目描述

代码结果

运行时间: 162 ms, 内存: 27.3 MB


/*
 * 思路:
 * 1. 使用 Java Stream API 实现相同的逻辑。
 * 2. 我们可以使用 stream 进行集合操作,
 *    然后使用 Collectors 和其他终止操作来实现功能。
 */

import java.util.*;
import java.util.stream.*;

public int minOperationsStream(int[] arr, int k) {
    int n = arr.length;
    return IntStream.range(0, k).map(i -> {
        List<Integer> seq = IntStream.range(i, n).filter(j -> j % k == 0)
                .mapToObj(j -> arr[j]).collect(Collectors.toList());
        return seq.size() - lengthOfLIS(seq);
    }).sum();
}

private int lengthOfLIS(List<Integer> seq) {
    List<Integer> lis = new ArrayList<>();
    for (int num : seq) {
        int idx = Collections.binarySearch(lis, num);
        if (idx < 0) idx = -(idx + 1);
        if (idx < lis.size()) lis.set(idx, num);
        else lis.add(num);
    }
    return lis.size();
}

解释

方法:

本题解使用了最长递增子序列(LIS)的思想来解决问题。首先,对于每个下标 i(i < k),我们考虑以 i 为起点,间隔为 k 的子序列,这样可以得到 k 个子序列。对每个子序列,我们使用二分查找法求出其最长递增子序列的长度。由于我们需要将子序列变为递增,所以需要进行的操作次数等于子序列的长度减去其最长递增子序列的长度。最后,将所有子序列需要的操作次数相加,即为最终答案。

时间复杂度:

O(n * log(n/k))

空间复杂度:

O(n)

代码细节讲解

🦆
为什么在处理这个问题时选择将原数组分解为k个子序列,而不是整个数组进行一次操作?
这个策略是基于题目的要求,即确保每个下标i(i < k)开始的、间隔为k的子序列需要单独递增。如果直接对整个数组操作,会忽略这种间隔的特殊要求,可能导致某些间隔的子序列不满足递增的条件。分解为k个子序列后,可以确保每个间隔k的子序列独立处理,从而符合题目的要求。
🦆
在使用二分查找法来确定最长递增子序列长度时,为什么替换时选择更新t数组中的元素而不是只进行添加操作?
在求解最长递增子序列(LIS)时,使用二分查找来更新t数组中的元素可以帮助我们保持t数组尽可能的小,这是因为更新操作可以替换掉较大的数,使t数组的末尾元素更小,这有助于后续元素的添加。此外,这种方法可以确保t数组始终保持为LIS的一个潜在候选,即使它可能不是唯一的或最终的LIS。
🦆
你是如何保证在每个子序列中应用最长递增子序列方法能够满足整个数组的K递增条件的?
通过确保每个子序列独立满足递增的条件,即每个从下标i (i < k) 开始,间隔为k的子序列都是递增的,从而整个数组自然满足k递增的条件。这是因为k递增的定义本质上要求从任一位置i开始的、间隔为k的子序列必须是递增的,所以单独确保每个这样的子序列是递增的,就能满足整个数组的k递增要求。
🦆
在实际操作中,如果数组的长度n不是k的整数倍,如何处理最后不完全的子序列?
在这种情况下,最后的子序列可能会比其他子序列短,但这并不影响处理方式。我们仍然对这个短的子序列应用同样的最长递增子序列方法来求解需要减少的元素数量。由于这个子序列本身长度就短,其对总操作次数的影响相对较小。因此,无论数组的长度是否为k的倍数,该方法都是有效的。

相关问题