【路飞】53. 最大子数组和、1508. 子数组和排序后的区间和

118 阅读2分钟

53. 最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

示例 1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

输入: nums = [1]
输出: 1

示例 3:

输入: nums = [5,4,-1,7,8]
输出: 23

解题思路:一般数组和我们都会用到前缀和,我们可以求得前缀和,区间和值等于前缀和值相减,所以我们可以记录前缀和里的最小值,用当前值减最小值,然后保存到数组,数组里最大值就是区间和最大值,代码如下:

var maxSubArray = function(nums) {
    //边界值判断
    if(nums.length == 1) return nums[0];
    //记录前缀和值
    let ans = 0;
    //保存前缀和值
    let ansList = [0];
    //计算前缀和值
    for(let i = 0; i < nums.length; i ++){
        ans += nums[i];
        ansList.push(ans);
    }
    //记录当前位置之前的最小值
    let min = 0;
    //保存前缀和当前值与最小值相减
    let minList = [];
    for(let i = 0; i < ansList.length; i ++){
        //保存区间和,因为区间和等于前缀和相减
        minList.push(ansList[i] - min);
        //更新最小值
        min = ansList[i] > min ? min : ansList[i];
    }
    //排序,最大就是最大区间和值
    minList.sort(function(a,b){
        return b - a;
    })
    //因为默认minList第一个数是0,所以如果最大值是0的话,就去第二位数
    return minList[0] == 0 ? minList[1] : minList[0];
};

1508. 子数组和排序后的区间和

给你一个数组 nums ,它包含 n 个正整数。你需要计算所有非空连续子数组的和,并将它们按升序排序,得到一个新的包含 n * (n + 1) / 2 个数字的数组。

请你返回在新数组中下标为 left 到 right (下标从 1 开始)的所有数字和(包括左右端点)。由于答案可能很大,请你将它对 10^9 + 7 取模后返回。

示例 1:

输入:nums = [1,2,3,4], n = 4, left = 1, right = 5
输出:13 
解释:所有的子数组和为 1, 3, 6, 10, 2, 5, 9, 3, 7, 4 。将它们升序排序后,我们得到新的数组 [1, 2, 3, 3, 4, 5, 6, 7, 9, 10] 。下标从 le = 1 到 ri = 5 的和为 1 + 2 + 3 + 3 + 4 = 13

示例 2:

输入:nums = [1,2,3,4], n = 4, left = 3, right = 4
输出:6
解释:给定数组与示例 1 一样,所以新数组为 [1, 2, 3, 3, 4, 5, 6, 7, 9, 10] 。下标从 le = 3 到 ri = 4 的和为 3 + 3 = 6

示例 3:

输入: nums = [1,2,3,4], n = 4, left = 1, right = 10
输出: 50

解题思路:因为数组是由一些正整数组成的,所以比如,区间和满足[0,0]<[0,1] < [0,2]...,[1,1] < [1.2] < [1,3]...,所以我们可以将区间和维护成n和数组,然后利用归并函数的并来排序,n个数组分别是比如[0,0][1,1][2,2]开头,比如n1 = [[0,0],[0,1],[0,2]...],n2 = [[1,1],[1,2],[1,3]...],代码如下:

var rangeSum = function(nums, n, left, right) {
    //维护小顶堆的数组
    let data = [];
    //维护小顶堆特性的方法
    let h = heap;
    //分别n呢数组的头元素入队,并记录位置,i代表的是第几个数组,j是第几个位置
    for(let i = 0; i < n; i ++){
        data = h.push_up({sum:nums[i],i:i,j:i},data,'min');
    }
    //记录排序后的left-right的区间和
     let sum = 0;
     //保存小顶堆最小的元素
    let d = {}
    for(let i = 1; i <= right; i ++){
        d = data[0];
        //删除left之前的元素,
        data = h.pop_down(data,'min');
        //当是left-right元素直接就累计求区间和
        if(i >= left)sum += d.sum;
        //获得当前最小值的头元素,减该数组的下一位加入数组求最小值
        if(d.j + 1 < n) data = h.push_up({sum: d.sum + nums[d.j + 1],i:d.i,j:d.j + 1},data,'min');
    }
    return sum % 1000000007;
};
//优先队列之前的文章有讲过,这里就不解释了
let heap = {
    push_up(val,data,type){
        data.push(val);
        let idx = data.length - 1;//当前节点
        let gIdx = parseInt((idx - 1) / 2);//根节点
        while(idx && this.CMP(idx,gIdx,data,type)){
            data = this.swap(data[idx],idx,data[gIdx],gIdx,data);
            idx = gIdx;
            gIdx = parseInt((idx - 1)/2);
        }
        return data;
    },
    pop_down(data,type){
        if(data.length <= 1){
            data = [];
            return data;
        }
        let cnt = data.length;
        //删除最大值,将尾结点放在开头,位置树结构
        data[0] = data.pop();
        cnt --;
        let idx = 0;//当前节点
        let n = cnt -1;//最大节点个数
        let zIdx = 2 * idx + 1;//左子节点
        while(zIdx <= n){
            let temp = idx;//三角区最大值下标
            if(this.CMP(zIdx,idx,data,type)) temp = zIdx;
            if(zIdx + 1 <= n && this.CMP(zIdx + 1,temp,data,type)) temp = zIdx + 1;
            if(temp == idx) break;
            data = this.swap(data[idx],idx,data[temp],temp,data);
            idx = temp;
            zIdx = 2*idx +1;
        }
        return data;
    },
    swap(v1,i1,v2,i2,data){
        data[i1] = v2;
        data[i2] = v1;
        return data;
    },
    CMP(val,val2,data,type){
        if(type == 'max'){
            if(data[val].sum > data[val2].sum) return true;
            else return false
        }else{
            if(data[val].sum < data[val2].sum) return true;
            else return false
        }
    }
}