二分查找|前缀和|滑动窗口|2302:统计得分小于 K 的子数组数目

简介: 二分查找|前缀和|滑动窗口|2302:统计得分小于 K 的子数组数目

作者推荐

贪心算法LeetCode2071:你可以安排的最多任务数目

本文涉及的基础知识点

二分查找算法合集

题目

一个数组的 分数 定义为数组之和 乘以 数组的长度。

比方说,[1, 2, 3, 4, 5] 的分数为 (1 + 2 + 3 + 4 + 5) * 5 = 75 。

给你一个正整数数组 nums 和一个整数 k ,请你返回 nums 中分数 严格小于 k 的 非空整数子数组数目。

子数组 是数组中的一个连续元素序列。

示例 1:

输入:nums = [2,1,4,3,5], k = 10

输出:6

解释:

有 6 个子数组的分数小于 10 :

  • [2] 分数为 2 * 1 = 2 。
  • [1] 分数为 1 * 1 = 1 。
  • [4] 分数为 4 * 1 = 4 。
  • [3] 分数为 3 * 1 = 3 。
  • [5] 分数为 5 * 1 = 5 。
  • [2,1] 分数为 (2 + 1) * 2 = 6 。
    注意,子数组 [1,4] 和 [4,3,5] 不符合要求,因为它们的分数分别为 10 和 36,但我们要求子数组的分数严格小于 10 。
    示例 2:
    输入:nums = [1,1,1], k = 5
    输出:5
    解释:
    除了 [1,1,1] 以外每个子数组分数都小于 5 。
    [1,1,1] 分数为 (1 + 1 + 1) * 3 = 9 ,大于 5 。
    所以总共有 5 个子数组得分小于 5 。
    参数范围
    1 <= nums.length <= 105
    1 <= nums[i] <= 105
    1 <= k <= 1015

二分查找、前缀和

代码

时间复杂度

O(nlogn)。枚举子数组起点,时间复杂度O(n);二分查找终点:时间复杂度O(logn)。

原理

寻找最后一个符合以下条件的vNumRight,故用左闭右开空间。vNumRight的取值范围是[vNumLeft,m_c],换成左闭右开空间是[vNumLeft,m_c+1)。nums[vNumLeft,vNumRight) 就是要判断的子数组。

核心代码

class Solution {
public:
  long long countSubarrays(vector<int>& nums, long long k) {
    m_c = nums.size();
    vector<long long> vPreSum = { 0 };
    for (const auto& n : nums)
    {
      vPreSum.emplace_back(vPreSum.back() + n);
    }
    long long llRet = 0;
    for (int vNumLeft = 0; vNumLeft < m_c; vNumLeft++)
    {
      //求最大的vNumRight,使得nums[vNumLeft,vNumRight)的得分小于k。左闭右开
      int left = vNumLeft, right = m_c + 1;
      while (right - left > 1)
      {
        const auto mid = left + (right - left) / 2;
        if ((vPreSum[mid] - vPreSum[vNumLeft])*(mid-vNumLeft) < k)
        {
          left = mid;
        }
        else
        {
          right = mid;
        }
      }
      llRet += left - vNumLeft;
    }
    return llRet;
  }
  int m_c;
};

测试用例

template
void Assert(const vector& v1, const vector& v2)
{
if (v1.size() != v2.size())
{
assert(false);
return;
}
for (int i = 0; i < v1.size(); i++)
{
assert(v1[i] == v2[i]);
}
}
template
void Assert(const T& t1, const T& t2)
{
assert(t1 == t2);
}
int main()
{
vector nums;
long long k,res;
{
Solution slu;
nums = { 2, 1, 4, 3, 5 }, k = 10;
auto res = slu.countSubarrays(nums, k);
Assert(6LL, res);
}
{
Solution slu;
nums = { 1,1,1 }, k =5;
auto res = slu.countSubarrays(nums, k);
Assert(5LL, res);
}
{
Solution slu;
nums = { 9,5,3,8,4,7,2,7,4,5,4,9,1,4,8,10,8,10,4,7 }, k = 4;
auto res = slu.countSubarrays(nums, k);
Assert(3LL, res);
}
//CConsole::Out(res);
}

滑动窗口

时间复杂度O(n)

由于是正整数,所以子数组起点相同,终点越大,积分越大。相同的left,right不断增大。left增大,right不变或变大。left循环O(n)次,right也是。right总共循环了o(n),每次都是接着上次的right开始,没有重新开始。

代码

class Solution {
public:
  long long countSubarrays(vector<int>& nums, long long k) {
    m_c = nums.size();
    long long llSum = 0;
    long long llRet = 0;
    for (int left = 0,right=0; left < m_c; left++)
    {
      //子数组nums[left,right)符合要求,且right是当前left的最大值
      while ((right < m_c) && ((llSum+nums[right]) * (right+1 - left) < k))
      {
        llSum += nums[right++];
      }
      llRet += right - left;
      llSum -= nums[left];
    } 
    return llRet;
  }
  int m_c;
};


。也就是我们常说的专业的人做专业的事。 |

|如果程序是一条龙,那算法就是他的是睛|

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境:

VS2022 C++17

相关文章
|
10月前
|
算法 测试技术 C#
C++前缀和算法的应用:统计中位数为 K 的子数组
C++前缀和算法的应用:统计中位数为 K 的子数组
|
12月前
|
算法
【算法专题突破】滑动窗口 - 长度最小的子数组(9)
【算法专题突破】滑动窗口 - 长度最小的子数组(9)
37 0
|
10月前
|
算法 测试技术 C#
C++前缀和算法的应用:统计得分小于K的子数组数目
C++前缀和算法的应用:统计得分小于K的子数组数目
|
2月前
|
算法
【算法】滑动窗口——长度最小的子数组
【算法】滑动窗口——长度最小的子数组
|
5月前
|
算法 测试技术 C#
【对顶队列】【中位数贪心】【前缀和】3086. 拾起 K 个 1 需要的最少行动次数
【对顶队列】【中位数贪心】【前缀和】3086. 拾起 K 个 1 需要的最少行动次数
|
3月前
|
vr&ar C++
1695. 删除子数组的最大得分(C++,滑动窗口)
1695. 删除子数组的最大得分(C++,滑动窗口)
|
5月前
|
算法 测试技术 C#
【线段树】2276. 统计区间中的整数数目
【线段树】2276. 统计区间中的整数数目
|
5月前
|
算法 测试技术 C#
二分查找|前缀和|滑动窗口|2302:统计得分小于 K 的子数组数目
二分查找|前缀和|滑动窗口|2302:统计得分小于 K 的子数组数目
|
5月前
|
算法 测试技术 C#
LeetCode2444: 统计定界子数组的数目
LeetCode2444: 统计定界子数组的数目
|
5月前
|
算法 测试技术 C#
C++双指针算法:统计点对的数目
C++双指针算法:统计点对的数目