1793. 好子数组的最大分数
题目
方法一(单调栈)
代码
这段Java代码解决的问题是在一个整数数组nums
中找到一个子数组,这个子数组至少包含nums[k]
,并且这个子数组的最小值乘以子数组的长度可以达到的最大分数。代码使用了单调栈的数据结构来高效地找到每个元素左侧和右侧第一个小于该元素的位置,以此作为可能的子数组边界,然后计算可能的最大分数。
下面是对这段代码的详细解读和添加的注释:
class Solution {
public int maximumScore(int[] nums, int k) {
// left 和 right 数组分别用来存储每个元素左侧和右侧第一个小于当前元素的索引
int[] left = new int[nums.length];
int[] right = new int[nums.length];
LinkedList<Integer> list = new LinkedList<>(); // 使用 LinkedList 作为单调栈
// 正向遍历 nums 数组来填充 left 数组
for (int i = 0; i < nums.length; i++) {
// 当栈不为空且当前元素小于等于栈顶元素时,不断弹出栈顶
while (!list.isEmpty() && nums[list.getLast()] >= nums[i]) {
list.removeLast();
}
// 栈为空则设为-1,否则设为栈顶元素的索引
left[i] = list.isEmpty() ? -1 : list.getLast();
// 当前元素索引入栈
list.add(i);
}
list.clear(); // 清空栈,准备用于右侧查找
// 反向遍历 nums 数组来填充 right 数组
for (int i = nums.length - 1; i >= 0; i--) {
while (!list.isEmpty() && nums[list.getLast()] >= nums[i]) {
list.removeLast();
}
// 栈为空则设为 nums.length,否则设为栈顶元素的索引
right[i] = list.isEmpty() ? nums.length : list.getLast();
// 当前元素索引入栈
list.add(i);
}
// 初始化最大分数为0
int ans = 0;
// 遍历每个元素,寻找最大分数
for (int i = 0; i < nums.length; i++) {
int l = left[i];
int r = right[i];
// 确保当前元素的子数组包含 nums[k]
if (l < k && k < r) {
// 更新最大分数,即最小值乘以子数组长度
ans = Math.max(ans, (nums[i] * (r - l - 1)));
}
}
return ans;
}
}
这段代码首先利用单调栈分别找到数组中每个元素的左侧和右侧第一个小于该元素的索引,这些索引定义了包含当前元素的最大可能子数组的边界。然后,对于每个元素,检查其对应的最大可能子数组是否包含nums[k]
(即k
索引在左右边界之间)。如果满足条件,则计算该子数组的分数(即当前元素值乘以子数组长度),并更新最大分数。最后返回最大分数。
单调栈具体讲解见:LeetCode(84. 柱状图中最大的矩形)
今天的每日一题和这题的区别只在于需要在最后添加一个(l<k<r)
的判断条件。
方法二(双指针+贪心)
class Solution {
public int maximumScore(int[] nums, int k) {
// 初始化左右边界为k,并设置数组长度和最大分数的初始值
int l = k, r = k, n = nums.length, res = 0;
// 使用循环不断扩展左右边界
while (true) {
// 向右扩展边界,直到找到小于nums[k]的元素
while (r < n && nums[r] >= nums[k]) r++;
// 向左扩展边界,直到找到小于nums[k]的元素
while (l >= 0 && nums[l] >= nums[k]) l--;
// 更新最大可能分数
res = Math.max(res, (r - l - 1) * nums[k]);
// 如果左右边界都已经达到数组的端点,退出循环
if (l < 0 && r == n) break;
// 更新nums[k]为左右边界中的较大者,这一步是为了在后续的迭代中尝试获得更大的分数
if (l >= 0 && r < n) {
nums[k] = Math.max(nums[l], nums[r]);
} else if (l < 0) {
// 如果左边界已经遍历完,只能向右边界扩展
nums[k] = nums[r];
} else {
// 如果右边界已经遍历完,只能向左边界扩展
nums[k] = nums[l];
}
}
return res;
}
}
代码总结
这个算法首先固定了一个点nums[k]
作为子数组的最小值,然后通过扩展子数组的左右边界来探索所有可能包含nums[k]
且以nums[k]
为最小值的子数组。算法的目标是找到这样的子数组,使得它的"分数"(最小值乘以长度)最大。
关键步骤如下:
- 边界扩展:通过两个while循环向左右两边扩展,寻找以
nums[k]
为最小值的子数组的最大可能边界。 - 分数更新:在每次边界扩展后,更新最大分数
res
。 - 终止条件:当左右边界都达到数组的端点时,结束循环。
- 动态调整:通过更新
nums[k]
的值,尝试在后续迭代中通过调整边界来获得更大的分数。