题目
一个数组的 分数 定义为数组之和 乘以 数组的长度。
比方说,[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; };
扩展阅读
视频课程
有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快
速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关下载
想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653
。也就是我们常说的专业的人做专业的事。 |
|如果程序是一条龙,那算法就是他的是睛|
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境:
VS2022 C++17