算法题001:求数组可能的最大得分
题目
题目来源:牛客笔试
给定一个从 $0$ 开始的数组 $nums$ 和一个非负整数 $k$。
在一次操作中,你可以做以下操作:
- 选择一个之前未选择过的下标 $i$,范围为 $[0, nums.length - 1]$ 。
- 将 $nums[i]$ 替换为范围 $[nums[i] - k, nums[i] + k]$ 内的任意整数(包含两端)。
在应用任意次数的操作后,返回数组 $nums$ 的最大可能分数。
数组分数是“数组中最多的重复元素个数”
注意,你只能对每个下标应用一次操作。
示例
示例1
输入:
1 | [4,6,1,2] |
输出:
1 | 3 |
说明:
在这个示例中,我们进行了以下操作:
- 选择下标1,将其替换为4(范围为[4, 8]),nums = [4,4,1,2]。
- 选择下标3,将其替换为4(范围为[0, 4]),nums = [4,4,1,4]。
应用这些操作后,数组 nums 的得分为3(由下标 0、1 和 3 组成的子序列)。可以证明,3 是我们可以达到的最大可能长度。
示例2
输入:
1 | [1,1,1,1] |
输出:
1 | 4 |
说明:
在这个示例中,我们无需执行任何操作。
数组nums的得分是4(整个数组)。
思路
重新读题
题目的表述有一股浓重的机翻味,我们不妨重新表述一下:
给定一个数组 $nums$ 和一个自然数 $k$
对于任意下标 $i$,我们可以将该元素 $nums[i]$ 替换为 $[nums[i] - k, nums[i] + k]$ 中的任意整数。一个下标只能操作一次,也可以不操作。
返回任意次操作后,$nums$ 中最大可能的重复元素数量。
观察1:排序
为了得到尽可能多的重复元素,我们需要地将大小接近的元素进行操作,使它们的值相等。
如何找出大小接近的元素呢?—— 排序
观察2:找规律
假设在排序后,我们试图使区间 $[i, j]$内的元素在操作后的值相等。那么此时 $i$ 和 $j$ 需要满足什么条件呢?
- 在排序后,已知 $nums[i] \le nums[i + 1] \le … \le nums[j - 1] \le nums[j]$
- $nums[i]$ 的替换范围是 $[nums[i] - k, nums[i] + k]$ ;$nums[j]$ 的替换范围是 $[nums[j] - k, nums[j] + k]$
- 则 $nums[i] + k \ge nums[j] - k$
- 化简,得到 $0 \le nums[j] - nums[i] \le 2k$
可以看出,当 $i$ 固定时,为了尽可能使区间范围 $j - i + 1$ 变大,我们需要找到一个 $j$ ,使得 $nums[j] - nums[i]$ 尽可能地接近 $2k$。此时的区间范围 $j - i + 1$才有可能是我们想要的答案。
观察3:滑动窗口
经过我们的分析,这道题变成了:
- 对于一个有序数组 $nums$,和一个自然数 $k$
- 当下标 $i$ 固定,我们需要找到一个尽可能大的下标 $j$,并且满足 $0 \le nums[j] - nums[i] \le 2k$
- 在所有满足条件的区间 $[i, j]$ 中,最大的 $j - i + 1$ 即为答案
那么,使用滑动窗口算法能够将暴力遍历的时间复杂度从 $O(n^2)$ 降低到 $O(n)$
代码
Java
1 | import java.util.Arrays; |