【C/C++】396. 旋转函数

202 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第22天,点击查看活动详情


题目链接:396. 旋转函数

题目描述

给定一个长度为 n 的整数数组 nums 。

假设 arrkarr_k 是数组 nums 顺时针旋转 k 个位置后的数组,我们定义 nums 的 旋转函数 F 为:

  • F(k)=0arrk[0]+1arrk[1]+...+(n1)arrk[n1]F(k) = 0 * arr_k[0] + 1 * arr_k[1] + ... + (n - 1) * arr_k[n - 1]

返回 F(0)F(1)...F(n-1) 中的最大值 。

生成的测试用例让答案符合 32 整数。

提示:

  • n=nums.lengthn = nums.length
  • 1n1051 \leqslant n \leqslant 10^5
  • 100nums[i]100-100 \leqslant nums[i] \leqslant 100

示例 1:

输入: nums = [4,3,2,6]
输出: 26
解释:
F(0) = (0 * 4) + (1 * 3) + (2 * 2) + (3 * 6) = 0 + 3 + 4 + 18 = 25
F(1) = (0 * 6) + (1 * 4) + (2 * 3) + (3 * 2) = 0 + 4 + 6 + 6 = 16
F(2) = (0 * 2) + (1 * 6) + (2 * 4) + (3 * 3) = 0 + 6 + 8 + 9 = 23
F(3) = (0 * 3) + (1 * 2) + (2 * 6) + (3 * 4) = 0 + 2 + 12 + 12 = 26
所以 F(0), F(1), F(2), F(3) 中的最大值是 F(3) = 26 。

示例 2:

输入: nums = [100]
输出: 0

整理题意

给定一个整数数组,将数组不断旋转(这里的旋转就是将数组第一个元素放到末尾或者是将末尾元素放到数组首部所形成的新数组),每次旋转后将数组的每个整数乘以对应的下标(下标从 0 开始),然后求和,取最大值。

解题思路分析

习惯性动作,首先明确数据范围:

  • 1n1051 \leqslant n \leqslant 10^5
  • 100nums[i]100-100 \leqslant nums[i] \leqslant 100

如果暴力解决时间复杂度为 O(n2)O(n^2),显然会 TLE 超时。再确定题目数据是否会溢出,这里题目直接给出了提示,保证生成的测试用例让答案符合 32 整数。

我们假设每次旋转操作都是 将数组第一个元素放到末尾,观察每次的求和区间:

396. 旋转函数.jpg

这里 total 表示 i * nums[i]i ∈ [1, n))的求和,sum 表示 nums[i]i ∈ [1, n))的和,当我们对数组进行旋转时:

396. 旋转函数 (1).jpg

观察旋转后我们可以发现:

  • total 值的变化:减少的值相当于旋转前的 sum,增加了 (n - 1) * nums[0]
  • sum 值的变化:减少了 nums[1],增加了 nums[0]。 我们可以将情况一般化:
  • total 值的变化:减少的值相当于旋转前的 sum,增加了 (n - 1) * nums[i - 1]
  • sum 值的变化:减少了 nums[i],增加了 nums[i - 1]。 那么我们可以通过一次遍历维护 totalsum 的值,求得 total 的最大值即为答案。

具体实现

我们假设 n 为数组长度,total 为每个整数乘以对应的下标的和,sumnums[1]nums[n - 1] 的求和。

  1. 通过遍历一次 nums 数组求得 totalsum 的初始值。
  2. 再从 1 遍历到 n - 1,每次维护 totalsum 的值:
    • total -= sum;:每次减去上一轮的 sum
    • total += (n - 1) * nums[i - 1]; 相当于把 nums[i - 1] 旋转到数组尾部;
    • sum -= nums[i];:将 nums[i] 旋转到下标为 0 的位置;
    • sum += nums[i - 1];nums[i - 1] 旋转到数组尾部。
  3. 期间用 ans 记录 total 的最大值即可:ans = max(ans, total);

复杂度分析

  • 时间复杂度:O(n)O(n),其中 n 是数组 nums 的长度。计算第一个 sumtotal 消耗 O(n)O(n) 时间,后续迭代 n−1 次消耗 O(n)O(n) 时间。
  • 空间复杂度:O(1)O(1)。仅使用常数空间。

代码实现

class Solution {
public:
    int maxRotateFunction(vector<int>& nums) {
        //n为数组长度
        int n = nums.size();
        //sum为nums数组中下标为[1, n)的和
        int sum = 0;
        //total为当前选择函数F的值
        int total = 0;
        //初始化sum和total
        for(int i = 1; i < n; i++){
            sum += nums[i];
            total += (i * nums[i]);
        }
        //ans记录答案,也就是最大的total
        int ans = total;
        //不断旋转数组
        for(int i = 1; i < n; i++){
            //total每次减去[1, n)的和sum
            total -= sum;
            //total每次加上(n - 1) * nums[i - 1],相当于把nums[i - 1]旋转到数组尾部
            total += (n - 1) * nums[i - 1];
            //更新sum的值,将nums[i]的值删除,增加nums[i - 1]的值
            sum -= nums[i];
            sum += nums[i - 1];
            //记录total的最大值
            ans = max(ans, total);
        }
        return ans;
    }
};

总结

该题的 核心思想是迭代,在迭代过程中维护 totalsum 的值,记录 total 最大值即可,没有特殊样例以及坑点。需要注意的细节是答案的初始化和维护变量过程中的修改顺序。


结束语

很多人都觉得优秀是因为有天赋。其实,天赋异禀的人很少,真正让他们出类拔萃的是全心投入和用心付出。拥有得天独厚的优势固然重要,但更多时候,优秀靠的是日复一日的坚持努力。