LeetCode 1588. 所有奇数长度子数组的和

给你一个正整数数组 arr ,请你计算所有可能的奇数长度子数组的和。

子数组 定义为原数组中的一个连续子序列。

请你返回 arr 中 所有奇数长度子数组的和 。

输入:arr = [1,4,2,5,3]
输出:58
解释:所有奇数长度子数组和它们的和为:
[1] = 1
[4] = 4
[2] = 2
[5] = 5
[3] = 3
[1,4,2] = 7
[4,2,5] = 11
[2,5,3] = 10
[1,4,2,5,3] = 15
我们将所有值求和得到 1 + 4 + 2 + 5 + 3 + 7 + 11 + 10 + 15 = 58

法一:暴力法,时间复杂度为O(n3):

class Solution {
public:
    int sumOddLengthSubarrays(vector<int>& arr) {
        int res = 0;
        for (int i = 0; i < arr.size(); ++i) {    // i为计算连续数组和的起点
            for (int sz = 1; sz + i <= arr.size(); sz += 2) {
                res += accumulate(arr.begin() + i, arr.begin() + i + sz, 0);
            }
        }

        return res;
    }
};

法二:使用前缀和数组,前缀和即前n个数字的和,可以用一个数组保存下来前1、2、······、n个数字的和,之后每次计算一个连续子数组的和时,只需要用O(1)的时间即可,即如果想计算下标为2、3、4的子数组的和,只需用4的前缀和减去1的前缀和即可。前缀和数组需要O(n)的空间,而计算子数组和的时间从O(n)下降到了O(1),因此总时间复杂度为O(n2):

class Solution {
public:
    int sumOddLengthSubarrays(vector<int>& arr) {
        int res = 0;
        vector<int> prefixSum = {0};

        for (int i : arr) {
        	prefixSum.push_back(prefixSum.back() + i);
		}
		
        for (int i = 0; i < arr.size(); ++i) {    // i是子数组计算的起点
            for (int sz = 1; sz + i <= arr.size(); sz += 2) {    // sz为子数组的大小
                res += prefixSum[sz + i] - prefixSum[i];
            }
        }

        return res;
    }
};

法三:遍历一遍数组,每遍历到一个元素,计算出该元素会出现在几个奇数长度的子数组中,就知道了此元素在结果中会被计算多少次。当一个元素左边有连续偶(奇)数个元素时,它右边必须也有连续偶(奇)数个元素,这样加上此元素,子数组的元素数量才是奇数(以下谈到该元素左边或右边的元素数量时都包含该元素,并且元素数为0也算偶数个元素):
1.当该元素左边元素数量为left时,左边连续偶数个元素的情况有(left + 1) / 2种,左边连续奇数个元素的情况有left / 2种。
2.当该元素右边元素数量为right时,右边连续偶数个元素的情况有(right + 1) / 2种,右边连续奇数个元素的情况有right / 2种。

在找以上规律时,由于整数除法结果会截去小数位,因此可能会有两个可能的公式,如某元素左边有2个元素时,它左边只可能有一种连续偶数个元素的情况,左边连续偶数个元素的情况的公式就可以是left/2(left+1)/2;如果该元素左边有3个元素,它左边只可能有两种连续偶数个元素的情况,此时左边连续偶数个元素的情况的公式是(left+1)/2(left+2)/2,取交集就是(left+1)/2。其余公式同理。

此种方法每遍历到一个元素时,计算它在结果中出现的次数的时间复杂度为O(1),且只需遍历一次数组即可,因此总时间复杂度为O(n),空间复杂度为O(1):

class Solution {
public:
    int sumOddLengthSubarrays(vector<int>& arr) {
        int res = 0;
        
        for (int i = 0; i < arr.size(); ++i) {
            int left = i + 1, right = arr.size() - i;
            int left_odd = left / 2;
            int left_even = (left + 1) / 2;
            int right_odd = right / 2;
            int right_even = (right + 1) / 2;
        
            res += (left_odd * right_odd + left_even * right_even) * arr[i];
        }

        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值