程式設計之美 陣列分割問題

2021-08-10 07:17:00 字數 2607 閱讀 4574

一,問題:

1. 有乙個無序、元素個數為2n的正整數陣列,要求:如何能把這個陣列分割為兩個子陣列,子陣列的元素個數不限,並使兩個子陣列之和最接近。

2. 有乙個無序、元素個數為2n的正整數陣列,要求:如何能把這個陣列分割為元素個數為n的兩個陣列,並使兩個子陣列之和最接近。

二,分析:

假設陣列a[1..2n]所有元素的和是sum。模仿動態規劃解0-1揹包問題的策略,令s(k, i)表示前k個元素中任意i個元素的和的集合。顯然:

s(k, 1) =

s(k, k) =

s(k, i) = s(k-1, i) u

按照這個遞推公式來計算,最後找出集合s(2n, n)中與sum最接近的那個和,這便是答案。這個演算法的時間複雜度是o(2^n).

因為這個過程中只關注和不大於sum/2的那個子陣列的和。所以集合中重複的和以及大於sum/2的和都是沒有意義的。把這些沒有意義的和剔除掉,剩下的 有意義的和的個數最多就是sum/2個。所以,我們不需要記錄s(2n,n)中都有哪些和,只需要從sum/2到1遍歷一次,逐個詢問這個值是不是在 s(2n,n)中出現,第乙個出現的值就是答案。我們的程式不需要按照上述遞推公式計算每個集合,只需要為每個集合設乙個標誌陣列,標記sum/2到1這 個區間中的哪些值可以被計算出來。

二, 解法:

由於對兩個子陣列和最接近的判斷不太直觀,我們需要對題目進行適當轉化。我們知道當乙個子陣列之和最接近原陣列之和sum的一半時,兩個子陣列之和是最接近的。所以轉化後的題目是:從2n個數中選出任意個數,其和盡量接近於給定值sum/2

這個問題儲存的是從前k個數中選取任意個數,且其和為s的取法是否存在dp[k][s]。之所以將選出的數之和放在下標中,而不是作為dp[k]的值,是因為那種做法不滿足動態規劃的前提——最優化原理,假設我們找到最優解有k個數p1p2...pk(選出的這k個數之和是最接近sum/2的),但最優解的前k-1個數p1p2...pk-1之和可能並不是最接近sum/2的,也就是說可能在訪問到pk之前有另一組數q1q2....qk-1其和相比p1p2...pk-1之和會更接近sum/2,即最優解的子問題並不是最優的,所以不滿足最優化原理。因此我們需要將dp[k]的值作為下標儲存起來,將這個最優問題轉化為判定問題,用帶動態規劃的思想的遞推法來解。

外階段:在前k1個數中進行選擇,k1=1,2...2*n。

內階段:從這k1個數中任意選出k2個數,k2=1,2...k1。

狀態:這k2個數的和為s,s=1,2...sum/2。

決策:決定這k2個數的和有兩種決策,乙個是這k2個數中包含第k1個數,另乙個是不包含第k1個數。

dp[k][s]表示從前k個數中取任意個數,且這些數之和為s的取法是否存在。

[html]view plain

copy

#include   

#include   

using namespace std;  

#define maxn 101  

#define maxsum 100000  

int a[maxn];  

bool dp[maxn][maxsum];  

// dp[k][s]表示從前k個數中去任意個數,且這些數之和為s的取法是否存在  

int main()  

}  // 之前的dp[k][s]表示從前k個數中取任意k個數,經過下面的步驟後  

// 即表示從前k個數中取任意個數  

for (k1=2; k1<=2*n; k1++)  

for (s=1; s<=sum/2; s++)  

if (dp[k1-1][s])  

dp[k1][s]=true;  

// 確定最接近的給定值sum/2的和  

for (s=sum/2; s>=1 && !dp[2*n][s]; s--)  

;  printf("the differece between two sub array is %d\n", sum-2*s);  

}  

2. 解法:

但本題還增加了乙個限制條件,即選出的物體數必須為n,這個條件限制了內階段k2的取值範圍,並且dp[k][s]的含義也發生變化。這裡的dp[k][s]表示從前k個數中取任意不超過n的k個數,且這些數之和為s的取法是否存在

[sql]view plain

copy

#include 

#include 

using namespace std;  

#define maxn 101  

#define maxsum 100000  

int a[maxn];  

bool dp[maxn][maxsum];  

// 題目可轉換為從2n個數中選出n個數,其和盡量接近於給定值sum/2  

int main()    

注意:如果陣列中有負數的話,上面的揹包策略就不能使用了(因為第三重迴圈中的s是作為陣列的下標的,不能出現負數的),需要將陣列中的所有陣列都加上最小的那個負數的絕對值,將陣列中的元素全部都增加一定的範圍,全部轉化為正數,然後再使用上面的揹包策略就可以解決了。

程式設計之美 陣列分割問題

原文 一,問題 1.有乙個無序 元素個數為2n的正整數陣列,要求 如何能把這個陣列分割為兩個子陣列,子陣列的元素個數不限,並使兩個子陣列之和最接近。2.有乙個無序 元素個數為2n的正整數陣列,要求 如何能把這個陣列分割為元素個數為n的兩個陣列,並使兩個子陣列之和最接近。二,分析 假設陣列a 1.2n...

程式設計之美 陣列分割

問題1 有乙個無序 元素個數為n的正整數陣列,要求 如何能把這個陣列分割為兩個子陣列,子陣列的元素個數不限,並使兩個子陣列之和最接近。解答 int sum 0 for i 0 i0 j 從最大的開始遍歷,是為了防止同乙個數選取多次 for j sum 2 j if dp j break return...

程式設計之美 陣列分割

程式設計之美 陣列分割 和擴充套件 將乙個陣列劃分成兩個子陣列,要求他們的和最接近 1.長度要求相等的情況 2.長度沒有要求的情況 都能用動態規劃解決 include includeusing namespace std void arraysplit1 int a,int n 兩個子陣列長度要求相...