[Leetcode][396. 旋转函数] 3种方法:暴力法,动态规划,前缀和 + 滑动窗口
By Long Luo
方法一: 暴力法
思路与算法
很容易想到的方法是遍历,使用 \(2\) 个循环,很明显最多只能有 \(len\) 次旋转,因为对称性,所以数组下标可能会 \(>= len\),所以要取余 \((i+j) \mod len\) 。
每次旋转更新最大值,时间复杂度为 \(O(N^2)\) 。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// BF time: O(n^2) space: O(1)
public int maxRotateFunction(int[] nums) {
int len = nums.length;
if (len <= 1) {
return 0;
}
int ans = Integer.MIN_VALUE;
for (int i = 0; i < len; i++) {
int sum = 0;
for (int j = 0; j < len; j++) {
sum += j * nums[(i + j) % len];
}
ans = Math.max(ans, sum);
}
return ans;
}
复杂度分析:
时间复杂度:\(O(N^2)\) ,其中 \(N\) 是数组 \(\textit{nums}\) 的长度。
空间复杂度:\(O(1)\) 。
方法二: 动态规划
思路与算法:
方法一会超时,其实观察旋转数组的变化:
\[ F(0) = 0 \times nums[0] + 1 \times nums[1] + \ldots + (n - 1) \times nums[n-1] \]
\[ F(1) = 1 \times nums[0] + 2 \times nums[1] + \ldots + 0 \times nums[n-1] = F(0) + \sum_{i=0}^{n-1}nums[i] - n \times nums[n-1] \]
我们很容易发现相邻旋转的递推关系:
\[ F(k+1) = F(k) + \sum_{i=0}^{n-1}nums[i] - n \times nums[n-k], 0 \le k \lt n \]
从\(F(0)\)出发,迭代计算出不同的\(F(k)\),并求出最大值。
很容易使用DP写出如下代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// DP time: O(n) space: O(n)
public int maxRotateFunction_dp(int[] nums) {
int len = nums.length;
if (len <= 1) {
return 0;
}
int[] dp = new int[len];
int sum = 0;
for (int i = 0; i < len; i++) {
dp[0] += i * nums[i];
sum += nums[i];
}
int ans = dp[0];
for (int i = 1; i < len; i++) {
dp[i] = dp[i - 1] + sum - len * nums[(len - i) % len];
ans = Math.max(ans, dp[i]);
}
return ans;
}
观察上述代码,我们发现dp[]
数组只是记录数值,实际上可以将空间复杂度优化到\(O(1)\)。