689 Maximum Sum of 3 Non-Overlapping Subarrays

题目
思路:首先是长度为k的子数组的和。这个好计算。题目要求返回的是三个和最大的子数组的第一个数字的下标。下标要尽可能小。如果只要求这样,题目就很简单了。还有个要求是各个子数组不重叠。要想不重叠首先得要求下标不重叠。子数组1下标是:0,1,2;子数组2下标如果是1,2,3那肯定会重叠。其次要计算出每个子数组的最大值,最小值。在找下个子数组的时候,范围不能在这之间。我应该是傻眼了。需要考虑的因素比较多。看别人怎么解吧。
学习:看了别人的解法,明白题目意思了解错了。不重叠的子数组就是指下标不重叠,而不是各个值不重叠。要返回的三个下标(idx1,idx2,idx3) 满足 idx2>=idx1+k i d x 2 >= i d x 1 + k 并且 idx3>=idx2+k i d x 3 >= i d x 2 + k
还要学习的一点是:当计算了长度为k的子数组的和,计算之后其实得到了一个sums的数组。sums[i]表示以i为起点的长度为k的子数组的和。sums的长度是n-k+1。如此看待问题会比较简单一些。
对于求这三个下标我一开始的做法是:

        int max1 = sums[0],max2 = sums[0+k],max3 = sums[0+2*k];
        int idx1 = 0,idx2 = k,idx3 = 2*k;
        for (i = 1; i < k; i++) {
            if (sums[i] > max1) {
                max1 = sums[i];
                idx1 = i;
            }
            if (i+k <n && sums[i+k] > max2) {
                max2 = sums[i+k];
                idx2 = i+k;
            }
            if (i+2*k < n && sums[i+2*k] > max3) {
                max3 = sums[i+2*k];
                idx3 = i+2*k;
            }
        }

这样做的问题是:可能idx2没有变化,idx1变化了。这样他们的差值就不是k,就不能符合条件了。所以需要固定idx2的位置。n=sums.length;那么 idx1[0,idx2k] i d x 1 ∈ [ 0 , i d x 2 − k ] 并且 idx3[idx2+1,n1] i d x 3 ∈ [ i d x 2 + 1 , n − 1 ] 。而idx2能够取值的范围是 [k,nk1] [ k , n − k − 1 ] 。(n是原始数组长度)。

当idx2位置固定,对于idx1应该是[0,idx2-k]那个最大数的下标。这个问题可以用动态规划解决。用一个数组先保存从[0,当前位置]值最大的下标。动态转移方程式: 如果 sums[i]>sums[left[i1]] s u m s [ i ] > s u m s [ l e f t [ i − 1 ] ] ,则 left[i]=i l e f t [ i ] = i ;否则 left[i]=left[i1] l e f t [ i ] = l e f t [ i − 1 ] 。当然这里不用动态规划,那就每次循环一下从0到idx2-k找到最大值的下标。

右边也是一样。当idx2位置固定,对于idx3应该是[idx2+1,n-1]那个最大数的下标。用动态规划解决。用一个数组先保存从[当前位置,n-1]值最大的下标。动态转移方程式: 如果 sums[i]>=sums[right[i+1]] s u m s [ i ] >= s u m s [ r i g h t [ i + 1 ] ] ,则 right[i]=i r i g h t [ i ] = i ;否则 right[i]=right[i+1] r i g h t [ i ] = r i g h t [ i + 1 ] 。这里的条件是 >= >= 是因为,结果要求返回下标最小值。

学习链接

public int[] maxSumOfThreeSubarrays(int[] nums, int k) {
        int n = nums.length;
        //计算和,因为入参都是正数,所以不需要初始化sums,否则可以都初始化为Integer.MIN_VALUE;
        int[] sums = new int[n];
        //这步可以优化
        //0,1 1,2  2,3 3,4  4,5 5,6
        int sum = 0;
        for (int i = 0; i <n; i++) {
            sum += nums[i];
            if(i>=k) sum -= nums[i-k];
            if(i>=k-1) sums[i-k+1] = sum; 
        }

        //与左边的数组比较
        int[] left = new int[n-k+1];
        int best = 0;
        for(int i=0;i<left.length;i++){
            if(sums[i]>sums[best]) best = i;
            left[i] = best;
        }

        //跟右边的数组比较
        int[] right = new int[n-k+1];
        best = right.length - 1;
        for(int i = right.length -1;i>=0;i--){
            if(sums[i]>=sums[best]) best = i;
            right[i] = best;
        }

        int idx1 = -1,idx2 = -1,idx3 = -1;
        for (int j = k; j < right.length-k; j++) {
            int l = left[j-k],r = right[j+k];
            if(idx1==-1 || (sums[idx1]+sums[idx2]+sums[idx3] < sums[l]+sums[j]+sums[r])){
                idx1 = l;
                idx2 = j;
                idx3 = r;
            }
        }
        return new int[]{idx1,idx2,idx3};
    }

复杂度分析:时间复杂度O(n),空间复杂度O(n)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值