牛客网答题笔记---数字游戏

题目描述

小易邀请你玩一个数字游戏,小易给你一系列的整数。你们俩使用这些整数玩游戏。每次小易会任意说一个数字出来,然后你需要从这一系列数字中选取一部分出来让它们的和等于小易所说的数字。
例如: 如果{2,1,2,7}是你有的一系列数,小易说的数字是11.你可以得到方案2+2+7 = 11.如果顽皮的小易想坑你,他说的数字是6,那么你没有办法拼凑出和为6 现在小易给你n个数,让你找出无法从n个数中选取部分求和的数字中的最小数。

输入描述:

输入第一行为数字个数n (n ≤ 20)
第二行为n个数xi (1 ≤ xi ≤ 100000)

输出描述:

输出最小不能由n个数选取求和组成的数

输入例子:

3
5 1 2

输出例子:

4

题目分析

  1. 首先,我们假设{2,1,2,7}为例子,要求:无法从这n个数中选取某一部分数字求和的最小数,涉及到这种数组的最小或最大,最好先给数组排个序,sort之后,就是{1,2,2,7}
  2. 然后我们根据题意,找出最小的那个无法从n个数中抽数求和的值,那必然是这些所有的和中某一个的值+1
  3. 那我们就从1开始,设定out = 0,第一次判断res[0] > out+1,是不是比1还大,大的话就直接break了,因为直接1,你就没法从原数组中拿出数来构造,如果大于不大于1(因为数字都是大于0的,所以顶多也就是个1,之后继续判断)
  4. 因为前面的数判断了,(在这到题)是能构造出来的,那就加上它,out += res[0]。继续上面的判断是否当前的res[i]大于累加的out+1,如果是大于,那么说明我之前所有的数加起来(在这个[1~out+1]范围之内能被构造的数都有了,but依然)都不能赶上这个数out+1,而后面的那个数肯定也是大于这个out+1(因为当前的res[i]就大于了,所有想让后面的数作出被加的贡献是不可能的了),也就说明这是我们要找的数,那个无法从n个数中抽出某些数求和的数。

代码

大概理清以上的思路,代码也挺简单的:

解法1:

#include<vector>
#include<algorithm>
#include<iostream>
using namespace std;

int main(){
    int n;
    while(cin>>n){
        vector<int> res(n);
        for(int i = 0;i < n;i++)
            cin>>res[i];
        sort(res.begin(),res.end());
        int out = 0;
        for(int i = 0;i < n;i++){
            if(res[i] > out+1)
                break;
            out += res[i];    
        }
        cout<<out+1<<endl;

    }
    return 0;
}

python代码:

//注:在牛客上提交的时候,一样的代码老是提示block错误,最后也不知道错在哪儿了,只能删了代码重新敲一遍,这时候好了.....
while True:
    try:
        n=int(raw_input())
        a=map(int,raw_input().split())
        a.sort()
        out = 0 
        for i in range(n):
            if a[i] > out+1:
                break
            out += a[i]
        print out+1
    except:
        break

解法二.

在讨论区看见的一种转换成背包问题的解法,代码和思路都贴在下面了,有时间可以思考下:

//原作者:smallx
import java.util.*;

/**
 * 背包问题的一种,本质为"若num小于不可解的最小数,那么1,2,3...num都是可解的"。
 *
 * 思路如下:
 *
 * 将给定的数据集nums从大到小排序。我们要判断命题"num小于不可解的最小数"是否成立。
 * 我们将num看作背包,然后从nums中拿出一个最大的值v,如果num中能够放得下就放进去,
 * 如果放进去后刚好满了,则num可解,命题成立,如果不满继续迭代;如果v放不进去背包中了,
 * 那么背包剩下的容量构成一个更小的子问题(<num),并且如果想要命题成立,那么该子问题
 * 必定可解,并且解必定由v后边的数字序列构成(已从大到小排序)。
 */
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            int n = scanner.nextInt();
            int[] nums = new int[n];
            for(int i=0; i<n; i++)
                nums[i] = scanner.nextInt();

            Arrays.sort(nums);
            long num = 1;
            while (true) {
                long sum = 0;
                for(int i=n-1; i>=0 && sum!=num; i--) {
                    if(nums[i] + sum <= num)
                        sum += nums[i];
                }
                if (sum != num) {
                    System.out.println(num);
                    break;
                }
                num++;
            }
        }
    }
}

补充

1.故事缘由

今天在一次面试的时候面试官老师聊到最后,说在我博客里挑一道题,让我给他讲明白就让我过,然后就选到了这道题。其实当时还是觉得挺有谱的,但是一上手,有点慌了,有些地方确实没有完完全全理清,导致代码写得七七八八,自己都有点模糊了,更别说给面试老师讲明白了,囧 …….

2.题目意思

所以回来再重新理一下这道题目的思路,必须完完全全弄明白:

这到题目意思是:给出一些数字,然后我们找出一个数字k,使得用数组里任意的数字组合都没法拼凑成这个数字k,并且这个k要最小。

我们先来找规律:
假定:res [4] = 2,1,2,7
我们给它排序一次得到:1,2,2,7

我们从其中找规律:
    到下标为0的时候,1能直接构造出来,[1,1]
    到下标为1的时候,1,2,3都能构造出来,也就是[1,1+2]
    到下标为2的时候,1,2,3,4,5都能构造出来,也就是[1,1+2+2]
    到下标为3的时候, 由于前者最多只能构造到5,所以这里最多能构造到[1,5] && [7,7+5],也就是说,6以及大于12的数都构造不出来

    这里的6便是我们要找的k。

找到这个规律,我们可以得出一个结论:
一个排序过的数组,假设当前下标所在位为i,那么sum = res[0] + res[1] + res[2] …. + res[i] ,用其中任意的数字来组合出的数的取值范围是[ res[0] , sum ],也就是这中间的数都能被构造出来(任意几个数字加起来即可)。

也就是说:假设前i项能构造出来的数是[res[0] , sum],也就是res[0] 到 sum 这之间的所有数字;这时候再加上res[i+1],那么就将范围扩大到了[ res[0] , sum ] 和 [ res[i+1] , res[i+1] + sum]
如果res[i+1] <= sum,那么这两者就可以合并到一起[ res[0] , sum + res[i+1]]
如果res[i+1] > sum ,那么说明这中间有无法构造出来的一些数,也就是在( sum , res[i+1] )之间,既然存在,那么最小的那个一定是sum + 1。

这就得到我们想要的东西,k一定是sum+1。所以我们也就可以开始写代码了:

#include<vector>
#include<algorithm>
int get_min_k(vector<int>& input){
    sort(input.begin(),input.end());
    int answer = 0;
    for(int i = 0;i < input.size();i++){
        if(input[i] > answer + 1)
            break;
        answer += input[i];
    }
    return answer+1;         //返回sum+1,得到最小的k 
}

3.结语

就像今天遇到的面试官老师说的,学习应该是一个稳扎稳打的过程,应该自己有足够深刻的理解,再来写总结,而不是模模糊糊就胡乱拼凑过去的。
往后的日子里,我会多对自己博客上写过的文章进行二次的理解和思考,来验证自己是否真正明白了某个问题。再不断写自己的补充和新的想法,学习之路还很远,走稳脚下的每一步。加油!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值