题目
给你一个整数数组 nums 和一个整数 k ,找出 nums 中和至少为 k 的 最短非空子数组 ,并返回该子数组的长度。如果不存在这样的 子数组 ,返回 -1 。
子数组 是数组中 连续 的一部分。
示例 1:
输入:nums = [1], k = 1
输出:1
示例 2:
输入:nums = [1,2], k = 4
输出:-1
示例 3:
输入:nums = [2,-1,2], k = 3
输出:3
提示:
1 <= nums.length <= 105
-105 <= nums[i] <= 105
1 <= k <= 10^9
#分析
时间复杂度
枚举子数组的结尾i,时间复杂度O(n),利用二分查找,每次枚举O(logn),故总时间复杂度是O(nlogn)。
细节
llSun是num[0,i]的和,vSumIndex 记录[0,j]之和,j取值[-1,i)。假定j0 < j1,且sum[j0] >= sum[j1],那sum[j0,i]小于sum[j1,i]且j0的长度大于j1,所以j0一定不是备选答案,可淘汰。淘汰后如果j0<j1,则sum[j0]一定小于sum[j1]。也就是前缀和和索引都是按升
序排序。sum-sumold >=k ==> sum-k>=sumold ==>sumold <= sum-k 。淘汰的时候:由于是按升序排序,所以sum[j1]不大于等于sum-k,那么sum[j0]也一定不大于等于sum-k。所以找到一个不符合,就可停止了。
#核心代码
class Solution {
public:
int shortestSubarray(vector& nums, int k) {
m_c = nums.size();
m_vRet.assign(m_c, -1);
vector<pair<long long, int>> vSumIndex = { {0,-1} };
long long llSum = 0;
m_vRet.assign(m_c, INT_MAX);
for (int i = 0; i < m_c; i++)
{
llSum += nums[i];
//sum-sumold >=k ==> sum-k>=sumold ==>sumold <= sum-k
//由于sum和index都是升序,所以可以二分
auto it = std::upper_bound(vSumIndex.begin(), vSumIndex.end(), llSum - k, []( const long long ll,const pair<long, int>& pi)
{
return ll < pi.first;
});
if (vSumIndex.begin() != it)
{
m_vRet[i] = i - std::prev(it)->second;
}
while (vSumIndex.size() && (vSumIndex.back().first >= llSum))
{
vSumIndex.pop_back();
}
vSumIndex.emplace_back(llSum, i);
}
const int iRet = *std::min_element(m_vRet.begin(), m_vRet.end());
return (INT_MAX == iRet) ? -1 : iRet;
}
int m_c;
vector m_vRet;
};
测试用例
m_vRet是多余的,是为了方便排错。
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);
}
class Solution {
public:
int shortestSubarray(vector& nums, int k) {
m_c = nums.size();
m_vRet.assign(m_c, -1);
vector<pair<long long, int>> vSumIndex = { {0,-1} };
long long llSum = 0;
m_vRet.assign(m_c, INT_MAX);
for (int i = 0; i < m_c; i++)
{
llSum += nums[i];
//sum-sumold >=k ==> sum-k>=sumold ==>sumold <= sum-k
//由于sum和index都是升序,所以可以二分
auto it = std::upper_bound(vSumIndex.begin(), vSumIndex.end(), llSum - k, []( const long long ll,const pair<long, int>& pi)
{
return ll < pi.first;
});
if (vSumIndex.begin() != it)
{
m_vRet[i] = i - std::prev(it)->second;
}
while (vSumIndex.size() && (vSumIndex.back().first >= llSum))
{
vSumIndex.pop_back();
}
vSumIndex.emplace_back(llSum, i);
}
const int iRet = *std::min_element(m_vRet.begin(), m_vRet.end());
return (INT_MAX == iRet) ? -1 : iRet;
}
int m_c;
vector m_vRet;
};
错误做法
auto it = std::upper_bound(vSumIndex.begin(), vSumIndex.end(), std::make_pair(llSum - k,0));
我们期望:
返回第一个 first大于llSum-k的值。
实际上,返回第一个符合以下条件之一的迭代器:
一,first大于llSum-k
二,first等于llSum-k,second>0
解决方法:将0换成m_c,这样条件二,就永远不会成立。也可以换成INT_MAX。修改后的代码如下:
auto it = std::upper_bound(vSumIndex.begin(), vSumIndex.end(), std::make_pair(llSum - k,m_c));
2023年3月分的旧版
仅供参考
template
bool Less(const std::pair<Class1, int>& p, Class1 iData)
{
return p.first < iData;
}template
bool Greater(const std::pair<Class1, int>& p, Class1 iData)
{
return p.first > iData ;
}class Solution {
public:
int shortestSubarray(vector& nums, int k) {
int iMinLen = INT_MAX;
std::vector<std::pair<long, int>> vQue;
vQue.emplace_back(0, -1);
long long llSum = 0;
for (int i = 0; i < nums.size(); i++)
{
llSum += nums[i];
int iLessEqualNum = std::lower_bound(vQue.begin(), vQue.end(), llSum - k + 1, Less) - vQue.begin();
if (iLessEqualNum > 0 )
{
iMinLen = min(iMinLen, i - vQue[iLessEqualNum - 1].second);
}
while (vQue.size() && (llSum <= vQue.back().first))
{
vQue.pop_back();
}
vQue.emplace_back(llSum, i);
}
return (INT_MAX == iMinLen) ? -1 : iMinLen;
}
};
扩展阅读
相关下载
想高屋建瓴的学习算法,请下载《闻缺陷则喜算法册》doc版
鄙人想对大家说的话 |
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
墨家名称的来源:有所得以墨记之。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17