如何從一堆數中選出若干個數,使其和等於給定的數?

如題,好比有一堆數: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);
    }
相關文章
相關標籤/搜索