每週一道算法題:兌換零錢

問題:

已知可兌換的零錢種類有1元,5元,10元,20元4種,現在有100塊錢要換成零錢且總數量少於15張,有幾種換法?分別是什麼?

思路:

已知有[1,5,10,20]這樣的一個可選數據集S,現在要從中取出n個數,每個數的張數爲a,使得a1xn1+a2xn2+...aixni = 100。

最大的面額是20,總共需要100/20=5張,這是最少的張數,所以循環的下限是5,上限題目已經限定了,是15。

也就是說,從S這個數據集中,取5-15個數,使得他們的和爲100,數是可以重複的。

先考慮最簡單的情況。先不考慮和爲100這個限制。僅僅是從數據集S中取5個數,數字可以重複,這種情況有多少種?

在我們這個問題中,對取出來的數的順序是沒有要求的,比如:取出來是[1,20,10,5,5]與[1,5,5,10,20]沒有區別,所以這是個組合的問題。
但一般的組合問題都是從m個不同的數裏選n個,公式就是C(m,n)。
但我們這裏的數是允許重複的,怎麼辦?可以轉換一下,假如我們取了[1,1,1,1,1]這5個數(即n=5),我們可以保持第一個1不變,餘下的4個數(即n-1)全部加上一個100以上的隨機數,使得其與S中別的元素都不同,那麼便相當於從m+n-1個不同的數中取出n個數,公式是C(m+n-1, n)。驗證一下。

假如S爲[1,5],從中取2個,可以得到如下的組合:
[1,1],[1,5],[5,5],共計3種。
用公式算,C(2+2-1,2) = C(3,2)=3;正確。
再假如從中取3個,可以得到如下組合:
[1,1,1],[5,1,1],[5,5,1],[5,5,5],共計4種。
用公式算,C(2+3-1,3)=4;正確。

所以從m個不同數中取出n個可重複的數的組合數量是C(m+n-1,n)

現在要把這些組合列出來,可以用如下的辦法:
我們建立一個臨時數組,用來存放S中各元素的下標。
初始化爲[0,0,0,0,0],表示取的5個元素全是S[0];
接着對首位進行加1,變爲[1,0,0,0,0],表示的金額爲[5,1,1,1,1];
首位繼續累加,直到編歷完所有的S,最後會變爲[3,0,0,0];

這是第一輪的過程,即:
[0,0,0,0,0]
[1,0,0,0,0]
[2,0,0,0,0]
[3,0,0,0,0]

然後進行下一輪,當首位元素的下標超過3時,對第2位進行處理,即處理成:
[1,1,0,0,0]
接着繼續累加首位
[2,1,0,0,0]
[3,1,0,0,0]

當首位超過3時,對第2位進行累加
[1,2,0,0,0]
接首繼續累加首位
[2,2,0,0,0]
[3,2,0,0,0]

重複這個過程,直到所有的元素變爲
[3,3,3,3,3]

但全部爲3這個條件不好判斷,我們可以增加一位,讓下標數組多出1位來,這樣,當第6個元素有值時,即認爲已經結束了。
即當元素下標成爲[1,1,1,1,1,1]時,程序退出。

把所有的下標數組彙總到一起,並映射到數據集的對應元素上,即爲最終結果。

解答:

php

/**
 * 取元素可重複的組合
 * @param $arr array 可選擇元素數組
 * @param $n int 要取的數量
 * @return array
 */
function repeatedCombination($arr, $n)
{
    $len = count($arr);// 數組長度
    $tmp = array_fill(0, $n + 1, 0);// 存放組合元素的下標,初始爲0
    $len--; // 數組長度減1
    $result = array();
    while (1) {
        for ($i = 0; $i < $n; ++$i) { // n個元素
            if ($tmp[$i] > $len) {
                $tmp[$i + 1] += 1;
                for ($j = $i; $j >= 0; --$j) {
                    $tmp[$j] = $tmp[$j + 1];
                }
            }
        }

        // 當最後一位非0時,所有的組合都已經處理完畢,退出
        if ($tmp[$n] > 0) {
            break;
        }

        $item = array();
        for ($i = 0; $i < $n; $i++) {
            $item[] = $arr[$tmp[$i]];
        }
        $result[] = $item;
        $tmp[0] += 1;
    }
    return $result;
}

$arr = array("1", "5", "10", "20");

$m = array();
for ($i = 5; $i < 15; $i++) {
    $rs = repeatedCombination($arr, $i);
    foreach ($rs as $r) {
        if (array_sum($r) == 100) {
            $m[] = $r;
        }
    }
}
print_r($m);

輸出:

Array
(
    [0] => 20-20-20-20-20
    [1] => 20-20-20-20-10-10
    [2] => 20-20-20-20-10-5-5
    [3] => 20-20-20-10-10-10-10
    [4] => 20-20-20-20-5-5-5-5
    [5] => 20-20-20-10-10-10-5-5
    [6] => 20-20-10-10-10-10-10-10
    [7] => 20-20-20-10-10-5-5-5-5
    [8] => 20-20-10-10-10-10-10-5-5
    [9] => 20-10-10-10-10-10-10-10-10
    [10] => 20-20-20-10-5-5-5-5-5-5
    [11] => 20-20-10-10-10-10-5-5-5-5
    [12] => 20-10-10-10-10-10-10-10-5-5
    [13] => 10-10-10-10-10-10-10-10-10-10
    [14] => 20-20-20-20-10-5-1-1-1-1-1
    [15] => 20-20-20-5-5-5-5-5-5-5-5
    [16] => 20-20-10-10-10-5-5-5-5-5-5
    [17] => 20-10-10-10-10-10-10-5-5-5-5
    [18] => 10-10-10-10-10-10-10-10-10-5-5
    [19] => 20-20-20-20-5-5-5-1-1-1-1-1
    [20] => 20-20-20-10-10-10-5-1-1-1-1-1
    [21] => 20-20-10-10-5-5-5-5-5-5-5-5
    [22] => 20-10-10-10-10-10-5-5-5-5-5-5
    [23] => 10-10-10-10-10-10-10-10-5-5-5-5
    [24] => 20-20-20-10-10-5-5-5-1-1-1-1-1
    [25] => 20-20-10-10-10-10-10-5-1-1-1-1-1
    [26] => 20-20-10-5-5-5-5-5-5-5-5-5-5
    [27] => 20-10-10-10-10-5-5-5-5-5-5-5-5
    [28] => 10-10-10-10-10-10-10-5-5-5-5-5-5
    [29] => 20-20-20-10-5-5-5-5-5-1-1-1-1-1
    [30] => 20-20-10-10-10-10-5-5-5-1-1-1-1-1
    [31] => 20-10-10-10-10-10-10-10-5-1-1-1-1-1
    [32] => 20-20-5-5-5-5-5-5-5-5-5-5-5-5
    [33] => 20-10-10-10-5-5-5-5-5-5-5-5-5-5
    [34] => 10-10-10-10-10-10-5-5-5-5-5-5-5-5
)

golang:

package main

import (
    "fmt"
    "strconv"
)

func main() {
    data := []string{"1", "5", "10", "20"}
    m := make([][]string, 0)
    for i := 5; i < 15; i++ {
        rs := repeatedCombination(data, i)
        for _, r := range rs {
            sum := 0
            for _, v := range r {
                val, _ := strconv.Atoi(v)
                sum += val
            }
            if sum == 100 {
                m = append(m, r)
            }
        }
    }
    fmt.Println(m)
}

func repeatedCombination(data []string, n int) [][]string {
    length := len(data)
    limit := length - 1
    tmp := make([]int, n+1)
    result := make([][]string, 0)
    for {
        for i := 0; i < n; i++ {
            if tmp[i] > limit {
                tmp[i+1] += 1
                for j := i; j >= 0; j-- {
                    tmp[j] = tmp[j+1]
                }
            }
        }

        if tmp[n] > 0 {
            break
        }

        var item []string
        for i := 0; i < n; i++ {
            item = append(item, data[tmp[i]])
        }
        result = append(result, item)

        tmp[0] += 1
    }
    return result
}

輸出:

[[20 20 20 20 20]
[20 20 20 20 10 10] 
[20 20 20 20 10 5 5]
[20 20 20 10 10 10 10]
[20 20 20 20 5 5 5 5] 
[20 20 20 10 10 10 5 5]
[20 20 10 10 10 10 10 10] 
[20 20 20 10 10 5 5 5 5] 
[20 20 10 10 10 10 10 5 5]
[20 10 10 10 10 10 10 10 10] 
[20 20 20 10 5 5 5 5 5 5]
[20 20 10 10 10 10 5 5 5 5]
[20 10 10 10 10 10 10 10 5 5] 
[10 10 10 10 10 10 10 10 10 10]
[20 20 20 20 10 5 1 1 1 1 1] 
[20 20 20 5 5 5 5 5 5 5 5] 
[20 20 10 10 10 5 5 5 5 5 5] 
[20 10 10 10 10 10 10 5 5 5 5] 
[10 10 10 10 10 10 10 10 10 5 5] 
[20 20 20 20 5 5 5 1 1 1 1 1] 
[20 20 20 10 10 10 5 1 1 1 1 1] 
[20 20 10 10 5 5 5 5 5 5 5 5]
[20 10 10 10 10 10 5 5 5 5 5 5] 
[10 10 10 10 10 10 10 10 5 5 5 5] 
[20 20 20 10 10 5 5 5 1 1 1 1 1]
[20 20 10 10 10 10 10 5 1 1 1 1 1] 
[20 20 10 5 5 5 5 5 5 5 5 5 5] 
[20 10 10 10 10 5 5 5 5 5 5 5 5] 
[10 10 10 10 10 10 10 5 5 5 5 5 5]
[20 20 20 10 5 5 5 5 5 1 1 1 1 1]
[20 20 10 10 10 10 5 5 5 1 1 1 1 1]
[20 10 10 10 10 10 10 10 5 1 1 1 1 1] 
[20 20 5 5 5 5 5 5 5 5 5 5 5 5] 
[20 10 10 10 5 5 5 5 5 5 5 5 5 5]
[10 10 10 10 10 10 5 5 5 5 5 5 5 5]]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章