uacs2024

导航

leetcode 1658. 将 x 减到 0 的最小操作数

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;
    }
};

 

posted on 2024-12-11 16:09  ᶜʸᵃⁿ  阅读(21)  评论(0)    收藏  举报