[路飞]_leetcode-1508-子数组和排序后的区间和

简介: leetcode-1508-子数组和排序后的区间和

网络异常,图片无法展示
|


[题目地址][B站地址]


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


请你返回在新数组中下标为 **leftright  (下标从 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
复制代码


提示:


  • 1 <= nums.length <= 10^3
  • nums.length == n
  • 1 <= nums[i] <= 100
  • 1 <= left <= right <= n * (n + 1) / 2


暴力解


解题思路


暴力解也是最简单直接的思路,采用双层循环获取所有的子数组和值插入一个和值数组。


对和值数组进行升序排序。


获取区间中的子数组和值之和,并对 10^9 + 7 取模后返回即可。


代码实现


var rangeSum = function(nums, n, left, right) {
  // 初始化子数组和数组
  const sums = []
  // 双层遍历,获取所有子数组和
  for(let i = 0;i<n;i++){
    let sum = nums[i];
    sums.push(sum);
    for(let j = i+1;j<n;j++){
      sum += nums[j];
      sums.push(sum)
    }
  }
  // 子数组和升序排序
  sums.sort((a,b) => a-b)
  // 累加区间和
  let res = 0;
  for(let i = left-1;i<right;i++){
    res += sums[i]
  }
  // 返回区间和取模结果
  return res%1000000007
};
复制代码


小顶堆+多路归并


解题思路


首先我们看一下以 1,2,3,4 为例双层循环求所有子数组和的过程。


网络异常,图片无法展示
|


我们暴力解中的外层循环其实就是每一行的第一个元素,内层循环就是每一行的后续元素。


暴力解的效率低是因为不管我们要求的 leftright 区间是哪里,都会找到所有的子数组和,而且是无序的,还有对子数组和数组进行升序排序。


这里我们采用小顶堆+多路合并的技巧,实现只需要获取前 right 个元素,并且保证获取到的子数组和是从小到大的。


具体思路如下:


我们将上图每一行看做一路,初始的时候将每一路的第一个值插入小顶堆,那么此时堆顶就保存了其中的最小值。 接下来我们从 1-right 进行循环,循环过程中每次弹出堆顶元素,因为整体逻辑是多路合并且保存在小顶堆中,那么此时弹出的堆顶元素,必然是未处理元素中的最小值。如果此时循环处理 left-right 区间中,则结果值累加当前元素保存的子数组和值,并且根据当前元素,推导所在路的下一个子数组和,插入堆中。


这样每次用堆中的最小值推导所在路的下一个元素,就保证了每次弹出的堆顶元素都是未处理元素中的最小值,同时也可以获取到所有的子数组和。


最后当 i 大于 right 的时候,结果值中就存储了 left-right 区间中的子数组和的和值,将其对 10^9 + 7 取模后返回即可。


代码实现


var rangeSum = function(nums, n, left, right) {
  // 创建小顶堆实例
  const heap = new Heap()
  // 首先将每一路的第一个值放入堆中
  for(let i = 0; i < n; i++) heap.push({i,j:i,sum:nums[i]})
  // 初始化结果值
  let res = 0
  // 从 1-right 遍历,获取前 right 个子数组和值
  for(let i = 1; i <= right; i++) {
    // 获取当前未处理的子数组和值的最小值
    const min = heap.pop()
    // 如果当前处于 left-right 区间,则累加结果值
    if(i >= left) res += min.sum
    // 根据当前元素,推导所在路的下一个元素,插入堆中
    if(min.j<n-1) heap.push({i:min.i, j:min.j + 1, sum:min.sum + nums[min.j + 1]})
  }
  // 返回结果
  return res%1000000007
};
// 小顶堆
class Heap {
  constructor() {
    // 初始化数组和size
    this.arr = [];
    this.size = 0;
  }
  // 插入操作
  push(item) {
    this.arr.push(item);
    this.size++;
    // 插入后的自下向上平衡调整
    if(this.size>1){
      let cur = this.size-1,
      parent = (cur-1)>>1;
      while(cur>0 && this.arr[parent].sum>this.arr[cur].sum){
        [this.arr[parent],this.arr[cur]] = [this.arr[cur],this.arr[parent]]
        cur = parent,parent = (cur-1)>>1;
      }
    }
  }
  // 弹出操作
  pop() {
    // 特殊处理弹出最后一个元素
    if(this.arr.length===1){
      this.size--;
      return this.arr.pop()
    }
    const res = this.arr[0];
    this.arr[0] = this.arr.pop();
    this.size--;
    // 弹出后的自上向下平衡调整
    let cur = 0,childl = 1,childr = 2;
    while(
      (childl<this.size && this.arr[childl].sum<this.arr[cur].sum) ||
      (childr<this.size && this.arr[childr].sum<this.arr[cur].sum)
    ){
      if(childr<this.size && this.arr[childr].sum<this.arr[childl].sum){
        [this.arr[cur],this.arr[childr]] = [this.arr[childr],this.arr[cur]]
        cur = childr
      }else{
        [this.arr[cur],this.arr[childl]] = [this.arr[childl],this.arr[cur]]
        cur = childl
      }
      childl = cur*2+1,childr = cur*2+2;
    }
    return res;
  }
}
复制代码


至此我们就完成了 leetcode-1508-子数组和排序后的区间和


如有任何问题或建议,欢迎留言讨论!

相关文章
|
4月前
【LeetCode-每日一题】 删除排序数组中的重复项
【LeetCode-每日一题】 删除排序数组中的重复项
37 4
|
4月前
|
算法
Leetcode第57题(插入区间)
LeetCode第57题“插入区间”的解题方法,包括题目描述、示例、算法思路和代码实现,旨在解决将新区间插入有序且不重叠的区间列表中,并合并重叠区间的问题。
37 0
Leetcode第57题(插入区间)
|
4月前
|
索引
Leetcode第三十三题(搜索旋转排序数组)
这篇文章介绍了解决LeetCode第33题“搜索旋转排序数组”的方法,该问题要求在旋转过的升序数组中找到给定目标值的索引,如果存在则返回索引,否则返回-1,文章提供了一个时间复杂度为O(logn)的二分搜索算法实现。
40 0
Leetcode第三十三题(搜索旋转排序数组)
|
6月前
|
存储 算法
LeetCode第83题删除排序链表中的重复元素
文章介绍了LeetCode第83题"删除排序链表中的重复元素"的解法,使用双指针技术在原链表上原地删除重复元素,提供了一种时间和空间效率都较高的解决方案。
LeetCode第83题删除排序链表中的重复元素
|
6月前
|
算法
LeetCode第57题插入区间
LeetCode第57题"插入区间"的解题方法,利用原区间集有序的特性,通过三步插入操作,有效实现了新区间的插入和重叠区间的合并。
LeetCode第57题插入区间
|
6月前
|
算法 索引
LeetCode第34题在排序数组中查找元素的第一个和最后一个位置
这篇文章介绍了LeetCode第34题"在排序数组中查找元素的第一个和最后一个位置"的解题方法,通过使用双指针法从数组两端向中间同时查找目标值,有效地找到了目标值的首次和最后一次出现的索引位置。
LeetCode第34题在排序数组中查找元素的第一个和最后一个位置
|
6月前
|
算法 Python
【Leetcode刷题Python】子数组查找
一个用于寻找给定字符串中最长重复子串的Python函数实现,采用了滑动窗口的方法来检测重复的子串。
30 1
|
6月前
|
算法 Java
LeetCode初级算法题:子数组最大平均数+二叉树的最小深度+最长连续递增序列+柠檬水找零
LeetCode初级算法题:子数组最大平均数+二叉树的最小深度+最长连续递增序列+柠檬水找零
59 0
|
6月前
|
存储 算法 Java
LeetCode初级算法题:反转链表+统计N以内的素数+删除排序数组中的重复项Java详解
LeetCode初级算法题:反转链表+统计N以内的素数+删除排序数组中的重复项Java详解
58 0
|
6月前
|
算法 索引 Python
【Leetcode刷题Python】34. 在排序数组中查找元素的第一个和最后一个位置(二分查找)
解决LeetCode "在排序数组中查找元素的第一个和最后一个位置" 问题的方法。第一种方法是使用两次二分查找,首先找到目标值的最左边界,然后找到最右边界。第二种方法是利用Python的list.index()方法,先正序找到起始位置,再逆序找到结束位置,并给出了两种方法的Python实现代码。
84 0