正数数组的最小不可组成和

链接:正数数组的最小不可组成和__牛客网
来源:牛客网
 

给定一个正数数组arr,其中所有的值都为整数,以下是最小不可组成和的概念

  • 把arr每个子集内的所有元素加起来会出现很多值,其中最小的记为min,最大的记为max
  • 在区间[min, max]上,如果有数不可以被arr某一个子集相加得到,那么其中最小的那个数是arr的最小不可组成和
  • 在区间[min, max]上,如果所有的数都可以被arr的某一个子集相加得到,那么max+1是arr的最小不可组成和

请写函数返回正数数组arr的最小不可组成和

时间复杂度为O(n×∑i=1narri)O(n \times \sum_{i=1}^n arr_i)O(n×∑i=1n​arri​),额外空间复杂度为O(n×∑i=1narri)O(n \times \sum_{i=1}^n arr_i)O(n×∑i=1n​arri​)

输入描述:

第一行一个整数N,表示数组长度。

接下来一行N个整数表示数组内的元素。

输出描述:

输出一个整数表示数组的最小不可组成和

示例1

输入

3

2 3 9

输出

4

示例2

输入

3

1 2 4

输出

8

说明

3 = 1 + 2

5 = 1 + 4

6 = 2 + 4

7 = 1 + 2 + 4

备注:

1 <= N <= 100

整数数组最大和不超过10000

解析:

根据题目要求的输出,很容易可以想到使用动态规划的方法来解决问题。

首先,我们要确定动态规划所用的cnt数组的长度——max + 1,通过对整数数组进行一次遍历,我们可以很容易求出该数组的子集的最大和 max 与最小和 min 。

将整数数组arr的值依次放入cnt中进行运算。

在进行运算时,我们需注意以下几点:
1.将cnt数组看做一组背包的集合,cnt中每个元素的下标即为其背包的容量。

2.将整数数组arr的每个元素看做与其值对应大小的填充物。

3.尽可能大的填充cnt中的每个背包

4.整数数组arr中每个元素在每个背包最多使用一次。 

我们以示例2、3、9 做出如下表格: 

239
01234567891011121314
变量2002222222222222
变量3002335555555555
变量90023355559911121214

其中:

2 = 2

3 = 3 

5 = 2 + 3

9 = 9

11 = 2 + 9

12 = 3 + 9

14 = 2 + 3 + 9

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> arr(n);
    int max = 0;
    for (int& i : arr) {
        cin >> i;
        max += i;
    }
    sort(arr.begin(), arr.end());
    int min = arr[0];
    vector<int> cnt(max + 1, 0);
    //将arr中每个元素依序计算
    for (int i = 0; i < arr.size(); ++i) {
        //从最大值开始,向后遍历cnt数组,知道第arr[i]个元素
        //k >= arr[i] : cnt中每个元素下标即为其背包容量,
        //    k < arr[i]部分自然放不下大小arr[i]的元素。
        //--k : 由于循环内的判断方法,从后向前遍历是为了防止元素的重复放入。
        //    如,当arr[i] = 2时,如果正序遍历,在k = 2时,cnt[2] = 2。
        //    但在k = 4时,cnt[2] + arr[i] 大于 cnt[4] 且小于等于 4
        //    导致了2的重复使用。
        for (int k = max; k >= arr[i]; --k) {
            //cnt[k - arr[i]] : 从大小为k的背包中提前占用arr[i]的空间
            //    对于剩下的空间,我们可以直接在cnt中查询先前对它的尽可能填充后的值。
            //如果将arr[i]的物品放入背包,如果此时的值大于当前的值,则更新值。
            if (arr[i] + cnt[k - arr[i]] > cnt[k]) {
                cnt[k] = cnt[k - arr[i]] + arr[i];
            }
        }
    }
    for (int i = min; i < max; ++i) {
        if (i != cnt[i]) {
            cout << i << endl;
            return 0;
        }
    }
    cout << max + 1 << endl;
    return 0;
}

代码纯享版

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> arr(n);
    int max = 0;
    for (int& i : arr) {
        cin >> i;
        max += i;
    }
    sort(arr.begin(), arr.end());
    int min = arr[0];
    vector<int> cnt(max + 1, 0);
    for (int i = 0; i < arr.size(); ++i) {
        for (int k = arr[i]; k <= max; ++k) {
            if (arr[i] + cnt[k - arr[i]] > cnt[k]) {
                cnt[k] = arr[i] + cnt[k - arr[i]];
            }
        }
    }
    for (int i = min; i < max; ++i) {
        if (i != cnt[i]) {
            cout << i << endl;
            return 0;
        }
    }
    cout << max + 1 << endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云雷屯176

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值