如何从一堆数中选出若干个数,使其和等于给定的数?

如题,比如有一堆数:13,2,4,2,4,8,7,8,6

要从中挑选出若干个数,使得它们的和等于32,挑选出来的数是:20,6,4,2

我是使用“试探”法来解这个题目,思路如下:

先对数进行排序:13,8,8,7,6,4,4,2,2

选出最大的数字,以及不大于目标数字后续数字,于是我挑选到了13,8,8,其和是29,如果这个时候再挑选7的话就会超过32,所以就跳过,尝试在后面找到合适的数字,找到4,加上仍然大于32,再接着找到2,这次好了,加起来是31。

再次向后面寻找小的数字的时候,发现没有合适的数字了。于是就“退回去”到最后一个选中的数字2那里,取消掉2的选择,选择下一个更小的数字:

但不幸的是仍然不符合要求,而且已经到底了,所以还要往前退,退到8,取消对8的选择,选择更小的数字7:

再尝试选择小于等于32的数字,6不符合,跳过,4,正好符合,13+8+7+4=32,挑选数字完成!

好,算法描述好了,如何用代码来实现?

这种不知道要循环多少次的问题最好还是用递归来处理,把这个问题简化成以下的问题:

具体代码见下:

public class CombineHelper {
        private readonly int[] _array;
        private readonly bool[] _chosenFlags;
        //排序,初始化
        public CombineHelper(IEnumerable<int> numbersToFetch) {
            Debug.Assert(numbersToFetch!=null);
            _array = numbersToFetch.OrderByDescending(i => i).ToArray();
            _chosenFlags = new bool[_array.Length];
        }
        //找组合
        public List<int> FindCombination(int value) {
            //初始化flags
            for (int i = 0; i < _chosenFlags.Length; i++) {
                _chosenFlags[i] = false;
            }
            if (Find(value, 0)) {
                //生成结果返回
                List<int> result = new List<int>();
                for (int i = 0; i < _chosenFlags.Length; i++) {
                    if (_chosenFlags[i]) {
                        result.Add(_array[i]);
                    }
                }
                return result;
            }
            throw new Exception("无法组合");
        }
        private bool Find(int value, int startIdx) {
            int i = startIdx;
            if (i == _array.Length) {
                return false;
            }
            //跳过
            if (_array[i] > value) {
                return Find(value, i + 1);
            }
            //匹配成功
            if (_array[i] == value) {
                _chosenFlags[i] = true;
                return true;
            }
            //_array[i] < value,尝试选择当前这个数并在后面的数中再去匹配余数
            for (int step = 0; i + step < _array.Length; step++) {
                if (Find(value - _array[i + step], i + step + 1)) {
                    _chosenFlags[i + step] = true;
                    return true;
                }
            }
            return false;
        }
    }

这段代码除开一些封装/初始化的部分之外,也没几行了,真正有用的就是Find方法,递归的代码就是简洁。用法示例:

    List<int> sourceList = new List<int> {13,2,4,2,4,8,7,8,6};
    CombineHelper combineHelper = new CombineHelper(sourceList);
    List<int> result = combineHelper.FindCombination(33);
    foreach (int i in result) {
        Console.WriteLine(i);
    }

原文地址:https://www.cnblogs.com/guogangj/p/8446861.html

时间: 2024-07-30 21:40:24

如何从一堆数中选出若干个数,使其和等于给定的数?的相关文章

一个组数中只有两个数只出现了一次,其他所有数都是成对出现的,找出这两个数

原题:给一组数,只有两个数只出现了一次,其他所有数都是成对出现的.怎么找出这两个数. 编写函数实现. 对于一组数中只有一个数只出现一次,其他所有数都是成对出现的,我们采用了对全部数组元素进行异或,经过分析发现异或全部数组的数所得到的数为整个数组中两个只出现一次数异或的结果先对所有的元素进行异或.可以通过将结果转换为二进制,在移位二进制数中的第一个1,然后根据这个1的判断条件将整个数组分为两组,分别对两个组的元素进行全部异或,则就找出两个不同的数. 例如:数组中的元素为下面这些数: 0000   

在已排序好的数组找两个数a加b等于给定的N

public class 在已排序好的数组找两个数a加b等于给定的N { public static void main(String[] args) { /** * 初始化参数 Result为结果值 * num 是测试数组 * start 开始游标, end 结束游标 */ int Result = 15; int[] num = {1,2,4,7,11,15}; int start = 0, end = num.length-1; //从数组的两端开始扫,若两数之和小于目标,则头往后进一位,

十进制数中1的个数

一.题目: 给定一个十进制的正整数,写下从1开始,到N的所有整数,然后数一下其中出现“1”的个数.要求: 1.写一个函数 f(N) ,返回1 到 N 之间出现的“1”的个数.例如 f(12)  = 5.2.在32位整数范围内,满足条件的“f(N) =N”的最大的N是多少. 二.思路: 这道题偏向数学的推理,其根本在于找规律,而规律则在于将所给的数进行分解,分成个十百千等位数的个体,再从每个中寻找规律.通过整理,每一位有多少个1只与其前后两位有关系,具体算法程序如下: 三.源程序 1 #inclu

(hdu step 4.3.3)Sum It Up(从n个数中选出m个数让他们的和达到指定和targetSum,输出所有的合法序列)

题目: Sum It Up Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 140 Accepted Submission(s): 73   Problem Description Given a specified total t and a list of n integers, find all distinct sums using

从给定的N个正数中选取若干个数之和为M

#include <iostream> #include <list> using namespace std; void find_seq(int sum, int index, int * value, list<int> & seq) { if(sum <= 0 || index < 0) return; if(sum == value[index]) { printf("%d ", value[index]); for(l

找出一组数中只出现一次的两个数,其他所有数都是成对出现的

题目: 给一组数,只有两个数只出现了一次,其他所有数都是成对出现的.怎么找出这两个数.编写函数实现. 题目分析: 上次介绍了,对于一组数中只有一个数只出现一次,其他所有数都是成对出现的,我们采用了对全部数组元素进行异或,但是对于找出两个出现一次的数应该怎么解决呢?先对所有的元素进行异或,则结果为两个出现一次的数的异或结果,然后将结果转换为二进制,找出二进制数中的第一个1,然后根据这个1的判断条件进行分组,分为两组,分别对两个组的元素进行全部异或,则就找出两个不同的数. 例如:数组中的元素为下面这

从数组中选出和等于固定值的n个数(JavaScript实现)

现实生活中的问题,可能会抽象为这样一种数据模型: 从一个数组中挑选出几个数,让这几个数相加的和为指定的值. 大多数读者应该有过网购的经历,网购一般会有个凑单功能,假如读者买了70元的商品,但是必须满100元才能包邮,这时系统会自动推荐一些商品,加起来差不多就100块钱了. 系统如何确定推荐哪些商品呢?这其实就是刚刚提到的模型,我们可以把热销商品的价格放到一个数组中,然后利用算法,找出数组中哪些价格的和为30元. 废话少说,小菜给大家分享一个JavaScript版本的算法实现. 算法代码: 1 f

【C语言】输入一个整数,输出该数二进制表示中1的个数(三种方法)

输入一个整数,输出该数二进制表示中1的个数.如输入32,输出1. 代码实现: 方法1:与运算 #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; int FindOneNumber(unsigned int num) {     int numberofOne = 0;     while (num)     {         num = num & (num - 1);         

巧妙使用Contains()方法查找一个数是否在某堆数中

问题:要判断用户输入的一个数,或者是程序里方法的一个参数的值,或者是一个变量的值是否在某堆数中. 简洁写法:把这堆数放在list中,使用list的Contains()方法检查list是否包含这个数,取反. 代码如下: static void Main(string[] args) { Console.WriteLine("请输入一个数字:"); string input=Console.ReadLine(); if (!(new List<string>() { "