最小化差題目
折半枚舉 + 二分查找
總和體積小的話,可以轉為01包問題
DP
1755. 最接近目標值的子序列和
題意
給你一個整數數組 nums 和一個目標值 goal 。
你需要從 nums 中選出一個子序列,使子序列元素總和最接近 goal 。也就是說,如果子序列元素和為 sum ,你需要 最小化絕對差 abs(sum - goal)
題解
(折半枚舉,二分查找) \(O\left(n 2^ \frac{n}{2}\right)\),
- 枚舉前半個數組所有組合的值,記錄在數組 \(q\)中。
- 將 \(q\) 數組從小到大排序。
- 枚舉另一半數組,對於某個組合 \(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。
題解
可以分析到:
可以在紙上模擬一下,就會發現最后的結果可以表示為:
轉化為該題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];
}
};