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
}
}
}