leetcode 1658. 将 x 减到 0 的最小操作数
其实就是个不定长的滑动窗口
暴力超时解🤡
class Solution {
public:
int sum(int left,int right,vector<int>& nums,int size){
int res = 0;
if(left <= right){
for(int i = left;i <= right;++i) res += nums[i];
}
else{
for(int i = 0;i <= right;++i) res += nums[i];
for(int i = left;i <= size-1;++i) res += nums[i];
}
return res;
}
int minOperations(vector<int>& nums, int x) {
int size = nums.size(),res = -1;
for(int i = 1;i <= size;++i){
int left = 0,right = left + i - 1;
while(right >= -1){
if(sum((left+size)%size,(right+size)%size,nums,size) == x){
if(res == -1) return i;
}
--left;--right;
}
}
return -1;
}
};
优化了一下,还是超时了🤡
class Solution {
public:
int minOperations(vector<int>& nums, int x) {
int size = nums.size(),nowSum = 0;
for(int i = 1;i <= size;++i){
nowSum += nums[i-1];
if(nowSum == x) return i;
if(i == size) return -1;//如果全都加上了还不满足,直接返回-1
int temp = nowSum;
int left = 0,right = i - 1;
while(right >= -1){
/*
if(right < 0) temp -= nums[right+size];
else temp -= nums[right];
if(left - 1 < 0) temp += nums[left-1+size];
else temp += nums[left-1];*/
temp = temp - nums[(right+size)%size] + nums[(left-1+size)%size];//两种都超时了
if(temp == x) return i;
--left;--right;
}
}
return -1;
}
};
法一:逆向思维
class Solution {
public:
//把问题转换成「从 nums 中移除一个最长的连续子数组,使得nums剩余元素的和为 x」。
//换句话说,要从nums中找最长的连续子数组,其元素和等于 s−x,这里 s 为 nums 所有元素之和。
//这种首尾拿取的题目没有思路就逆向思维做,就可能做出来了
int minOperations(vector<int>& nums, int x) {
int target = accumulate(nums.begin(),nums.end(),0) - x;
if(target < 0) return -1;
int size = nums.size();
if(target == 0) return size;
int res = -1,left = 0,sum = 0;
for(int right = 0;right < size;++right){
sum += nums[right];
while(sum > target) sum -= nums[left++];//直接用while判断,不需要弄个if
if(sum == target) res = max(res,right-left+1);
}
return res == -1 ? -1 : size - res;
}
};
法二:双指针直接做
class Solution {
public:
int minOperations(vector<int>& nums, int x) {
int sum = 0; // 用于记录当前处理的子数组的和
int size = nums.size(); // 数组的大小
int right = size; // 指向数组末尾的指针,用于从后向前计算最长满足条件的后缀
// 计算从数组末尾开始的最长后缀,其和不超过 x
while(right >= 1 && sum + nums[right-1] <= x)
sum += nums[--right];
// 如果整个数组的和都小于 x,则无法通过移除元素达到目标
if(right == 0 && sum < x) return -1;
int res; // 存储最终的最小操作次数
// 如果最长后缀的和正好等于 x,则直接返回需要移除的元素个数(即数组大小减去后缀长度)
if(sum == x) res = size - right;
else // 如果不等于 x,则先假设无法仅通过移除后缀达到目标,设置一个较大的初始值
res = size + 1;
// 从左到右遍历数组,尝试找到满足条件的最小子数组
for(int left = 0; left < size; ++left){
sum += nums[left]; // 加上当前左指针指向的元素
// 如果当前的前缀和后缀之和超过了 x,则尝试通过移动右指针来缩小后缀长度
while(right < size && sum > x)
sum -= nums[right++];
// 如果调整后仍然超过 x,则说明当前的前缀过长,无法形成满足条件的子数组
if(sum > x) break;
// 如果当前的前缀和后缀之和正好等于 x,则更新最小操作次数
if(sum == x)
res = min(res, left + 1 + size - right); // 操作次数为前缀长度+后缀长度(注意要加上 1,因为左右指针之间有一个未计算的元素)
}
// 如果最小操作次数大于数组大小,说明无法通过移除连续子数组达到目标
return res > size ? -1 : res;
}
};