最小化差題目(最接近目標值的子序列和、將數組分成兩個數組並最小化數組和的差 最后一塊石頭的重量 II)


最小化差題目

折半枚舉 + 二分查找

總和體積小的話,可以轉為01包問題

DP

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

題意

給你一個整數數組 nums 和一個目標值 goal 。

你需要從 nums 中選出一個子序列,使子序列元素總和最接近 goal 。也就是說,如果子序列元素和為 sum ,你需要 最小化絕對差 abs(sum - goal)

題解

(折半枚舉,二分查找) \(O\left(n 2^ \frac{n}{2}\right)\),

  1. 枚舉前半個數組所有組合的值,記錄在數組 \(q\)中。
  2. \(q\) 數組從小到大排序。
  3. 枚舉另一半數組,對於某個組合 \(t\), 在另一個數組中二分查找 goal \(-t\) 。找到第一個大於等於 \(g o a l-t\) 的位置 \(l\), 用 \(l\)\(l-1\) 兩個位置的元蛪更新答案。

時間復雜度

  • 預處理並排序 \(h\) 數組的時間為 \(O\left(2^{\frac{n}{2}} \log \left(2^{\frac{n}{2}}\right)\right)=O\left(n 2^{\frac{n}{2}}\right)\) \(\$_{0}\)
  • 對於另一半數組的每個組合,需要 \(O\left(\log \left(2^{\frac{n}{2}}\right)\right)\) 的時間二分 查詢。
  • 故總時間復雜度為 \(O\left(n 2^{\frac{n}{2}}\right)\)

枚舉可以分為二進制枚舉和dfs

二進制枚舉子集寫法

class Solution {
public:
    int minAbsDifference(vector<int>& nums, int goal) {
        const int n = nums.size();
        const int m = n >> 1;

        vector<int> h;
        for (int s = 0; s < (1 << m); s++) {
            int t = 0;
            for (int i = 0; i < m; i++)
                if (s & (1 << i))
                    t += nums[i];
            h.push_back(t);
        }
        sort(h.begin(), h.end());

        int ans = INT_MAX;
        for (int s = 0; s < (1 << (n - m)); s++) {
            int t = goal;
            for (int i = m; i < n; i++)
                if (s & (1 << (i - m)))
                    t -= nums[i];

            int l = 0, r = h.size() - 1;
            while (l < r) {
                int mid = (l + r) >> 1;
                if (h[mid] < t) l = mid + 1;
                else r = mid;
            }

            if (ans > abs(h[l] - t))
                ans = abs(h[l] - t);

            if (l < h.size() - 1 && ans > abs(h[l + 1] - t))
                ans = abs(h[l + 1] - t);
        }

        return ans;
    }
};

dfs寫法

const int N = 1100010;
int q[N];
class Solution {
public:
    int n, cnt, goal, ans;
    void dfs1(vector<int>& nums, int u, int s)
    {
        if(u == n / 2)
        {
            q[cnt ++] = s;   
            return ;
        }
        dfs1(nums, u + 1, s);
        dfs1(nums, u + 1, s + nums[u]);
    }
    void dfs2(vector<int>& nums, int u, int s)
    {
        if(u == n)
        {
            int l = 0, r = cnt - 1;
            while(l < r)
            {
                int mid = l + r + 1 >> 1;
                if(q[mid] + s <= goal) l = mid;
                else r = mid - 1;
            }
            ans = min(ans, abs(q[l] + s - goal));
            if(l + 1 < cnt)
                ans = min(ans, abs(q[l + 1] + s - goal));
            return ;
        }
        dfs2(nums, u + 1, s);
        dfs2(nums, u + 1,s + nums[u]);
    }
    int minAbsDifference(vector<int>& nums, int _goal) {
        n = nums.size(), goal = _goal, cnt = 0, ans = INT_MAX;
        dfs1(nums, 0, 0);
        sort(q, q + cnt);
        dfs2(nums, n / 2, 0);

        return ans;
    }
};

2035. 將數組分成兩個數組並最小化數組和的差

題意

給你一個長度為 2 * n 的整數數組。你需要將 nums 分成 兩個 長度為 n 的數組,分別求出兩個數組的和,並 最小化 兩個數組和之 差的絕對值 。nums 中每個元素都需要放入兩個數組之一。

請你返回 最小 的數組和之差。

題解

根上題一樣,對於前n個數,采用二進制或者dfs枚舉,開一個二維的vector保存,即q[cnt],表示前n個數選取cnt個數和的所有結果。后n個數,采用二進制或者dfs枚舉,假設枚舉選取了cnt1個數,在q[n - cnt1]二分選取結果即可。

dfs寫法

class Solution {
public:
    int s = 0, res = INT_MAX;
    vector<vector<int> > q;
    void dfs1(vector<int>& nums, int u, int sum, int num){
        if(u == (nums.size()>>1)){
            q[num].push_back(sum);
            return;
        }
        dfs1(nums, u + 1, sum, num);
        dfs1(nums, u + 1, sum + nums[u], num + 1);
    }
    void dfs2(vector<int>& nums, int u, int sum, int num){
        if(u == nums.size()){
            
            int cnt = nums.size() / 2 - num;
            int l = 0, r = q[cnt].size() - 1;
            while(l < r){
                int mid = (l + r) >> 1;
                if(sum + q[cnt][mid] >= s/2) r = mid;
                else l = mid + 1;
            }
           // cout<<l<<" "<<cnt<<" "<<num<<" "<<q[cnt][l]<<" "<<sum<<endl;
            if(l >= 0 && l < q[cnt].size())res = min(res, abs(s - 2 * (sum + q[cnt][l])));
            if(l >= 1) res = min(res, abs(s - 2 * (sum + q[cnt][l - 1])));
            return;
        }
        dfs2(nums, u + 1, sum, num);
        dfs2(nums, u + 1, sum + nums[u], num + 1);
    }
    int minimumDifference(vector<int>& nums) {
        for(auto x : nums) s += x;
        int n = nums.size() /2;
        q = vector<vector<int> >(n + 1);
        dfs1(nums, 0, 0, 0);
        for(int i = 1; i <= n; i++)sort(q[i].begin(), q[i].end());
        dfs2(nums, n, 0, 0);
        return res;
    }
};

二進制枚舉子集寫法

class Solution {
public:
    int minimumDifference(vector<int>& nums) {
        int n = nums.size();
        n /= 2;
        vector<vector<int>> q(n+1);
        for(int i = 0; i < 1 << n; i++){
            int cnt = 0, sum = 0;
            int S = 0;
            for(int j = 0; j < n; j++) S += nums[j+n];
            for(int j = 0; j < n; j++){
                if(i >> j & 1){
                    cnt ++ ;
                    sum += nums[j+n];
                }
            }
            q[cnt].push_back(sum - (S-sum));
        }
        int ans = 1e9;
        for(int i = 0; i <= n; i++) sort(q[i].begin(), q[i].end());
        for(int i = 0; i < 1 << n; i++){
            int cnt = 0, sum = 0;
            int S = 0;
            for(int j = 0; j < n; j++) S += nums[j];
            for(int j = 0; j < n; j++){
                if(i >> j & 1){
                    cnt ++ ;
                    sum += nums[j];
                }
            }
            cnt = n - cnt;
            sum = (S-sum)- sum;
            auto t = lower_bound(q[cnt].begin(), q[cnt].end(), sum);
            if(t != q[cnt].end()) ans = min(ans, abs(sum - *t));
            if(t != q[cnt].begin()) ans = min(ans, abs(sum - *prev(t)));
        }
        return ans;
    }
};

1049. 最后一塊石頭的重量 II

題意

有一堆石頭,用整數數組 stones 表示。其中 stones[i] 表示第 i 塊石頭的重量。

每一回合,從中選出任意兩塊石頭,然后將它們一起粉碎。假設石頭的重量分別為 x 和 y,且 x <= y。那么粉碎的可能結果如下:

如果 x == y,那么兩塊石頭都會被完全粉碎;
如果 x != y,那么重量為 x 的石頭將會完全粉碎,而重量為 y 的石頭新重量為 y-x。
最后,最多只會剩下一塊 石頭。返回此石頭 最小的可能重量 。如果沒有石頭剩下,就返回 0。

題解

可以分析到:

可以在紙上模擬一下,就會發現最后的結果可以表示為:

\[\sum_{i=0}^{n-1} k_{i} \times \text { stonesi }, \quad k_{i} \in\{-1,1\} \]

轉化為該題494. 目標和從 stones數組中選擇,湊成總和不超過sum/2的最大價值。

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int n = stones.size();
        vector<int> f(30010, 0);
        int sum = 0;
        for(int x : stones) sum += x;
        int m = sum / 2;
        for(int i = 0; i < n; i++){
            for(int j = m; j >= stones[i]; j--){
                f[j] = max(f[j], f[j - stones[i]] + stones[i]);
            }
        }
        return sum - f[m] -f[m];
    }
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM