1755. 最接近目标值的子序列和
题目描述
给一个整数数组nums,数组长度在40以下
再给一个目标值goal
问怎么能在数组中选一个子序列,让子序列的和最接近值goal?
| f1-折半+状态压缩+动态规划 |
基本分析
1.40这个长度怎么看,直接用状态压缩行不行?子集个数是\(O(2^n)\), 每个子集需要O(n)去找一个子集中的非0元素,总复杂度是\(O(n \cdot 2^n)\),对40这个长度,明显不行
2.怎么缩减规模?分治思想,将原来数组划分为左半和右半,分别枚举子集和lsum、rsum。原数组的一个子序列和,一定是3者之一
- lsum某个元素
- rsum的某个元素
- lsum某个元素 与 rsum某个元素和
- 以上划分后怎么处理?
- 直接遍历
- 直接遍历
- 抽象为经典双指针问题:给定两个数组,怎么在两个数组中各挑出一个整数,使它们的和尽可能接近目标值
代码
class Solution:
def minAbsDifference(self, nums: List[int], goal: int) -> int:
n = len(nums)
harf = n//2
ls, rs = harf, n-harf
# 求左半组合
lsum = [0] * (1<<ls)
for i in range(1, 1<<ls):
for j in range(ls):
if i & (1<<j):
lsum[i] = lsum[i-(1<<j)] + nums[j]
break
# 求右半组合
rsum = [0] * (1<<rs)
for i in range(1, 1<<rs):
for j in range(rs):
if i & (1<<j):
rsum[i] = rsum[i-(1<<j)] + nums[ls+j]
break
ans = inf
for v in lsum:
ans = ans if ans < abs(v-goal) else abs(v-goal)
for v in rsum:
ans = ans if ans < abs(v-goal) else abs(v-goal)
lsum.sort()
rsum.sort()
# 求左右混合
l, r = 0, (1<<rs)-1
while l < (1<<ls) and r >= 0:
s = lsum[l] + rsum[r]
ans = min(ans, abs(s-goal))
if s > goal:
r -= 1
elif s < goal:
l += 1
else:
break
return ans
复杂度
时间:求解lsum及rsum分别要\(O(n \cdot 2^{n/2})\), 排序要\(O(n \cdot log(2^{n/2}))\), 双指针要\(O(2^{n/2})\)
空间:放枚举的子集和需要\(O(2^{n/2})\)
总结
题目转化:这个题的特点在于不用枚举子集,但是因为n=40,需要有分治的想法,把题目转化成常规的状压dp+双指针经典问题
双指针经典问题:注意要先排序。对l、r的范围注意是range(1<<ls), range(1<<rs),不是ls和rs。另外指针移动的策略是和>值,右指针向左,如果和<值,左指针向右

浙公网安备 33010602011771号