背包系列問題——換零錢

參考資料:
1. 動(dòng)態(tài)規(guī)劃之背包問題系列

背包問題的定義參見參考資料1
跟背包問題不同的是匆骗,目標(biāo)是換的零錢的個(gè)數(shù)最少。
零錢個(gè)數(shù)不限誉简,所以是完全背包問題碉就。
因?yàn)橐婚_始沒有答案,所以初始化為INVALID闷串,背包問題一開始初始化為0铝噩,因?yàn)楸嘲裁炊疾谎b,0也算背包的收益窿克。

  • 分析一迭代求法
private int INVALID = Integer.MAX_VALUE;

// 分析一 迭代求法
public int coinChange(int[] coins, int amount){
    int[][] dp = new int[coins.length+1][amount+1];
    for (int i = 0; i < dp.length; i++) {
        Arrays.fill(dp[i], INVALID);
    }

    for (int i = 0; i < dp.length; i++) {
        dp[i][0] = 0;
    }

    for (int i = 1; i <= coins.length; i++) {
        for (int j = 1; j <= amount; j++) {
            int use;

            if(j >= coins[i-1]){
                use = dp[i][j - coins[i-1]];
                if(use != INVALID){
                    use += 1;
                }
            }else{
                use = INVALID;
            }

            dp[i][j] = Math.min(dp[i-1][j], use);
        }
    }

    return dp[coins.length][amount] == INVALID ? -1:dp[coins.length][amount];
}
  • 分析一節(jié)省空間解法
private int INVALID = Integer.MAX_VALUE;

// 分析一 節(jié)省空間
public int coinChange(int[] coins, int amount){
    int[] dp = new int[amount+1];
    Arrays.fill(dp, INVALID);

    dp[0] = 0;

    for (int i = 1; i <= coins.length; i++) {
        for (int j = 1; j <= amount; j++) {
            int use;
            if(j >= coins[i-1]){
                use = dp[j - coins[i-1]];
                if(use != INVALID){
                    use += 1;
                }
            }else{
                use = INVALID;
            }
            dp[j] = Math.min(dp[j], use);
        }
    }

    return dp[amount] == INVALID ? -1:dp[amount];
}
  • 分析一遞歸解法
private int INVALID = Integer.MAX_VALUE;

public int coinChange(int[] coins, int amount){
    if(amount==0)
        return 0;

    if(coins.length==0)
        return -1;

    int[][] dp = new int[coins.length][amount+1];

    int result = coinChange(coins, amount, dp, coins.length-1);
    return result == INVALID ? -1:result;
}

private int coinChange(int[] coins, int amount, int[][] dp, int indexOfLastCoin) {
    if(dp[indexOfLastCoin][amount] != 0)
        return dp[indexOfLastCoin][amount];

    int use = INVALID;
    int leftAmount = amount - coins[indexOfLastCoin];
    if(leftAmount > 0){
        use = coinChange(coins, leftAmount, dp, indexOfLastCoin);
        if(use!=INVALID){
            use += 1;
        }
    } else if(leftAmount == 0){
        use = 1;
    }

    int notUsed = INVALID;
    int indexOfLeftCoin = indexOfLastCoin - 1;
    if(indexOfLeftCoin >= 0){
        notUsed = coinChange(coins, amount, dp, indexOfLeftCoin);
    }

    dp[indexOfLastCoin][amount] = Math.min(use, notUsed);

    // System.out.println("dp[" + indexOfLastCoin + "][" + amount + "]:"+dp[indexOfLastCoin][amount]);
    return dp[indexOfLastCoin][amount];
}
  • 分析二迭代求法
private int INVALID = Integer.MAX_VALUE;

// 分析二 根據(jù)amount限制對(duì)應(yīng)的硬幣個(gè)數(shù)骏庸,在01背包里面加上一層循環(huán)
public int coinChange(int[] coins, int amount){
    int[][] dp = new int[coins.length+1][amount+1];

    for (int i = 0; i < dp.length; i++) {
        Arrays.fill(dp[i], INVALID);
    }

    for (int i = 0; i < dp.length; i++) {
        dp[i][0] = 0;
    }

    for (int i = 1; i <= coins.length; i++) {
        for (int j = 1; j <= amount; j++) {
            int min = INVALID;
            // 這里k必須從0開始,不一定要用到conis[i-1]年叮,可以不用具被;
            for (int k = 0; k <= j/coins[i-1]; k++) {
                int leftAmount = j-coins[i-1]*k;
                if(leftAmount < 0 || dp[i-1][leftAmount]==INVALID)
                    continue;

                min = Math.min(min, dp[i-1][leftAmount] + k);
            }

            dp[i][j] = min;
        }
    }

    return dp[coins.length][amount] == INVALID ? -1:dp[coins.length][amount];
}
  • 分析二節(jié)省空間版本
private int INVALID = Integer.MAX_VALUE;

// 分析二節(jié)省空間版本 根據(jù)amount限制對(duì)應(yīng)的硬幣個(gè)數(shù),在01背包里面加上一層循環(huán)
public int coinChange(int[] coins, int amount){
    int[] dp = new int[amount+1];

    Arrays.fill(dp, INVALID);
    dp[0] = 0;

    for (int i = 1; i <= coins.length; i++) {
        for (int j = amount; j >= 1; j--) {
            int min = INVALID;
            // 這里k必須從0開始只损,不一定要用到conis[i-1]一姿,可以不用;
            for (int k = 0; k <= j/coins[i-1]; k++) {
                int leftAmount = j-coins[i-1]*k;
                if(leftAmount < 0 || dp[leftAmount]==INVALID)
                    continue;

                min = Math.min(min, dp[leftAmount] + k);
            }

            dp[j] = min;
        }
    }

    return dp[amount] == INVALID ? -1:dp[amount];
}
  • 分析二遞歸求法(DFS剪枝版本跃惫,每個(gè)硬幣使用的次數(shù)是有上限的叮叹,超時(shí)!1妗)
private int INVALID = Integer.MAX_VALUE;

// 分析二遞歸版本 根據(jù)amount限制對(duì)應(yīng)的硬幣個(gè)數(shù)蛉顽,在01背包里面加上一層循環(huán)
public int coinChange(int[] coins, int amount){
    int result = coinChange(coins, amount, 0, 0);
    return result == INVALID?-1:result;
}

private int coinChange(int[] coins, int amount, int coinIndex, int coinUsed) {
    if(amount==0)
        return coinUsed;

    if(coinIndex==coins.length)
        return INVALID;

    int min = INVALID;

    for (int i = 0; i <= amount / coins[coinIndex]; i++) {
        min = Math.min(min, coinChange(coins, amount - coins[coinIndex] * i, coinIndex + 1, coinUsed+i));
    }

    return min;
}
  • 其他DFS剪枝版本
    // DFS 剪枝
    private int min = Integer.MAX_VALUE;

    public int coinChange(int[] coins, int amount) {
        if(amount==0)
            return 0;

        int[] sortedCoins = Arrays.stream(coins).boxed().sorted(Comparator.reverseOrder()).mapToInt(Integer::intValue).toArray();

        coinChange(sortedCoins, 0, amount, 0);
        return min==Integer.MAX_VALUE?-1:min;
    }

    private void coinChange(int[] coins, int start, int amount, int usedCoins) {
        if(amount<0) {
            return ;
        }

        if(amount==0) {
            min = Math.min(usedCoins, min);
        }

        for (int i = start; i < coins.length; i++) {
            // min 代表題目給的amount目前找到的最小的硬幣的個(gè)數(shù)
            // min - usedCoins 代表還可以使用最多的個(gè)數(shù)
            // coins[i]是最大的,所以coins[i]*(min - usedCoins)都湊不夠先较,不用試了携冤。
            if(min!= Integer.MAX_VALUE && (coins[i]*(min - usedCoins))<amount)
                return ;

            coinChange(coins, i, amount-coins[i], usedCoins + 1);
        }
    }
  • BFS
    // BFS 從某個(gè)點(diǎn)是amount開始悼粮,coin是邊
    public int coinChange(int[] coins, int amount) {
        if (amount == 0)
            return 0;

        Deque<Integer> queue = new LinkedList<>();
        Set<Integer> set = new HashSet<>();
        queue.add(0);
        set.add(0);
        int numCoins = 0;

        while(queue.size()!=0){
            int size = queue.size();
            numCoins += 1;

            for (int i = 0; i < size; i++) {
                int element = queue.removeFirst();
                set.remove(element);
                for (int coin : coins) {
                    int newElement = element + coin;
                    if(newElement==amount) {
                        return numCoins;
                    }else if(newElement < amount) {
                        if(!set.contains(newElement)) {
                            queue.addLast(newElement);
                            set.add(newElement);
                        }
                    }
                }
            }
        }

        return -1;
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嗤堰,一起剝皮案震驚了整個(gè)濱河市怕午,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捅膘,老刑警劉巖翘地,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件申尤,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡衙耕,警方通過查閱死者的電腦和手機(jī)瀑凝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來臭杰,“玉大人粤咪,你說我怎么就攤上這事】矢耍” “怎么了寥枝?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)磁奖。 經(jīng)常有香客問我囊拜,道長(zhǎng),這世上最難降的妖魔是什么比搭? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任冠跷,我火速辦了婚禮,結(jié)果婚禮上身诺,老公的妹妹穿的比我還像新娘蜜托。我一直安慰自己,他們只是感情好霉赡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布橄务。 她就那樣靜靜地躺著,像睡著了一般穴亏。 火紅的嫁衣襯著肌膚如雪蜂挪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天嗓化,我揣著相機(jī)與錄音棠涮,去河邊找鬼。 笑死刺覆,一個(gè)胖子當(dāng)著我的面吹牛严肪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼诬垂,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了伦仍?” 一聲冷哼從身側(cè)響起结窘,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎充蓝,沒想到半個(gè)月后隧枫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谓苟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年官脓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涝焙。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卑笨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仑撞,到底是詐尸還是另有隱情赤兴,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布隧哮,位于F島的核電站桶良,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏沮翔。R本人自食惡果不足惜陨帆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望采蚀。 院中可真熱鬧疲牵,春花似錦、人聲如沸榆鼠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)璧眠。三九已至缩焦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間责静,已是汗流浹背袁滥。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留灾螃,地道東北人题翻。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親嵌赠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子塑荒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355