周赛:将 x 减到 0 的最小操作数

将 x 减到 0 的最小操作数


给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。

如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。

示例 1:
输入:nums = [1,1,4,2,3], x = 5
输出:2
解释:最佳解决方案是移除后两个元素,将 x 减到 0 。

示例 2:
输入:nums = [5,6,7,8,9], x = 4
输出:-1

示例 3:
输入:nums = [3,2,20,1,1,3], x = 10
输出:5
解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 104
  • 1 <= x <= 109

这道题直接能想到的解法是模拟,但是数据量太大了,模拟肯定会超时。那么有没有什么优化的方法?

  1. 一种是利用前缀和,因为是从头删除和从尾删除,而且是连续的数组,符合前缀和的特征。
  2. 第二种方法,倒过来思考,我们不选择删除,而是用滑动窗口找出中间最长的序列,这样就是删除最少的。

前缀和

从前删除和从后删除可以分为前缀和、后缀和。前缀和+后缀和=x。所以在已知x和后缀和的时候,只需要判断前缀和里面是否存在x-后缀和。

class Solution {
    public int minOperations(int[] nums, int x) {
        HashMap<Integer,Integer> lr = new HashMap();
        int len = nums.length;
        int k = 0;
        int res = Integer.MAX_VALUE;
        // 存储前缀和
        for (int i = 0; i < len; i++) {
            k += nums[i];
            if (k == x) {
                // 不需要后缀,前缀就能满足
                res = i + 1;
            }
            lr.put(k,i);
        }
        int sum = k;
        k = 0;
        // 遍历后缀
        for (int i = len -1; i >= 0; i--) {
            k += nums[i];
            int t = x - k;
            if (t == 0) {
                // 不需要前缀和,自己就满足
                res = Math.min(res,len - i);
                continue;
            }
            if (lr.containsKey(t)) {
                // 含有另一半
                int v = lr.get(t);
                if (v < i) {
                    // 如果另一半的index在自己右边是不行的
                    // 测试样例[1,1]3
                    res = Math.min(res,lr.get(t) + 1+(len - i));
                }
                
            }
        }
        return res == Integer.MAX_VALUE ? -1 : res;
    }
}

滑动窗口

首先找到一个大于等于sum-x的滑动窗口,然后让左窗口向后移动,如果依然满足sum-x,那么就说明这个窗口满足条件,判断一次;如果不满足sum-x,说明当前窗口小了,需要移动右窗口直到满足sum-x的状态。

class Solution {
    public int minOperations(int[] nums, int x) {
        int sum = 0;
        int len = nums.length;
        // 计算出数组总和
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }
        int l = 0, r = 0;
        int res = Integer.MAX_VALUE;
        int times = 0;
        while (l < len) {
        // 调整右窗口大小
            while (r < len) {
                if (times + x >= sum) {
                    break;
                }
                times += nums[r];
                r++;
            }
            if (times + x == sum) {
                res = Math.min(res,len - (r - l));
            }
            // 左窗口要向右移动,减去nums[l]
            times -= nums[l];
            l++;
            if (r < l) {
                break;
            }
        }
        if (res == Integer.MAX_VALUE) {
            return -1;
        }
        return res;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值