美文网首页
1755. 最接近目标值的子序列和

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

作者: 来到了没有知识的荒原 | 来源:发表于2021-09-03 20:39 被阅读0次

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

选出一个子集,子集的和满足某个要求,用状态压缩dp。
当n过大时,比如本题的n <=40,可以把数组nums划分成左右两部分,left和right,分别求子集和。
答案就在:

  1. left构成的一个子集和中
  2. right构成的一个子集和中
  3. left中选择一个,right中选择一个,两个的和是目标结果

其中第3条可以用双指针解决。

这样问题的复杂度从O(n *2^n)降低至O(n * 2^{n/2} )
因为求left子集和时,需要O(n/2 * 2^{n/2} )
求right子集和时,需要O(n/2 * 2^{n/2} )
最后双指针需要O(2^{n/2} + 2^{n/2} )
最终复杂度为O(n * 2^{n/2} )

高度相似题:805. 数组的均值分割,还是本题简单一点,我是看了1755才会的805

class Solution {
 public:
  int minAbsDifference(vector<int>& nums, int goal) {
    int n = nums.size();
    vector<int> left, right;
    for (int i = 0; i < n / 2; i++) left.push_back(nums[i]);
    for (int i = n / 2; i < n; i++) right.push_back(nums[i]);

    int n1 = left.size(), n2 = right.size();
    int lsum[1 << n1], rsum[1 << n2];
    memset(lsum, 0, sizeof lsum);
    memset(rsum, 0, sizeof rsum);
    for (int i = 0; i < 1 << n1; i++)
      for (int j = 0; j < n1; j++)
        if (i & (1 << j)) lsum[i] = lsum[i ^ (1 << j)] + left[j];
    for (int i = 0; i < 1 << n2; i++)
      for (int j = 0; j < n2; j++)
        if (i & (1 << j)) rsum[i] = rsum[i ^ (1 << j)] + right[j];

    int res = INT_MAX;
    for (int i = 0; i < n1; i++) res = min(res, abs(lsum[i] - goal));
    for (int i = 0; i < n2; i++) res = min(res, abs(rsum[i] - goal));

    sort(lsum, lsum + (1 << n1));
    sort(rsum, rsum + (1 << n2));

    int i = 0, j = (1 << n2) - 1;
    while (i < (1 << n1) && j >= 0) {
      int s = lsum[i] + rsum[j];
      res = min(res, abs(goal - s));
      if (s > goal) j--;
      else i++;
    }
    return res;
  }
};

相关文章

网友评论

      本文标题:1755. 最接近目标值的子序列和

      本文链接:https://www.haomeiwen.com/subject/ujzkwltx.html