作者:Grey
原文地址:陣列的最小不可組成和問題
題目說明
連結:https://www.nowcoder.com/questionTerminal/296c2c18037843a7b719cf4c9c0144e4
來源:牛客網給定一個全是正數的陣列arr,定義一下arr的最小不可組成和的概念:
1,arr的所有非空子集中,把每個子集內的所有元素加起來會出現很多的值,其中最小的記為min,最大的記為max;
2,在區間[min,max]上,如果有一些正數不可以被arr某一個子集相加得到,那麼這些正數中最小的那個,就是arr的最小不可組成和;
3,在區間[min,max]上,如果所有的數都可以被arr的某一個子集相加得到,那麼max+1是arr的最小不可組成和;
舉例: arr = {3,2,5}
arr的min為2,max為10,
在區間[2,10]上,4是不能被任何一個子集相加得到的值中最小的,所以4是arr的最小不可組成和;
arr = {3,2,4}
arr的min為2,max為9,
在區間[2,9]上,8是不能被任何一個子集相加得到的值中最小的,所以8是arr的最小不可組成和;
arr = {3,1,2} arr的min為1,max為6,
在區間[2,6]上,任何數都可以被某一個子集相加得到,所以7是arr的最小不可組成和;
請寫函式返回arr的最小不可組成和。
思路
首先我們設定兩個變數,max
和min
用於記錄陣列累加得到的最大值,和當陣列不為空累加得到的最小值。那麼在陣列非空狀態下,累加和一定在[min, max]
區間內。我們設定
boolean[][] dp = new boolean[arr.length][max + 1];
其中dp[i][j]
表示[0....i]
範圍內的元素任意累加,能否組成j這個累加和。
顯然有
// 0元素可以組成arr[0]這個累加和
dp[0][arr[0]] = true;
for (int i = 0; i < dp.length; i++) {
// 0..i上一個元素都不用,可以組成0這個累加和
dp[i][0] = true;
}
這樣我們得到dp
這個陣列第一行和第一列的情況。
然後我們可以推導普遍位置
dp[i][j] = dp[i - 1][j] || (j - arr[i] >= 0 && dp[i - 1][j - arr[i]]);
其含義為:
[0...i]
範圍內,任意選擇,能否組成j
這個累加和,其實包括了兩種情況:
情況1:[0...i-1]
範圍內,任意選擇,能否組成j
這個累加和,如果可以,說明dp[i][j]=true
情況2:[0...i-1]
範圍內,任意選擇,能否組成j-arr[i]
這個累加和(注意不能越界),如果可以,說明dp[i][j] = true
所以,普遍位置的求法如下
for (int i = 1; i < dp.length; i++) {
for (int j = 1; j < max + 1; j++) {
dp[i][j] = dp[i - 1][j] || (j - arr[i] >= 0 && dp[i - 1][j - arr[i]]);
}
}
經過上述處理,dp
已全部填好,接下來就是判斷dp
中第一個為false
的位置,即為答案
for (int i = min; i <= max; i++) {
if (!dp[arr.length - 1][i]) {
return i;
}
}
如果上述過程沒有找到,則返回max+1
,完整程式碼如下
public static int getFirstUnFormedNum(int[] arr) {
int min = arr[0];
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
max += arr[i];
min = Math.min(min, arr[i]);
}
// 可以到的範圍是[min,max]
// dp[i][j] 0....i能否組成j
boolean[][] dp = new boolean[arr.length][max + 1];
// 第0行 除了下述位置,其他位置都是false
dp[0][arr[0]] = true;
for (int i = 0; i < dp.length; i++) {
// 0..i上一個元素都不用,可以組成0這個累加和
dp[i][0] = true;
}
for (int i = 1; i < dp.length; i++) {
for (int j = 1; j < max + 1; j++) {
dp[i][j] = dp[i - 1][j] || (j - arr[i] >= 0 && dp[i - 1][j - arr[i]]);
}
}
for (int i = min; i <= max; i++) {
if (!dp[arr.length - 1][i]) {
return i;
}
}
return max + 1;
}
進階
連結:https://www.nowcoder.com/questionTerminal/a689a05f75ff4caaa129b1f971aeb71e
來源:牛客網給定一個正數陣列arr,其中所有的值都為整數,以下是最小不可組成和的概念
把arr每個子集內的所有元素加起來會出現很多值,其中最小的記為min,最大的記為max
在區間[min, max]上,如果有數不可以被arr某一個子集相加得到,那麼其中最小的那個數是arr的最小不可組成和
在區間[min, max]上,如果所有的數都可以被arr的某一個子集相加得到,那麼max+1是arr的最小不可組成和
請寫函式返回正數陣列arr的最小不可組成和
保證1一定出現過!
時間複雜度為O(n),額外空間複雜度為O(1)
主要思路:
如果一定有1這個數,那麼如果將這個正數陣列排序後,0位置上的值一定是1,設定一個變數range
,初始值為1,表示當前可以搞定的最小正整數的範圍,
接下來我們要通過遍歷整個陣列來擴充range
的範圍,假設[0.....i-1]
上可以讓range擴充到b
這個值,i
位置上的值假設是a
,如果
a <= b + 1
則遍歷到i
位置,可以讓range
的值擴充到a + b
,
如果
a > b + 1
則b + 1
就是整個陣列的最小不可組成和,可以直接返回。完整程式碼如下:
public static long unformedSum(long[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
Arrays.sort(arr);
long range = 1;
for (int i = 1; i != arr.length; i++) {
if (arr[i] > range + 1) {
return range + 1;
} else {
range += arr[i];
}
}
return range + 1;
}