LeetCode 1755. 最接近目标值的子序列和

给你一个整数数组 nums 和一个目标值 goal 。

你需要从 nums 中选出一个子序列,使子序列元素总和最接近 goal 。也就是说,如果子序列元素和为 sum ,你需要 最小化绝对差 abs(sum - goal) 。

返回 abs(sum - goal) 可能的 最小值 。

注意,数组的子序列是通过移除原始数组中的某些元素(可能全部或无)而形成的数组。

示例 1:

输入:nums = [5,-7,3,5], goal = 6
输出:0
解释:选择整个数组作为选出的子序列,元素和为 6 。
子序列和与目标值相等,所以绝对差为 0 。
示例 2:

输入:nums = [7,-9,15,-2], goal = -5
输出:1
解释:选出子序列 [7,-9,-2] ,元素和为 -4 。
绝对差为 abs(-4 - (-5)) = abs(1) = 1 ,是可能的最小值。
示例 3:

输入:nums = [1,2,3], goal = -7
输出:7
 

提示:

1 <= nums.length <= 40
-107 <= nums[i] <= 107
-109 <= goal <= 109

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/closest-subsequence-sum
 

解法

折半查找,状态压缩, 双指针/二分。求最接近目标的子序列和,数组nums的最大长度为40,所以应该用折半查找+状态压缩。将数组nums分为两部分A和B,分别在A与B中利用状态压缩枚举,求子序列和ASum与BSum。那么答案可能存在于:

1)ASum中的某一元素

2)BSum中的某一元素

3)ASum中的某一元素+Bsum中的某一元素

得到ASum与Bsum后,求解答案就变为了  “给定两个数组,两个数组中分别取一个元素,使得它们的和尽可能接近目标值”。可以通过双指针或者二分求解。

其中状态压缩枚举子集和:

比如数组A有n个元素,那么我们可以枚举出(1<<n)个子集,从小到大枚举子集,其中第i位为1表示取下标为i的数组元素。

class Solution {
public:
    int minAbsDifference(vector<int>& nums, int goal) {
        int n=nums.size();
        int len1=n/2+n%2, len2=n-len1;
        auto getSum=[&](int x, int start, int len, vector<int> &arr)
        {
            for(int i=0;i<len;++i)
            {
                if((x&1<<i)==0)
                    continue;
                arr[x]=arr[x^(1<<i)]+nums[start+i]; //可以通过前面已经计算过的子集转移而来,避免重复计算
                break;            //有一个就行了,不break的话,效率差别很大
            }
            return;
        };
        vector<int> left(1<<len1);
        int ans=INT_MAX;
        for(int i=0;i<1<<len1;++i)
        {
            getSum(i, 0, len1, left);
            ans=min(ans, abs(goal-left[i]));
        }
        sort(left.begin(), left.end());

        vector<int> right(1<<len2);
        for(int i=0;i<(1<<len2);++i)
        {
            getSum(i, len1, len2, right);
            ans=min(ans, abs(goal-right[i]));
        }
        sort(right.begin(), right.end());
        int l=0, r=(1<<len2)-1;    
        while(l<(1<<len1)&&r>=0)            //双指针找元素
        {
            int temp=left[l]+right[r]-goal;
            ans=min(ans, abs(temp));
            if(temp>0)
                --r;
            else 
                ++l;
        }

        return ans;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值