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某个元素和
  1. 以上划分后怎么处理?
  • 直接遍历
  • 直接遍历
  • 抽象为经典双指针问题:给定两个数组,怎么在两个数组中各挑出一个整数,使它们的和尽可能接近目标值

代码

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。另外指针移动的策略是和>值,右指针向左,如果和<值,左指针向右

posted @ 2022-10-27 15:27  zhangk1988  阅读(88)  评论(0)    收藏  举报