LeetCode 798.得分最高的最小轮调(差分应用)


题目

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

解题思路

暴力解

遍历每个可能的 k,计算轮调 k个位置之后的数组得分。假设数组的长度是 n,则有 n 种可能的轮调,对于每种轮调都需要 O ( n ) O(n) O(n) 的时间计算得分,总时间复杂度是 O ( n 2 ) O(n^2) O(n2),考虑到数据范围为 1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1n105,暴力解会超出时间限制

差分

假定当前下标为 i,轮调次数为 k,那么轮调后下标为 i−k,当新下标为负数时,相当于 nums[i] 出现在比原数组更“靠后”的位置,此时下标等价于 ( i − k + n ) m o d    n (i - k + n) \mod n (ik+n)modn

考虑什么情况下 n u m s [ i ] nums[i] nums[i] 能够得分?

首先新下标的取值范围为 [ 0 , n − 1 ] [0, n - 1] [0,n1],即有 0 ⩽ i − k ⩽ n − 1 0 \leqslant i - k \leqslant n - 1 0ikn1。由此可分析出 k k k 的取值范围为:

0 ⩽ i − k ⇔ k ⩽ i 0 \leqslant i - k \Leftrightarrow k \leqslant i 0ikki

i − k ⩽ n − 1 ⇔ i − ( n − 1 ) ⩽ k i - k \leqslant n - 1 \Leftrightarrow i - (n - 1) \leqslant k ikn1i(n1)k

即由新下标取值范围可知 k k k 的上下界分别为 i i i i − ( n − 1 ) i - (n - 1) i(n1)

同时为了满足得分定义,还有 n u m s [ i ] ⩽ i − k nums[i] \leqslant i - k nums[i]ik,进行变形可得:

n u m s [ i ] ⩽ i − k ⇔ k ⩽ i − n u m s [ i ] nums[i] \leqslant i - k \Leftrightarrow k \leqslant i - nums[i] nums[i]ikkinums[i]
此时有两个关于 k k k 的上界
k ⩽ i k \leqslant i ki
以及
k ⩽ i − n u m s [ i ] k \leqslant i - nums[i] kinums[i]
由于 n u m s [ i ] nums[i] nums[i]取值范围为 [ 0 , n ) [0, n) [0,n),则有
i − n u m s [ i ] ⩽ i i - nums[i] \leqslant i inums[i]i
由于必须同时满足「合法移动(有效下标)」和「能够得分」,仅考虑范围更小(更严格)由 n u m s [ i ] ⩽ i − k nums[i] \leqslant i - k nums[i]ik 推导而来的上界 k ⩽ i − n u m s [ i ] k \leqslant i - nums[i] kinums[i] 即可。

综上, n u m s [ i ] nums[i] nums[i] 能够得分的 k k k 的取值范围为:
[ i − ( n − 1 ) , i − n u m s [ i ] ] [i - (n - 1), i - nums[i]] [i(n1),inums[i]]
最后考虑 [ i − ( n − 1 ) , i − n u m s [ i ] ] [i - (n - 1), i - nums[i]] [i(n1),inums[i]]编程时均进行加 n n n n n n 转为正数)什么情况下为合法的连续段:

  • i − ( n − 1 ) ⩽ i − n u m s [ i ] i - (n - 1) \leqslant i - nums[i] i(n1)inums[i] 时, [ i − ( n − 1 ) , i − n u m s [ i ] ] [i - (n - 1), i - nums[i]] [i(n1),inums[i]] 为合法连续段;
  • i − ( n − 1 ) > i − n u m s [ i ] i - (n - 1) > i - nums[i] i(n1)>inums[i] 时,根据负数下标等价于 ( i − k + n ) m o d    n (i - k + n) \mod n (ik+n)modn,此时 [ i − ( n − 1 ) , i − n u m s [ i ] ] [i - (n - 1), i - nums[i]] [i(n1),inums[i]] 等价于 [ 0 , i − n u m s [ i ] ] [0, i - nums[i]] [0,inums[i]] [ i − ( n − 1 ) , n − 1 ] [i - (n - 1), n - 1] [i(n1),n1] 两段。

连续一段有可能出现中间,也有可能是两头两段

分析出原数组的每个 n u m s [ i ] nums[i] nums[i] 能够得分的 k k k 的取值范围后,就可以将原问题转换为【区间修改】&【单点查询】问题,进而可利用差分模板解决(对照LeetCode 1109.航班预订统计),举例如下:

输入:nums = [2,3,1,4,0]

转换为区间修改问题,定义数组K[n]K[i]代表轮调下标为i时的得分

K下标01234
nums[0]=2可以得分的轮调下标范围111
nums[1]=3可以得分的轮调下标范围11
nums[2]=1可以得分的轮调下标范围1111
nums[3]=4可以得分的轮调下标范围1
nums[4]=0可以得分的轮调下标范围11111
每次轮调所能获得的分数23343

综上,轮调下标k=3时,得分最高。

算法流程如下:

  • 分析出原数组的每个 n u m s [ i ] nums[i] nums[i] 能够得分的 k k k 的取值范围表达式
  • 遍历原数组并更新差分数组
    • 计算出每个 n u m s [ i ] nums[i] nums[i] 能够得分的 k k k 的取值范围,假定取值范围为 [ l , r ] [l, r] [l,r],可以对 [ l , r ] [l, r] [l,r] 进行 + 1 +1 +1 标记(即【区间修改】,修改差分数组两端点即可),代表 k k k 的取值范围为 [ l . r ] [l.r] [l.r] n u m s [ i ] nums[i] nums[i]能够得 1 分
  • 当处理完所有的 n u m s [ i ] nums[i] nums[i] 并更新差分数组之后,找到标记次数最多的位置 k k k 即是答案
    • 遍历差分数组并计算前缀和,则每个下标处的前缀和表示当前轮调下标处的得分
    • 在遍历过程中维护最大得分和最大得分的最小轮调下标,遍历结束之后即可得到结果
class Solution {
    public int bestRotation(int[] nums) {
        int n = nums.length;
        int[] diffs = new int[n + 1];
        for (int i = 0; i < n; i++) {
            // nums[i] 能够得分的 k 的取值范围[l, r]
            int l = (i - (n - 1) + n) % n;
            int r = (i - nums[i] + n) % n;
            if (l <= r) {
                // 得分段出现在中间
                add(diffs, l, r);
            } else {
                // 得分段出现在两头
                add(diffs, 0, r);
                add(diffs, l, n - 1);
            }
        }

        // 前缀和 每个下标处的前缀和表示当前轮调下标处的得分
        // 不需要显性创建得分数组 points
        // 遍历差分数组时即可根据前缀和得到数组 points 中的每个元素值,节约空间
        for (int i = 1; i <= n; i++) {
            diffs[i] += diffs[i - 1];
        }
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            // 维护最大得分下标
            if (diffs[i] > diffs[ans]) {
                ans = i;
            }
        }
        return ans;
    }

    // 区间修改
    private void add(int[] c, int l, int r) {
        c[l] += 1;
        c[r + 1] -= 1;
    }
}
  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要遍历数组 nums \textit{nums} nums两次。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums的长度。需要创建长度为 n n n 的数组 diffs \textit{diffs} diffs

Reference

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xylitolz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值