LeetCode 396. 旋转函数
题目描述


题意分析
数组向右旋转
k次后,用每个元素乘以它的新下标并求和,得到F(k)。求F(0)到F(n - 1)的最大值,包含不旋转的情况;旋转n次会回到原数组,不必重复计算。
解法:递推公式
核心思路
[!blue]
若每次真的旋转数组并重新求和,总共需要平方级时间。相邻两次旋转中元素不变,只是下标权重发生规律变化,可以从上一轮的加权和直接递推。
设当前末尾元素为
x,数组总和为sum。向右旋转一次时,除x外的元素都向右移动一格,权重加一;可以先给所有元素的权重都加一,使加权和增加sum。但x实际移到下标 0,刚才却把它的权重从n - 1加成了n,还需要减去n * x。所以
新加权和 = 旧加权和 + sum - n * x。第k次右旋移到开头的元素,在原数组中的下标是n - k,得到F(k) = F(k - 1) + sum - n * nums[n - k]。先扫描一次计算
sum和F(0),之后用cur依次保存当前旋转值,用best保存已经枚举的最大值。每次更新只做常数次运算,并且准确对应一种新的旋转位置,枚举完n种位置就覆盖了所有候选。
解题步骤
- 计算数组总和,以及
cur = Σ i * nums[i]。- 用真实的
F(0)初始化best,不要默认答案至少为 0。- 枚举
k = 1..n - 1,按递推式更新cur,再用它更新best。- 返回
best。长度为 1 时只有权重 0,初始值就是答案。题目保证最终答案在 32 位整数范围内,但中间加权和不应依赖这个保证。代码用
long或int64保存计算结果,并在乘法之前提升类型,最后再转换返回值。
代码实现
// 旋转一次的递推关系:F(k) = F(k-1) + sum - n * nums[n-k]。
class Solution {
public int maxRotateFunction(int[] nums) {
int n = nums.length;
long sum = 0;
long cur = 0;
for (int i = 0; i < n; i++) {
sum += nums[i];
cur += (long) i * nums[i];
}
long best = cur;
for (int k = 1; k < n; k++) {
// 所有权重先加一,再扣掉越过边界项的全部新权重
cur = cur + sum - (long) n * nums[n - k];
if (cur > best) {
best = cur;
}
}
return (int) best;
}
}
// 旋转一次的递推关系:F(k) = F(k-1) + sum - n * nums[n-k]。
func maxRotateFunction(nums []int) int {
n := len(nums)
var sum int64
var cur int64
for i, val := range nums {
sum += int64(val)
cur += int64(i) * int64(val)
}
best := cur
for k := 1; k < n; k++ {
// 所有权重先加一,再扣掉越过边界项的全部新权重
cur = cur + sum - int64(n)*int64(nums[n-k])
if cur > best {
best = cur
}
}
return int(best)
}
复杂度分析
- 时间复杂度:$O(n)$,初始统计与滚动更新各一次。
- 空间复杂度:$O(1)$,只维护总和、当前值与最大值。
关键点总结
[!green]
- 总和是全部权重加一的公共变化,减去
n倍负责修正越过边界的一项。nums[n - k]来自原数组,计算过程中不需要修改或旋转数组。- 中间乘法与加权和使用宽整数。
易错点总结
[!yellow]
- 最大值初始零,会在全部候选为负时引入不存在答案。
- 扣减用
n-1,忘了刚才已经给末项多加一份。- 比较后才更新,会漏掉最后一次旋转。
- 先用窄整数相乘、再赋给宽整数,不能避免乘法本身的溢出。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 798. 得分最高的最小轮调 | 困难 | 同样在全部旋转位置中求最优值,原题累计满足位置条件的贡献,本题累计带下标权重的和。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!