1793.好子数组的最大分数(力扣每日一题)

1793.好子数组的最大分数

给你一个整数数组 nums (下标从 0 开始)和一个整数 k

一个子数组 (i, j)分数 定义为 min(nums[i], nums[i+1], ..., nums[j]) * (j - i + 1) 。一个 子数组的两个端点下标需要满足 i <= k <= j

请你返回 子数组的最大可能 分数

示例 1:

输入:nums = [1,4,3,7,4,5], k = 3
输出:15
解释:最优子数组的左右端点下标是 (1, 5) ,分数为 min(4,3,7,4,5) * (5-1+1) = 3 * 5 = 15 。

示例 2:

输入:nums = [5,5,4,5,4,1,1,1], k = 0
输出:20
解释:最优子数组的左右端点下标是 (0, 4) ,分数为 min(5,5,4,5,4) * (4-0+1) = 4 * 5 = 20 。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 2 * 104
  • 0 <= k < nums.length

题解(双指针+贪心):

由题意可知,最大分数的区间内必定包含 nums[k] ,我们考虑从这个数开始扩充区间,即处理包含这个数的所有区间,记录这些区间的最大分数即可。

初始区间的分数为:min(nums[k]) * 1(区间长度)

我们先考虑向右边(或者左边,二者对等)扩充区间:

1、若 nums[k + 1] >= nums[k], 显然 min 的值没有改变,而且区间长度增加了,即此时的区间分数为:nums[k] * 2

2、若 nums[k + 1] < nums[k], 此时若要把这个数添加到区间内,则必定会改变min的值,但是区间长度增加了,此时的区间分数为:nums[k + 1] * 2

同理,区间向左边扩充也是一样的,但如何决定先向左边还是右边扩充呢?

nums[k - 1] >= nums[k] && nums[k + 1] >= nums[k], 正如1中所描述,这种情况直接扩充即可,即向左向右都进行扩充区间操作,直到左边或者右边都出现小于 nums[k] 的数。

此时,nums[k - 1] < nums[k] && nums[k +1] < nums[k] 我们要考虑先向左还是先向右扩充的最优性。

分析可知,显然:

结论1: 在区间长度不变的情况下, min值越大分数越高

结论2:在min值不变的情况下,区间长度越长,分数越高

我们主要考虑 nums[k - 1] != nums[k + 1] 这种情况(相等的时候随便选择一个更新min值都可以),无论我们向哪边扩充区间长度都是+1,对于相同区间长度,min的值越大则分数越大,因此我们选取nums[k - 1]nums[k + 1]中较大的那个值肯定是最优的,因为它最终的分数肯定是大于另一个的。

至此,我们已经考虑了所有最优扩充区间的情况,接下来只需要考虑维护当前区间的分数即可,为了比较好的分辨出当前的区间,可以考虑使用双指针,即i代表区间的头部,j代表区间的尾部。

那么区间长度就能很容易得到,即 j - i + 1

然后我们只需要知道当前区间的 min 值就能解决问题啦。

min 的初始值是 nums[k],根据上面对区间扩充的分析,min 的值发生改变只有当向左或者向右扩充时,新增加的元素值小于 min 时才会发生改变,此时更新min值即可。

举个例子:

输入:nums = [1,4,3,7,4,5], k = 3

nums[k] = 7, 初始区间min值为 7

我们首先扩充区间,但确保 min 值不变,显然这无法做到,根据结论1,那我们记录此时的分数,即 min 值为 7 时的最大分数,score = 7 * 1 = 7

接下来要扩充区间只能改变min值,左边为3, 右边为4 ,根据结论2我们选择 4 肯定是最优的,然后我们考虑 min 值为 4 的区间长度最大是多少,即按照上文的方法扩充区间。

此时得到区间 [7,4,5], 根据结论2min 值为 4 时这种情况分数最大,score = 4 * 3 = 12

同理下一个扩充区间为 [4,3,7,4,5] ,此时 min 值为 3 的这种情况分数最大, score = 3 * 5 = 15

.......

我们依次扩充区间记录每次min值改变的最长区间的分数,保存较大值即可。

最后编写我们的代码,记得考虑边界情况。

//总体时间复杂度O(N)
class Solution {
public:
    int a[100010];//个人比较习惯用数组
    int maximumScore(vector<int>& nums, int k) {
        for(int i = 0; i < nums.size(); i ++) a[i + 1] = nums[i];//存到数组中,下标1开始,避免越界
        k ++;//因为a数组是下标1开始的所以nums[k] = a[k + 1]
        int i = k - 1, j = k + 1, ans = 0, n = nums.size(), p = a[k];//i表示起点,j表示终点,ans存储分数最大值,p记录区间min值
        //左右不改变min值情况下扩充区间,得到min值为a[k]的最长区间长度
        while(a[i] >= a[k] && i > 0)
        {
            i --;
        }
        while(a[j] >= p && j <= n)
        {
            j ++;
        }
        //记录分数
        ans = p * (j - i - 1);
        while(i > 0 || j <= n)//一直扩充直到不能再扩充
        {
            if(i > 0 && j <= n)//判断下标是否到边界值
            {
                //记录二则最优的min值
                if(a[i] > a[j]) p = a[i];
                else p = a[j];
                //无脑扩充长度
                while(i > 0 && a[i] >= p)
                {
                    i --;
                }
                while(a[j] >= p && j <= n)
                {
                    j ++;
                }
                //记录分数
                ans = max(ans, p * (j - i - 1));
            }
            else if(i > 0)//右边不能再扩充,只能扩充左边
            {
                p = a[i];
                while(i > 0 && a[i] >= p)
                {
                    i --;
                }
                //记录分数
                ans = max(ans, p * (j - i - 1));
            }
            else if(j <= n)//左边不能再扩充,只能扩充右边
            {
                p = a[j];
                while(a[j] >= p && j <= n)
                {
                    j ++;
                }
                //记录分数
                ans = max(ans, p * (j - i - 1));
            }
        }
        return ans;
    }
};
posted @ 2024-03-19 14:38  wockcow  阅读(40)  评论(0)    收藏  举报