|
| 1 | +**689. 三个无重叠子数组的最大和** |
| 2 | +--- |
| 3 | +[https://leetcode-cn.com/problems/maximum-sum-of-3-non-overlapping-subarrays/](https://leetcode-cn.com/problems/maximum-sum-of-3-non-overlapping-subarrays/) |
| 4 | + |
| 5 | +```java |
| 6 | + |
| 7 | + public int[] maxSumOfThreeSubarrays(int[] nums, int K) { |
| 8 | + // W是由数组nums中每K个元素的和组成的整数数组 |
| 9 | + // 把间隔K看成滑动窗口的长度 |
| 10 | + // 窗口在nums数组上从第1个元素开始滑动 |
| 11 | + // 窗口每滑动一次,就计算一次窗口内所有元素的和,放入数组W |
| 12 | + // 滑动窗口到nums的第nums.length - K个元素时,共计算了nums.length - K次 |
| 13 | + // 此时nums数组中还剩K个元素,可以计算最后一次 |
| 14 | + // 所以W的长度是nums.length - K + 1 |
| 15 | + int[] W = new int[nums.length - K + 1]; |
| 16 | + //定义一个整数sum用于存储每K个元素的和 |
| 17 | + int sum = 0; |
| 18 | + //遍历数组nums并求得W数组的所有值 |
| 19 | + for (int i = 0; i < nums.length; i++) { |
| 20 | + //用+=对nums中连续的元素累加求和 |
| 21 | + sum += nums[i]; |
| 22 | + // 如果i>=K,说明前K个元素的值已经累加完毕,窗口开始向右滑动 |
| 23 | + // 从K个元素之后,每次滑动1个元素 |
| 24 | + // 需要从sum里减去上一次滑动窗口的首个元素nums[i - K] |
| 25 | + // sum -= nums[i - K]得到从第[i-K+1]个元素起到第i个元素为止的K个元素之和 |
| 26 | + if (i >= K) sum -= nums[i - K]; |
| 27 | + // 第一组K个元素在nums中的下标从0到K-1 |
| 28 | + // 所以W的第一个元素是 W[i-K+1] = W[(K-1)-K+1] = W[0] |
| 29 | + if (i >= K - 1) W[i - K + 1] = sum; |
| 30 | + } |
| 31 | + |
| 32 | + // 首先需要明白本解法中nums、W和left(或right)这三个数组索引的对应关系 |
| 33 | + // |
| 34 | + // 先看W[i]和nums[i]的关系: |
| 35 | + // 在前一个给W赋值的for循环代码中可以看出,W[i]对应着nums中从nums[i]算起,到nums[i+K-1],共K个元素的和 |
| 36 | + // 如题目示例,nums=[1,2,1,2,6,7,5,1],K=2,W=[3,3,3,8,13,12,6] |
| 37 | + // W[0]=nums[0]+nums[1]=1+2=3,即W[0]等于nums[0]到nums[0+2-1]=nums[1],共2个元素的和 |
| 38 | + // |
| 39 | + // 再看left[i]和W[i]的关系: |
| 40 | + // 给left[i](或right[i])赋值时,需要找到从W[0]到W[i]为止,元素值且索引最小那个元素 |
| 41 | + // 然后将W中这个元素的索引赋给left[i] |
| 42 | + // 即给left[0]赋值时,比较W[0]到W[0]共1个元素, |
| 43 | + // 给left[1]赋值时,比较W[0]到W[1]共2个元素, |
| 44 | + // 给left[2]赋值时,比较W[0]到W[2]共3个元素...以此类推 |
| 45 | + // 因为W[0]=w[1]=W[2]=3,而W[0]、W[1]、W[2]三个元素中W[0]的索引最小 |
| 46 | + // 所以left[0]=left[1]=left[2]=0,都取最小的那个索引0 |
| 47 | + // |
| 48 | + // 同时,由于到nums[nums.length - K + 1]为止,W和nums中的索引是一一对应的 |
| 49 | + // 而left[i]记录的又是W中的索引,所以left[i]与nums中的元素索引一一对应 |
| 50 | + // 索引的对应关系清楚了,下面关于left和right的代码就容易理解了 |
| 51 | + |
| 52 | + //题目要求返回nums中和最大的3个子序列的起始索引 |
| 53 | + //我们将这3个子序列中的第1个子序列的可能索引都存入一个整数数组left |
| 54 | + int[] left = new int[W.length]; |
| 55 | + //定义一个整数best,记录每次迭代时,从数组W中获取到的相对最大值在W中的索引 |
| 56 | + //best默认值为0 |
| 57 | + int best = 0; |
| 58 | + //按索引从小到大遍历数组W |
| 59 | + for (int i = 0; i < W.length; i++) { |
| 60 | + //W[i] > W[best]表示当前第i个元素W[i],大于W中已知的最大元素W[best] |
| 61 | + //将索引i赋值给best |
| 62 | + if (W[i] > W[best]) best = i; |
| 63 | + //将当前为止W中最大值的索引best赋给left[i] |
| 64 | + left[i] = best; |
| 65 | + } |
| 66 | + |
| 67 | + //题目要求返回nums中和最大的3个子序列的起始索引 |
| 68 | + //我们将这3个子序列中的第3个子序列的可能索引都存入一个整数数组right |
| 69 | + int[] right = new int[W.length]; |
| 70 | + //best默认值为W数组的末尾索引 |
| 71 | + best = W.length - 1; |
| 72 | + //按索引从大到小遍历数组W |
| 73 | + for (int i = W.length - 1; i >= 0; i--) { |
| 74 | + //W[i] > W[best]表示当前第i个元素W[i],大于等于W中已知的最大元素W[best] |
| 75 | + //将索引i赋值给best |
| 76 | + if (W[i] >= W[best]) best = i; |
| 77 | + //将当前为止W中最大值的索引best赋给right[i] |
| 78 | + right[i] = best; |
| 79 | + } |
| 80 | + |
| 81 | + // 定义一个整数数组ans用于返回最终结果,即3个子序列的初始索引 |
| 82 | + int[] ans = new int[]{-1, -1, -1}; |
| 83 | + // for循环的控制变量j在循环体中要进行j-K和j + K的操作 |
| 84 | + // j-K要大于等于0,j+K要小于W.length |
| 85 | + // 所以j的初始值为K,j的上限值W.length - K |
| 86 | + for (int j = K; j < W.length - K; j++) { |
| 87 | + // 定义3个子序列的中间序列的索引为j |
| 88 | + // 那么W[j]等于nums[j]到nums[j+K-1]共K个元素的和 |
| 89 | + // 因为3个子序列不重叠,所以: |
| 90 | + // 第1个序列的索引应该小于或等于j-K |
| 91 | + // 第3个序列的索引应该大于或等于j+K |
| 92 | + // left[j - K]记录了到从nums[0]到nums[j-K]为止,子序列和最大初始索引最小的那个索引 |
| 93 | + // right[j - K]记录了到nums[nums.length]到nums[j+K]为止,子序列和最大初始索引最小的那个索引 |
| 94 | + int i = left[j - K], k = right[j + K]; |
| 95 | + //当ans[0] == -1时,ans还没有赋值 |
| 96 | + //当W[i] + W[j] + W[k] > W[ans[0]] + W[ans[1]] + W[ans[2]]时 |
| 97 | + //说明找到了更大的3个子序列之和 |
| 98 | + //满足上述两个条件之一,即对ans[0]、ans[1]、ans[2]重新赋值 |
| 99 | + if (ans[0] == -1 || W[i] + W[j] + W[k] > |
| 100 | + W[ans[0]] + W[ans[1]] + W[ans[2]]) { |
| 101 | + //分别将初始索引i、j、k赋值给ans[0]、ans[1]、ans[2] |
| 102 | + ans[0] = i; |
| 103 | + ans[1] = j; |
| 104 | + ans[2] = k; |
| 105 | + } |
| 106 | + } |
| 107 | + //返回最终结果 |
| 108 | + return ans; |
| 109 | + } |
| 110 | + |
| 111 | +``` |
| 112 | + |
| 113 | +**复杂度分析** |
| 114 | + |
| 115 | +时间复杂度:O(N), |
| 116 | +方法中使用了4个for循环,迭代次数都在nums的长度范围内, |
| 117 | +所以时间复杂度为4N,消去常数项,时间复杂度为O(N) |
| 118 | + |
| 119 | +空间复杂度:O(N), |
| 120 | +定义了3个整数数组W、left、right, |
| 121 | +占用空间都在nums数组的长度范围内, |
| 122 | +还定义了一个数组ans,长度为常数3, |
| 123 | +所以空间复杂度是3N+3,消去常数项最终空间复杂度为O(N) |
| 124 | + |
| 125 | +--- |
| 126 | + |
| 127 | +**参考资料** |
| 128 | + |
| 129 | +* 本题leetCode英文官方题解: |
| 130 | +[https://leetcode.com/articles/maximum-sum-of-3-non-overlapping-intervals/](https://leetcode.com/articles/maximum-sum-of-3-non-overlapping-intervals/) |
0 commit comments