1. 问题描述:
给你一个整数数组 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 <= 10^5
1 <= nums[i] <= 10 ^ 4
1 <= x <= 10 ^ 9
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-operations-to-reduce-x-to-zero
2. 思路分析:
① 分析题目可以知道这道题目本质上是一道区间和的问题,也就是我们需要求解出数组中头部与尾部各自连续的区间和为某个常数x的最小的长度,这个在题目中是最容易看出来的,因为是区间和的问题所以比较容易想到的思路是是前缀和,我们可以使用循环计算出数组中所有索引对应的前缀和与后缀和,因为数组长度最大为10^5,
为了尽可能减少时间复杂度所以借助哈希表来存储数组中所有索引对应的前缀和与后缀和,这样在前缀和与后缀和进行匹配的时候速度会快一些,因为使用的是python语言,所以使用字典来存储对应的前缀和与后缀和,键表示前缀和(后缀和),值表示对应的索引
② 将前缀和和后缀和存储到字典之后我们就可以对前缀和与后缀和进行组合看是否能够形成前缀和 + 后缀和 == x的头部与尾部区间,基于这个想法我们可以遍历前缀和字典,检查x - key1在后缀和字典中是否存在假如存在说明是可以头部与尾部连续区间相加是可以构成目标数字x的,并且还需要一个限制条件就是前缀和索引应该小于后缀和索引的时候才决定是否更新答案,整个思路还是挺好理解的
③ 除了上面说到的第一种思路之外,在力扣的题解中发现一个更棒的思路,思路值得学习学习,我的理解如下:分析题目可以知道我们需要求解头部与尾部和为x的最短区间长度,所以可以转化为求解在数组nums中和为sum(nums) - x的最大长度,也就是求解数组中间某一段连续区间和为sum(nums) - x的最大长度l,然后我们使用len(nums) - l就为求解的答案,下图中可以看出我们需要求解的是中间红色区间的最大长度,我们在验证数组中的区间和是否等于某一个数字的时候可以借助于哈希表的方式来寻找,可以使用当前位置的前缀和减去某一个数字x看字典是否存在假如存在说明这一段区间是满足条件的
前缀和 + 哈希表检验一个区间和是否等于x的方法很有效,方法是使用一个字典然后借助于相减的方式检查字典中是否存在,假如存在那么更新相应的变量值,假如不存在那么将当前遍历的值存入到字典中,而且时间复杂度比较低,当在遇到类似的题目的时候可以考虑使用一下这个思路
下面是一个例子:nums = [1,1,4,2,3], x = 5,下图是pycharm编辑器debug模式的截图,可以看到当index =3的时候,这个时候前缀和等于6 - 6 = 0(第一个6表示在pre数组中0到index = 3这个位置的前缀和,第二个6表示我们需要查找的和为6的区间),这个时候0在字典中是存在的,相减的值在字典中存在说明这一段区间是满足条件的,这个时候更新答案(1 + 1 + 4 = 6==>说明剩余的元素的和就为5)
3. 代码如下:
前缀和 + 后缀和 + 字典:
import collections
import sys
from typing import List
class Solution:
def minOperations(self, nums: List[int], x: int) -> int:
nums = [0] + nums + [0]
n = len(nums)
sum1, sum2 = 0, 0
pre_dic, suff_dic = collections.defaultdict(int), collections.defaultdict(int)
for i in range(n):
sum1 += nums[i]
sum2 += nums[n - i - 1]
# 将前缀和与后缀和的下标存储在字典中
pre_dic[sum1] = i
suff_dic[sum2] = n - i - 1
res = sys.maxsize
for key1, value1 in pre_dic.items():
# 检查后缀字典中是否存在这样的值使得前缀和 + 后缀和 == x
if x - key1 in suff_dic and value1 < suff_dic[x - key1]:
res = min(res, value1 + n - suff_dic[x - key1] - 1)
return -1 if res == sys.maxsize else res
前缀和 + 字典优化:
import collections
from typing import List
class Solution:
def minOperations(self, nums: List[int], x: int) -> int:
sum_nums = sum(nums)
# 假如数组元素总和等于x说明需要全部元素才可以构成目标数字x
if sum_nums == x: return len(nums)
# 我们在数组中找的连续的区间和为t = sum - x, 并且所有的区间和为t的长度l应该是最大的, 计算这一段连续数字的和然后使用数组总的长度减去len(nums) - l
last = sum_nums - x
pre = [0]
# 将前缀和存储在列表中方便后面的计算某一个区间的和
for i in range(len(nums)):
pre.append(pre[-1] + nums[i])
res = float("-inf")
# 使用字典来检查是否存在和为sum - x的区间
dic = collections.defaultdict(int)
for index, value in enumerate(pre):
if value - last in dic:
res = max(res, index - dic[value - last])
if value not in dic:
dic[value] = index
return -1 if res == float("-inf") else len(nums) - res