1. 程式人生 > 其它 >leetcode 1755. 最接近目標值的子序列和

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

題目描述

https://leetcode-cn.com/problems/closest-subsequence-sum/

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

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

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

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

示例

輸入:nums = [5,-7,3,5], goal = 6
輸出:0
解釋:選擇整個陣列作為選出的子序列,元素和為 6 。子序列和與目標值相等,所以絕對差為 0 。

資料範圍

1 <= nums.length <= 40
-10^7 <= nums[i] <= 10^7
-10^9 <= goal <= 10^9

思路

由於nums的長度為不超過40,因此首先想到能否基於狀態壓縮法來遍歷所有的子序列,逐一進行計算。在一個數組中選出一個子序列,每個元素只有“選”或者“不選”兩種狀態,所有元素都被給予一個狀態即可得到一個子序列。長度為40的陣列即可將所有元素的狀態資訊壓縮排一個64位的整數中。然而,子序列最多有2^40種情況,為10^12量級,顯然會超時。

於是我們考慮是否能降低計算複雜度。由於超時是陣列的長度導致的,所以考慮能否在更小的規模上運用上述思路。我們發現,如果將陣列分為左右兩部分來考慮,那麼原陣列上的一個子序列就成為兩個子陣列上的子序列。顯然,原陣列上的一個狀態與子陣列上的一對狀態是一一對應的。因此,我們可以放心地遍歷子陣列上的全部狀態,而不會漏掉原陣列上的任何一個狀態或者引入其它不存在的狀態。

上述方式在計算狀態時減小了問題的規模,顯然我們不能再根據子陣列上的狀態一個一個把完整的狀態拼湊出來,否則前面的問題分解就沒有意義了。我們的目標是從兩個陣列中各選出一個狀態,使得完整的子序列的元素和與目標的“距離”最小。我們使用基於兩個排序陣列的雙指標法。設兩個升序排列的陣列為a、b。令p指向a的第一個元素,q指向b的最後一個元素。在每一次迭代中,如果兩個元素之和大於目標值,就左移q;否則右移p。稍後給出這一方法的正確性證明。

程式碼

int minAbsDifference(vector<int>& nums, int goal) {
    int n = nums.size();

    int lsz = n / 2, rsz = n - lsz;
    vector<int> left_sum(1 << lsz, 0), right_sum(1 << rsz, 0);
    for (int state = 0; state < (1 << lsz); ++state) {
        for (int i = 0; i < lsz; ++i) {
            if (state & (1 << i)) {
                left_sum[state] += nums[i];
            }
        }
    }
    for (int state = 0; state < (1 << rsz); ++state) {
        for (int i = 0; i < rsz; ++i) {
            if (state & (1 << i)) {
                right_sum[state] += nums[lsz + i];
            }
        }
    }

    sort(left_sum.begin(), left_sum.end());
    sort(right_sum.begin(), right_sum.end());
    
    int res = INT_MAX;
    int p = 0, q = right_sum.size() - 1;
    while (p < left_sum.size() && q >= 0) {
        int tmp = left_sum[p] + right_sum[q];
        res = min(res, abs(tmp - goal));
        tmp >= goal ? --q : ++p;
    }

    return res;
}

雙指標法的正確性證明

記陣列\(a\)的元素為\(a_1, a_2, ..., a_s\),陣列\(b\)的元素為\(b_1,b_2,...,b_t\)

反證法:若迭代過程中沒有計算過最優的元素對,且最優的元素對為\(a_i,b_j\),不妨設\(a_i,b_j\geq goal\)。考慮\(p\)指向\(a_i\)\(q\)的位置。顯然,\(q\)一定位於\(b_j\)左邊的位置。否則,根據迭代演算法,在\(p\)保持不動的情況下\(q\)一定會從\(b_j\)右側走到\(b_j\)。設某一次迭代時\(q\)指向\(b_{j'}\),顯然,\(a_i+b_{j'}<goal\),否則\(a_i,b_j\)將不是最優解。考慮迭代過程,要達到此狀態,要麼是\(p\)右移一位,要麼是\(q\)左移一位。如果是\(q\)左移一位,則有\(a_i+b_{j'+1}>=goal\),但\(j'+1<j\),與\(a_i,b_j\)是最優解矛盾。因此,是由\(p\)右移一位轉移到此狀態的。於是考慮上一個狀態:\(p\)指向\(i-1\)\(q\)指向\(j'\)。顯然\(a_{i-1}+b_{j'}<goal\),類似地,可以推知這個狀態也是由\(p\)右移而來的。按同樣的方法往前推,直到\(p\)指向1,\(q\)指向\(j'\),但這一狀態無法再由\(p\)右移一位或者是\(q\)左移一位得到,矛盾。

舉一反三