【798. 得分最高的最小轮调】

798. 得分最高的最小轮调

题目描述

给你一个数组 nums,我们可以将它按一个非负整数 k 进行轮调,这样可以使数组变为 [nums[k], nums[k + 1], ... nums[nums.length - 1], nums[0], nums[1], ..., nums[k-1]] 的形式。此后,任何值小于或等于其索引的项都可以记作一分。

  • 例如,数组为 nums = [2,4,1,3,0],我们按 k = 2 进行轮调后,它将变成 [1,3,0,2,4]。这将记为 3 分,因为 1 > 0 [不计分]、3 > 1 [不计分]、0 <= 2 [计 1 分]、2 <= 3 [计 1 分],4 <= 4 [计 1 分]。

在所有可能的轮调中,返回我们所能得到的最高分数对应的轮调下标 k 。如果有多个答案,返回满足条件的最小的下标 k

最简单的做法是遍历每个可能的 k k k,计算轮调 k k k 个位置之后的数组得分。假设数组的长度是 n n n,则有 n n n 种可能的轮调,对于每种轮调都需要 O ( n ) O(n) O(n) 的时间计算得分,总时间复杂度是 O ( n 2 ) O(n ^ 2) O(n2),对于 n ≤ 1 0 5 n \le 10^5 n105的数据范围会超出时间限制,因此需要优化。

对于数组 n u m s nums nums 中的元素 x x x,当 x x x所在下标大于或等于 x x x 时,元素 x x x 会记 1 1 1 分。因此元素 x x x 1 1 1 分的下标范围是 [ x , n − 1 ] [x,n-1] [x,n1],有 n − x n-x nx个下标,元素 x x x 不计分的下标范围是 [ 0 , x − 1 ] [0, x-1] [0,x1],有 x x x 个下标。

官方题解写的也挺好的,但是我觉得中间有几个地方光看文字有些不清楚,另外也是想完全理解这道题,所以自己参考官方题解写一个题解。

得分分析

假设元素 x x x的初始下标为 i i i,则当轮调下标为 k k k 时,元素 x x x 位于下标 ( i − k + n )  mod  n (i-k+n)\ \text{mod} \ n (ik+n) mod n。如果元素 xxx 记 111 分,则有 ( i − k + n )  mod  n ≥ x (i-k+n)\ \text{mod} \ n \ge x (ik+n) mod nx,等价于 k ≤ ( i − x + n )  mod  n k \le (i-x+n)\ \text{mod} \ n k(ix+n) mod n。下面分两种情况来讨论:

  • i < x i<x i<x

    • k < = i k<=i k<=i,因为 k ≥ 0 k\ge 0 k0,而下标 k k k的元素被轮调到下标 0 0 0处,那么由轮调定义,原来在下标 i i i x x x被轮调到下标 i − k i-k ik处,易得 ( i − k ) ∈ [ 0 , i ] (i-k) \in [0,i] (ik)[0,i],此时元素 x x x的得分是0。所以要求元素 x x x的得分是 1 1 1的轮调 k k k必须满足 k ≥ i + 1 k \ge i + 1 ki+1
    • k ≤ ( i − x + n )  mod  n k \le (i-x+n)\ \text{mod} \ n k(ix+n) mod n 等价于 k ≤ ( i − x + n ) k \le (i-x+n) k(ix+n)

    由上述得,当 i < x i<x i<x时,使得元素 x x x得分为 1 1 1 k k k的取值范围为 i + 1 ≤ k ≤ i − x + n i+1 \le k \le i-x+n i+1kix+n

    在这里插入图片描述

  • i ≥ x i \ge x ix

    i i i的元素 x x x有两种移动方式,都能使得元素 x x x的得分为 1 1 1

    • 当元素 x x x向右移动,此时 x x x能达到的最远位置为 n − 1 n-1 n1,且在 k k k轮调时,这个位置是原始位置 k − 1 k-1 k1处的元素占据,那么容易有 k − 1 = i k-1=i k1=i,那么 k = i + 1 k=i+1 k=i+1,当 k > i + 1 k>i+1 k>i+1时,元素 x x x仍然会在 [ i , n − 2 ] [i,n-2] [i,n2]的某个位置,所以 k ≥ i + 1 k \ge i+1 ki+1都会是使得元素 x x x的得分为 1 1 1的合法位置。
    • 当元素 x x x向左移动,元素 x x x的新位置必须在 [ x , i ] [x,i] [x,i]之间。 k k k轮调时,在位置 0 0 0的元素原来所在位置为 k k k,那么要满足元素 x x x的新位置必须在 [ x , i ] [x,i] [x,i]之间,对于原来的位置来说, i − k ≥ x i-k \ge x ikx,即 k ≤ i − x k \le i-x kix必须被满足。

    i ≥ x i \ge x ix时,使得元素 x x x得分为 1 1 1 k k k的取值范围为 k ≥ i + 1 k \ge i+1 ki+1 k ≤ i − x k \le i-x kix

    在这里插入图片描述

计算得分

对于数组的每个元素,都可以计算得分为 1 1 1的轮调 k k k的范围。在这个范围内的每个轮调下标 k k k加1,这样遍历完所有元素后,找到值最大的最小轮调下标。

创建长度为 n n n 的数组 p o i n t s points points,其中 p o i n t s [ k ] points[k] points[k] 表示轮调下标为 k k k 时的得分。对于数组 n u m s nums nums 中的每个元素,得到该元素记 1 1 1 分的轮调下标范围,然后将数组 p o i n t s points points的该下标范围内的所有元素加 1 1 1。当数组 p o i n t s points points 中的元素值确定后,找到最大元素的最小下标。该做法的时间复杂度仍然是 O ( n 2 ) O(n ^ 2) O(n2),为了降低时间复杂度,需要利用差分数组。

差分数组优化

在使用 p o i n t s points points数组时,当 i < x i<x i<x我们需要把 p o i n t s points points的下标范围 [ i + 1 , i − x + n ] [i+1,i-x+n] [i+1,ix+n]内的所有元素加 1 1 1,当 i ≥ x i\ge x ix时应该将 p o i n t s points points的下标范围 [ 0 , i − x ] [0,i-x] [0,ix] [ i + 1 , n − 1 ] [i+1, n-1] [i+1,n1]内的所有元素都加 1 1 1。由于是将一段或两段连续下标范围内的元素加 1 1 1,因此可以使用差分数组实现。

定义长度为 n n n 的差分数组 d i f f s diffs diffs,其中 d i f f s [ k ] = p o i n t s [ k ] − p o i n t s [ k − 1 ] diffs[k]=points[k]-points[k-1] diffs[k]=points[k]points[k1](特别地, p o i n t s [ − 1 ] = 0 points[-1]=0 points[1]=0),具体做法是:令 l o w = ( i + 1 )  mod  n , h i g h = ( i − x + n + 1 )  mod  n low=(i+1)\ \text{mod}\ n, high=(i-x+n+1)\ \text{mod}\ n low=(i+1) mod n,high=(ix+n+1) mod n,将 d i f f s [ l o w ] diffs[low] diffs[low] 的值加 1 1 1,将 d i f f s [ h i g h ] diffs[high] diffs[high] 的值减 1 1 1,如果 l o w ≥ h i g h low \ge high lowhigh 则将 d i f f s [ 0 ] diffs[0] diffs[0] 的值加 1 1 1

遍历数组 n u m s nums nums 的所有元素并更新差分数组之后,遍历数组 d i f f s diffs diffs并计算前缀和,则每个下标处的前缀和表示当前轮调下标处的得分。在遍历过程中维护最大得分和最大得分的最小轮调下标,遍历结束之后即可得到结果。

首先证明,为什么前缀和就是轮调为 k k k时的得分。因为由 d i f f s diffs diffs的定义, d i f f s [ k ] = p o i n t s [ k ] − p o i n t s [ k − 1 ] diffs[k]=points[k]-points[k-1] diffs[k]=points[k]points[k1],所以下标为 k k k的前缀和 p r e s u m [ k ] presum[k] presum[k]最终可以通过式 ( 1 ) (1) (1)化简,这就证明了前缀和确实是轮调下标为 k k k时的得分。

p r e s u m [ k ] = d i f f s [ k ] + d i f f s [ k − 1 ] + ⋯ + d i f f s [ 1 ] + d i f f s [ 0 ] = p o i n t s [ k ] − p o i n t s [ k − 1 ] + p o i n t s [ k − 1 ] − p o i n t s [ k − 2 ] + ⋯ + p o i n t s [ 1 ] − p o i n t s [ 0 ] + p o i n t s [ 0 ] − p o i n t s [ − 1 ] = p o i n t s [ k ] − p o i n t s [ − 1 ] = p o i n s [ k ] \begin{equation} \begin{split} presum[k] &= diffs[k]+diffs[k-1]+ \dots +diffs[1]+diffs[0]\\ &=points[k]-points[k-1]+points[k-1]-points[k-2]+ \dots +points[1]-points[0]+points[0]-points[-1]\\ &=points[k]-points[-1]\\ &=poins[k] \end{split} \end{equation} presum[k]=diffs[k]+diffs[k1]++diffs[1]+diffs[0]=points[k]points[k1]+points[k1]points[k2]++points[1]points[0]+points[0]points[1]=points[k]points[1]=poins[k]

接下来说明使用差分数组时,给 p o i n t s points points数组中 [ l o w , h i g h − 1 ] [low,high-1] [low,high1]范围加 1 1 1可以通过 d i f f s [ l o w ] = d i f f s [ l o w ] + 1 diffs[low]=diffs[low]+1 diffs[low]=diffs[low]+1 d i f f s [ h i g h ] = d i f f s [ h i g h ] − 1 diffs[high]=diffs[high]-1 diffs[high]=diffs[high]1来完成。此时,当求前缀和在 l o w low low的位置时,我们的和加 1 1 1了,然后当 h i g h high high位置时,和减 1 1 1,在这之间和没有变化,这说明我们的差分数组是有用的。

在这里插入图片描述

证明

差分数组做法的正确性证明需要考虑 l o w low low h i g h high high 的不同情况。以 i i i x x x的关系分情况讨论。

  • i < x i<x i<x

    这种情况下, i + 1 ≤ k ≤ i − x + n i+1 \le k \le i-x+n i+1kix+n,此时 h i g h high high的最大值在 i = x i=x i=x时取得为 n n n,这样对 n n n进行取模后, h i g h = 0 high=0 high=0,于是 d i f f s [ 0 ] = d i f f s [ 0 ] − 1 diffs[0]=diffs[0]-1 diffs[0]=diffs[0]1,但是实际上,加一的范围在 [ l o w , n − 1 ] [low,n-1] [low,n1],和位置 0 0 0没有关系, h i g h high high 0 0 0是由于取模的关系,此时也满足 l o w ≥ h i g h low \ge high lowhigh,所以要把位置 0 0 0多减的 1 1 1给加回来, d i f f s [ 0 ] = d i f f s [ 0 ] + 1 diffs[0]= diffs[0]+1 diffs[0]=diffs[0]+1

  • i ≥ x i \ge x ix

    此情况下,由两段区域组成。

    • 当元素 x x x向右移动,此时 x x x能达到的最远位置为 n − 1 n-1 n1,且在 k k k轮调时,这个位置是原始位置 k − 1 k-1 k1处的元素占据,那么容易有 k − 1 = i k-1=i k1=i,那么 k = i + 1 k=i+1 k=i+1,当 k > i + 1 k>i+1 k>i+1时,元素 x x x仍然会在 [ i , n − 2 ] [i,n-2] [i,n2]的某个位置,所以 k ≥ i + 1 k \ge i+1 ki+1都会是使得元素 x x x的得分为 1 1 1的合法位置。

      这种情况下的 h i g h high high将是定值 n n n,对 n n n取模后为 0 0 0,有 i < x i < x i<x的论述可知,需要 d i f f s [ 0 ] = d i f f s [ 0 ] + 1 diffs[0]= diffs[0]+1 diffs[0]=diffs[0]+1

    • 当元素 x x x向左移动,元素 x x x的新位置必须在 [ x , i ] [x,i] [x,i]之间。 k k k轮调时,在位置 0 0 0的元素原来所在位置为 k k k,那么要满足元素 x x x的新位置必须在 [ x , i ] [x,i] [x,i]之间,对于原来的位置来说, i − k ≥ x i-k \ge x ikx,即 k ≤ i − x k \le i-x kix必须被满足。

      这一段需要将 d i f f s [ 0 ] = d i f f s [ 0 ] + 1 diffs[0]= diffs[0]+1 diffs[0]=diffs[0]+1,我们可以这么看,其实是因为这一段并没有 l o w low low,但是和元素 x x x向右移动,隐含了那一段区域的 h i g h = n high=n high=n一样,这里也隐含了这一段区域的 l o w = 0 low=0 low=0,所以 d i f f s [ 0 ] = d i f f s [ 0 ] + 1 diffs[0]= diffs[0]+1 diffs[0]=diffs[0]+1需要被执行。

    这两段区域的分析整体来看,那就是执行如下代码:

    ++diffs[low];
    --diffs[high];
    ++diffs[0];
    

总结上面两种情况,是否需要执行 d i f f s [ 0 ] = d i f f s [ 0 ] + 1 diffs[0]= diffs[0]+1 diffs[0]=diffs[0]+1都可以根据 l o w ≥ h i g h low \ge high lowhigh这个条件来判断。

class Solution {
public:
    int bestRotation(vector<int>& nums) {
        int n = nums.size();
        vector<int> diffs(n);
        for(int i = 0; i < n; ++i) {
            int x = nums[i];
            if (i < x) {
                int low = (i + 1) % n;
                int high = (i - x + n + 1) % n;
                ++diffs[low];
                --diffs[high];
                if (low >= high) {
                    // 此时high其实只能为0
                    // if (high != 0) {
                    //     return 0;       // 做一个测试,实际代码中可以去除
                    // }
                    ++diffs[0];
                }
            } else {
                // x往左移的这一段
                int low = 0, high = (i - x + 1) % n;
                ++diffs[low];
                if (high != 0) {
                    --diffs[high];
                }
                // x往右移的这一段
                low = (i + 1) % n;
                high = 0;
                ++diffs[low];    
                // ++diffs[high];   // 反正加了又要减
            }
        }
        int ans = 0;
        int score = 0;
        int maxScore = -1;
        for(int i = 0; i < n; ++i) {
            score += diffs[i];
            if (score > maxScore) {
                maxScore = score;
                ans = i;
            }
        }
        return ans;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值