Leetcode每日一题 798. 得分最高的最小轮调 动态规划 + 预处理 (感谢 bellyache 提供的思路)

296 阅读4分钟

📖本篇内容:Leetcode每日一题 798. 得分最高的最小轮调 动态规划 + 预处理

📑 文章专栏:leetcode每日一题《打卡日常》

📆 最近更新:2022年3月8日 Leetcode每日一题2055. 蜡烛之间的盘子 前缀和+预处理 / 二分 ⭐算法仓库:小付的算法之路——Alascanfu-algorithm.git.io 🙊个人简介:一只二本院校在读的大三程序猿,本着注重基础,打卡算法,分享技术作为个人的经验总结性的博文博主,虽然可能有时会犯懒,但是还是会坚持下去的,如果你很喜欢博文的话,建议看下面一行~(疯狂暗示QwQ) 🌇 点赞 👍 收藏 ⭐留言 📝 一键三连 关爱程序猿,从你我做起

🙊写在前面🙊

来了来了,上午满课,睡过午觉过后开始今天的学习~

题目

给你一个数组 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 。

示例1:

输入:nums = [2,3,1,4,0]
输出:3
解释:
下面列出了每个 k 的得分:
k = 0,  nums = [2,3,1,4,0],    score 2
k = 1,  nums = [3,1,4,0,2],    score 3
k = 2,  nums = [1,4,0,2,3],    score 3
k = 3,  nums = [4,0,2,3,1],    score 4
k = 4,  nums = [0,2,3,1,4],    score 3
所以我们应当选择 k = 3,得分最高。

示例2:

输入:nums = [1,3,0,2,4]
输出:0
解释:
nums 无论怎么变化总是有 3 分。
所以我们将选择最小的 k,即 0。

提示

  • 1 <= nums.length <= 10^5
  • 0 <= nums[i] < nums.length

📝思路📝

本题考查知识点

这里参阅学习了bellyache大佬的思路:bellyache的 Leetcode 主页

  • 思路:作为本题,我们很容易可以知道每次轮换使得当前值对应的下标会 -1,由此我们就可以知道如下几种情况:

  • 1、如果在轮换之前当前对应的值大于此时的下标,那么轮换之后他就一定还是大于或者等于下标的,所以肯定还是处于之前的得分状态

  • 2、如果在轮换之前当前对应的值等于此时的下标,那么轮换之后他就一个会比下标小1 ,这是我们发现的每次轮换使得当前值对应下标会 -1得出来的。

  • 3、如果在 轮换之前处于下标0位置的数 ,经过轮换之后该数 就会被轮换到数组的最后位置此时无论处于数组第一个位置的该正整数的值是多少都是大于等于0的所以 会得分。

  • 由上述三点的得分状态分析不同的转换次数的得分情况 记录最大得分的转换次数就可以求解。

  • 我们很容易知道 score[k] = score[k-1] - ( k-1次轮换时此时下标等于下标对应值的个数 ) + 1 这个看起来就和动归的转换方程有点像啦,接着就是代码实现。

  • 本地难点在于——如何求解 k-1次轮换时此时下标等于下标对应值的个数?

  • 换一种思路就是相当于求当前k次循环时,下标等于下标对应值的元素个数。这里用数组moveCurVal2ValIdxStep[n] 来存储对应k次循环下,下标与下标对应值相等的元素个数。

⭐代码实现⭐

动态规划+预处理

class Solution {
    public int bestRotation(int[] nums) {
        int val = 0,n = nums.length;
        // 定义的是一个经过k次轮换后 此时nums中的值与下标相等的个数
        int[] moveCurVal2ValIdxStep = new int[n];
        // 这里是用来找到 当前数组的当前值需要转换多少次才能到达对应其值的下标 至于为什么可以这样呢? 这里不会越界么?
        // 题中给的提示告诉我们了 数组中的每个值都是小于数组长度的 0 <= nums[i] < nums.length 这样就不可能会越界
        for (int i = 0 ; i< n;i++){
            // 如果当前的值是大于下标的所以他肯定需要轮换到第一个之后再进行轮换才可能到达当前的值对应的下标
            // moveCurVal2ValIdxStep[i+n-nums[i]]++ 我们可以理解为当前数值需要轮换 ? 次才能使得当前数值与下标相同 
            // 使得在?次时的个数+1,即可遍历数组中每个数字需要经过?次轮换才能到达下标值与下标相同位置的个数啦~
            if (nums[i] > i)moveCurVal2ValIdxStep[i + n - nums[i]]++;
            // 这里是如果我们当前的下标都大于或者等于当前值了,
            //我们就直接计算轮换 i - nums[i] 次就可以使得我们的当前值得下标 = 当前值
            else moveCurVal2ValIdxStep[i-nums[i]]++;
        } 
        int k = 0 ,maxVal = val;
        for (int i = 1;i< n;i++){
            // 状态转换方程 dp[i] = dp[i-1] + (k-1次轮换时此时下标等于下标对应值的个数 
            // 即 下标0时 nums[0] = 0的情况下) + 1 (这里为什么要加1呢 
            // 因为当我们第一个的值转换后就会到数组的最后一个位置此时最后位置的数值一定是大于等于 0 的所以需要+1)
            val = val - moveCurVal2ValIdxStep[i-1] + 1;
            // 通过滚动变量val 找到最大值记录当前的转换次数k 得到转换 k 次能得到最大的分数
            if (val > maxVal){
                maxVal = val;
                k = i;
            }
        }
        return k;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

运行结果

动态规划+预处理

image.png

🙊写在最后🙊

2022-3-9今天小付打卡了哦~

美好的日出 美好的山河

都因有你存在 而璀璨 耀眼

6.gif