有多少種硬幣組合——找出獨特子數組之和

寫在前面的自我反省

這道題的解法,剛開始我本身作的並不算是一個很好的解法,只能說題目是作出來了,但過程當中的計算有大量的重複計算,致使函數複雜度直逼O(n^n)。查閱資料以後便有了一個改良版。感謝這篇文章Find all distinct subset (or subsequence) sums of an array!javascript

背景

最近由於一些緣由,作了幾道「簡單」的算法題。今天要講的即是其中的一道題:若是你有一個硬幣數組和一個表明其數量的數組,如何獲得一共有多少種不一樣組合所得的和?java

分析

好比:硬幣數組[10, 50, 100],和表明其數量的數組[1, 2, 1]就表示這裏一共有三種硬幣,1枚10分,2枚50分和1枚100分硬幣。那麼其可能的排列組合則包括算法

  1. 10 = 10
  2. 50 = 50
  3. 100 = 100
  4. 10 + 50 = 60
  5. 10 + 100 = 110
  6. 50 + 50 = 100
  7. 50 + 100 = 150
  8. 10 + 50 + 50 = 110
  9. 10 + 50 + 100 = 160
  10. 50 + 50 + 100 = 200
  11. 10 + 50 + 50 + 100 = 210

則,不重複的值則包括加黑的部分,一共是9個。數組

而咱們如今的問題即是,輸入兩個數組:硬幣數組和表明其數量的數組,獲得一個整數的返回值。函數

實際操做

第一步 定義輸入與輸出

根據分析,函數的輸入與輸出應該規定以下。oop

/**
 * Count number of 
 * @param {Array<Number>} coins array contains coins with different values
 * @param {Array<Number>} counts array contains corresponding counts of different coins
 * @returns {Number} The count of distinct sums
 */
function countDistinctSum(coins, counts) {
    // Implementation
}

第二步 初始化變量和定義函數結構

首先,咱們應該先作一個檢查,若是coins的長度跟counts的長度並不相等的話,則函數不該該繼續處理,而是應該返回一個錯誤值。暫且定爲-1吧。測試

而後,咱們採用key value pair的形式來儲存不一樣和(sum)的數量。因此咱們必須有一個名叫result的對象。固然,其中並無任何的key。在函數運行必要的計算以後,再返回result的key的數量做爲最後的答案。code

另外,咱們還須要將全部硬幣組合起來,組成一個新的數組coinList,其中包含了全部的硬幣。好比:硬幣數組[10, 50, 100],和表明其數量的數組[1, 2, 1],組合成[10, 50, 50, 100]。這一部分用兩個for loop輕鬆搞定。對象

代碼以下:遞歸

function countDistinctSum(coins, counts) {
    if(coins.length !== counts.length) {
        return -1;
    }

    const results = {};
    const coinList = [];

    for(let i = 0; i < coins.length; i++){
        for(let j = 0; j < counts[i]; j++) {
            coinList.push(coins[i]);
        }
    }

    processList(coinList, coinList.length, 0, 0, results);

    return Object.keys(results).length - 1; // Remove the empty 0 coin element
}

第三步 處理coinList

當咱們獲得一個coinList以後,接下來咱們便要處理這個數組,將其內部的元素全都排列一遍。好比,[10, 50, 100]就有如下排列方法。

  1. [10]
  2. [10, 50]
  3. [10, 100]
  4. [10, 50, 100]
  5. [50]
  6. [50, 100]
  7. [100]

這時,我便考慮使用遞歸法(recursive method)來解決這個問題。

  • 函數接受兩個輸入

    • results用來儲存已經有的sum
    • n用來儲存硬幣數組的長度
    • sum用來儲存臨時已經疊加的和
    • currentIndex用來記錄當前遍歷到的索引
    • coinList用來輸入當下數組裏的硬幣元素。
  • 設置出口條件

    • currentIndex大於coinList的長度時,返回。
    • currentIndex等於coinList的長度時,調用addToResults函數(下一步講解)而後返回。
  • 遞歸函數

    • processList()會被遞歸兩次,這二者之間的帶入參數區別只有sum的不一樣而已
    • 第一個processList()將帶入sum加上當下的coin值,達到計算累計的效果。好比:[10, 50, 50, 100]一路加到最後成爲210
    • 第二個processList()將之帶入當下的sum值去到下一個遞歸。隨着index的增長,該sum值會將一直被保留直到最終被加入result,它也將實現跳躍相加的方法。好比:[10, 50, 50, 100]其中的10 + 100就能夠在這個遞歸中實現。

代碼以下:

/**
 * Recursive function to collect all the sums from all combinations
 * @param {Array<Number>} coinList array of coins
 * @param {Number} n the length of coin array
 * @param {Number} sum the current accumulated value
 * @param {Number} currentIndex the current processing index in the coin array
 * @param {Object} results existing result object brought into recursive function for recording sum
 */
function processList(coinList, n, sum, currentIndex, results) {
    if(currentIndex > n) {
        return;
    }

    if(currentIndex === n){
        addToResults(results, sum);
        return;
    }

    processList(coinList, n, sum + coinList[currentIndex], currentIndex + 1, results);
    processList(coinList, n, sum, currentIndex + 1, results);
}

第四步 addToResults函數

由於result自己是空的,當咱們算出一個和是result裏沒有的key的話,必須初始化這個key,使其值爲1。反之,則只須要在其值上加1即可。

代碼以下:

/**
 * Add the number to results as a key
 * @param {Object} results 
 * @param {Number} number 
 */
function addToResults(results, number) {
    if(results[number]) {
        results[number]++;
    } else {
        results[number] = 1;
    }
}

大功告成!

完整代碼:

/**
 * Count number of 
 * @param {Array<Number>} coins array contains coins with different values
 * @param {Array<Number>} counts array contains corresponding counts of different coins
 * @returns {Number} The count of distinct sums
 */
function countDistinctSum(coins, counts) {
    if(coins.length !== counts.length) {
        return -1;
    }

    const results = {};
    const coinList = [];

    for(let i = 0; i < coins.length; i++){
        for(let j = 0; j < counts[i]; j++) {
            coinList.push(coins[i]);
        }
    }

    processList(coinList, coinList.length, 0, 0, results);

    return Object.keys(results).length - 1; // Remove the empty 0 coin element
}

/**
 * Recursive function to collect all the sums from all combinations
 * @param {Array<Number>} coinList array of coins
 * @param {Number} n the length of coin array
 * @param {Number} sum the current accumulated value
 * @param {Number} currentIndex the current processing index in the coin array
 * @param {Object} results existing result object brought into recursive function for recording sum
 */
function processList(coinList, n, sum, currentIndex, results) {
    if(currentIndex > n) {
        return;
    }

    if(currentIndex === n){
        addToResults(results, sum);
        return;
    }

    processList(coinList, n, sum + coinList[currentIndex], currentIndex + 1, results);
    processList(coinList, n, sum, currentIndex + 1, results);
}

/**
 * Add the number to results as a key
 * @param {Object} results 
 * @param {Number} number 
 */
function addToResults(results, number) {
    if(results[number]) {
        results[number]++;
    } else {
        results[number] = 1;
    }
}

測試:

const a = [10, 50, 100];
const b = [1, 2, 1];

console.log('Result:', countDistinctSum(a, b)) // Result: 9
相關文章
相關標籤/搜索