算法09-貪心算法

《算法練習(xí)-文章匯總》

貪心算法是一種在每一步選擇中都采取在當(dāng)前狀態(tài)下最好或最優(yōu)(即最有利)的選擇,從而希望導(dǎo)致結(jié)果是全局最好或最優(yōu)的算法。

貪心算法與動態(tài)規(guī)劃的不同在于它對每個子問題的解決方案都作出選擇,不能回退。動態(tài)規(guī)劃則會保存以前的運(yùn)算結(jié)果,并根據(jù)以前的結(jié)果對當(dāng)前進(jìn)行選擇,有回退功能。

每次當(dāng)下選擇最好的會不會是全局最優(yōu)的呢?不一定,有時候可以,有時候不行

很多情況下,可以在某一步用貪心算法,全局再加一個搜索或遞歸或動態(tài)規(guī)劃之類

貪心:當(dāng)下做局部最優(yōu)判斷

回溯:能夠回退

動態(tài)規(guī)劃:最優(yōu)判斷+回退

貪心法可以解決一些最優(yōu)化問題,如:求圖中的最小生成樹、求哈夫曼編碼等。然而對于工程和生活中的問題,貪心法一般不能得到我們所要求的答案。
一單一個問題可以通過貪心法來解決,那么貪心法一般是解決這個問題的最好辦法。由于貪心法的高效性以及其所求得的答案比較接近最優(yōu)結(jié)果,貪心法也可以用作輔助算法或者直接解決一些要求結(jié)果不特別精確的問題。

當(dāng)硬幣可選集合固定:Coins = [20,10,5,1],求最少幾個硬幣可以拼出總數(shù)。比如total=36。
36 - 20 = 16 20
16 - 10 = 6 20 10
6 - 5 = 1 20 10 5
1 - 1 = 0 20 10 5 1
前面這些硬幣依次是后面硬幣的整數(shù)倍,可以用貪心法,能得到最優(yōu)解,

貪心法的反例
非整除關(guān)系的硬幣,可選集合:Coins = [10,9,1],求拼出總數(shù)為18最少需要幾個硬幣?
最優(yōu)化算法:9 + 9 = 18 兩個9
貪心算法:18 - 10 = 8 - 1 - 1 - 1 - 1 - 1 - 1 - 1 - 1 = 0 八個1

何種情況下可以用到貪心算法

簡單地說,問題能夠分解成子問題來解決,子問題的最優(yōu)解能遞推到最終問題的最優(yōu)解。這種子問題最優(yōu)解成為最優(yōu)子結(jié)構(gòu)。
貪心算法與動態(tài)規(guī)劃的不同在于它對每個子問題的最終方案都作出選擇,不能回退。
動態(tài)規(guī)劃則會保存以前的運(yùn)算結(jié)果,并根據(jù)以前的結(jié)果對當(dāng)前進(jìn)行選擇,有回退功能。

分發(fā)餅干(亞馬遜)

假設(shè)你是一位很棒的家長,想要給你的孩子們一些小餅干。但是,每個孩子最多只能給一塊餅干。
對每個孩子 i,都有一個胃口值 g[i],這是能讓孩子們滿足胃口的餅干的最小尺寸;并且每塊餅干 j,都有一個尺寸 s[j] 。如果 s[j] >= g[i],我們可以將這個餅干 j 分配給孩子 i ,這個孩子會得到滿足。你的目標(biāo)是盡可能滿足越多數(shù)量的孩子,并輸出這個最大數(shù)值。
示例 1:
輸入: g = [1,2,3], s = [1,1]
輸出: 1
解釋:
你有三個孩子和兩塊小餅干,3個孩子的胃口值分別是:1,2,3。
雖然你有兩塊小餅干,由于他們的尺寸都是1,你只能讓胃口值是1的孩子滿足。
所以你應(yīng)該輸出1。
示例 2:
輸入: g = [1,2], s = [1,2,3]
輸出: 2
解釋:
你有兩個孩子和三塊小餅干,2個孩子的胃口值分別是1,2。
你擁有的餅干數(shù)量和尺寸都足以讓所有孩子滿足。
所以你應(yīng)該輸出2.
提示:
1 <= g.length <= 3 * 104
0 <= s.length <= 3 * 104
1 <= g[i], s[j] <= 231 - 1

    public int findContentChildren(int[] g, int[] s) {
        //排序小孩胃口值
        Arrays.sort(g);
        //排序餅干數(shù)量
        Arrays.sort(s);
        //記錄滿足條件數(shù)量
        int res = 0;
        int i = 0;
        int j = 0;
        while (i<g.length && j<s.length){
            if (g[i] <= s[j]){
                res+=1;
                i++;
                j++;
            }else{
                j++;
            }
        }
        return res;
    }

買賣股票的最佳時機(jī)(亞馬遜、字節(jié)、微軟)

給定一個數(shù)組,它的第 i 個元素是一支給定股票第 i 天的價格。
設(shè)計(jì)一個算法來計(jì)算你所能獲取的最大利潤。你可以盡可能地完成更多的交易(多次買賣一支股票)。
注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
隨后,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。
示例 2:
輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接連購買股票,之后再將它們賣出。
因?yàn)檫@樣屬于同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。
示例 3:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。

    public int maxProfit(int[] prices) {
        if (prices.length < 2) return 0;
        int res = 0;
        for(int i = 1;i<prices.length;i++){
            if (prices[i] > prices[i-1]){
                res += (prices[i] - prices[i-1]);
            }
        }
        return res;
    }

跳躍游戲(亞馬遜、華為、Facebook)

給定一個非負(fù)整數(shù)數(shù)組 nums ,你最初位于數(shù)組的 第一個下標(biāo) 。
數(shù)組中的每個元素代表你在該位置可以跳躍的最大長度。
判斷你是否能夠到達(dá)最后一個下標(biāo)。
示例 1:
輸入:nums = [2,3,1,1,4]
輸出:true
解釋:可以先跳 1 步,從下標(biāo) 0 到達(dá)下標(biāo) 1, 然后再從下標(biāo) 1 跳 3 步到達(dá)最后一個下標(biāo)。
示例 2:
輸入:nums = [3,2,1,0,4]
輸出:false
解釋:無論怎樣,總會到達(dá)下標(biāo)為 3 的位置。但該下標(biāo)的最大跳躍長度是 0 , 所以永遠(yuǎn)不可能到達(dá)最后一個下標(biāo)。

    public boolean canJump(int[] nums) {
        if (nums == null) return false;
        int endReachable = nums.length - 1;
        for (int i = nums.length -1; i>=0;i--){
            if (nums[i] + i >= endReachable){
                endReachable = i;
            }
        }
        return endReachable == 0;
    }

跳躍游戲II(亞馬遜、華為、字節(jié)跳動)

給定一個非負(fù)整數(shù)數(shù)組,你最初位于數(shù)組的第一個位置。
數(shù)組中的每個元素代表你在該位置可以跳躍的最大長度。
你的目標(biāo)是使用最少的跳躍次數(shù)到達(dá)數(shù)組的最后一個位置。
示例:
輸入: [2,3,1,1,4]
輸出: 2
解釋: 跳到最后一個位置的最小跳躍數(shù)是 2。
從下標(biāo)為 0 跳到下標(biāo)為 1 的位置,跳 1 步,然后跳 3 步到達(dá)數(shù)組的最后一個位置。
說明:
假設(shè)你總是可以到達(dá)數(shù)組的最后一個位置。
移動下標(biāo)只要遇到當(dāng)前覆蓋最遠(yuǎn)距離的下標(biāo),直接步數(shù)加一,不考慮是不是終點(diǎn)的情況。
想要達(dá)到這樣的效果,只要讓移動下標(biāo),最大只能移動到nums.size - 2的地方就可以了。
因?yàn)楫?dāng)移動下標(biāo)指向nums.size - 2時:
如果移動下標(biāo)等于當(dāng)前覆蓋最大距離下標(biāo), 需要再走一步(即ans++),因?yàn)樽詈笠徊揭欢ㄊ强梢缘降慕K點(diǎn)。(題目假設(shè)總是可以到達(dá)數(shù)組的最后一個位置),如圖:


跳躍游戲II.png

如果移動下標(biāo)不等于當(dāng)前覆蓋最大距離下標(biāo),說明當(dāng)前覆蓋最遠(yuǎn)距離就可以直接達(dá)到終點(diǎn)了,不需要再走一步。如圖:


跳躍游戲II-0.png
    public int jump(int[] nums) {
        int ans = 0;//記錄要跳躍的步數(shù)
        int curDistance = 0; //當(dāng)前覆蓋的最遠(yuǎn)距離下標(biāo)
        int nextDistance = 0; //下一步覆蓋的最遠(yuǎn)距離下標(biāo)
        for (int i = 0;i<nums.length - 1;i++){//小于nums.length-1
            nextDistance = Math.max(nums[i]+i,nextDistance);//獲取下一步能覆蓋的最遠(yuǎn)距離下標(biāo)
            if (curDistance == i){//遇到當(dāng)前位置的最遠(yuǎn)距離下標(biāo)
                curDistance = nextDistance;//更新當(dāng)前覆蓋的最遠(yuǎn)距離下標(biāo)
                ans++;
            }
        }
        return ans;
    }

模擬行走機(jī)器人

機(jī)器人在一個無限大小的 XY 網(wǎng)格平面上行走,從點(diǎn) (0, 0) 處開始出發(fā),面向北方。該機(jī)器人可以接收以下三種類型的命令 commands :
-2 :向左轉(zhuǎn) 90 度
-1 :向右轉(zhuǎn) 90 度
1 <= x <= 9 :向前移動 x 個單位長度
在網(wǎng)格上有一些格子被視為障礙物 obstacles 。第 i 個障礙物位于網(wǎng)格點(diǎn) obstacles[i] = (xi, yi) 。
機(jī)器人無法走到障礙物上,它將會停留在障礙物的前一個網(wǎng)格方塊上,但仍然可以繼續(xù)嘗試進(jìn)行該路線的其余部分。
返回從原點(diǎn)到機(jī)器人所有經(jīng)過的路徑點(diǎn)(坐標(biāo)為整數(shù))的最大歐式距離的平方。(即,如果距離為 5 ,則返回 25 )
注意:
北表示 +Y 方向。
東表示 +X 方向。
南表示 -Y 方向。
西表示 -X 方向。
示例 1:
輸入:commands = [4,-1,3], obstacles = []
輸出:25
解釋:
機(jī)器人開始位于 (0, 0):

  1. 向北移動 4 個單位,到達(dá) (0, 4)
  2. 右轉(zhuǎn)
  3. 向東移動 3 個單位,到達(dá) (3, 4)
    距離原點(diǎn)最遠(yuǎn)的是 (3, 4) ,距離為 32 + 42 = 25
    示例 2:
    輸入:commands = [4,-1,4,-2,4], obstacles = [[2,4]]
    輸出:65
    解釋:機(jī)器人開始位于 (0, 0):
  4. 向北移動 4 個單位,到達(dá) (0, 4)
  5. 右轉(zhuǎn)
  6. 向東移動 1 個單位,然后被位于 (2, 4) 的障礙物阻擋,機(jī)器人停在 (1, 4)
  7. 左轉(zhuǎn)
  8. 向北走 4 個單位,到達(dá) (1, 8)
    距離原點(diǎn)最遠(yuǎn)的是 (1, 8) ,距離為 12 + 82 = 65
public int robotSim(int[] commands, int[][] obstacles) {
        int result = 0;
        int x=0,y=0,direction =0;
        int[] directX = new int[]{0,1,0,-1};
        int[] directY = new int[]{1,0,-1,0};
        Set<String> obstacleSet = new HashSet<>();
        for (int[] m: obstacles) {
           obstacleSet.add(m[0]+","+m[1]);
        }
        for (int i : commands) {
           if (i == -2){//向左轉(zhuǎn)
               direction = (direction + 3)%4;
           }
           else if (i==-1){//向右轉(zhuǎn)
               direction = (direction + 1)%4;
           }else{
               for (int j = 1;j<=i;j++){
                   int newX = x + directX[direction];
                   int newY = y + directY[direction];
                   if (obstacleSet.contains(newX+","+newY)){
                       break;
                   }
                   x = newX;
                   y = newY;
                   result = Math.max(result,x*x+y*y);
               }
           }
        }
        return result;
    }

檸檬水找零(亞馬遜)

在檸檬水?dāng)偵?,每一杯檸檬水的售價為 5 美元。
顧客排隊(duì)購買你的產(chǎn)品,(按賬單 bills 支付的順序)一次購買一杯。
每位顧客只買一杯檸檬水,然后向你付 5 美元、10 美元或 20 美元。你必須給每個顧客正確找零,也就是說凈交易是每位顧客向你支付 5 美元。
注意,一開始你手頭沒有任何零錢。
如果你能給每位顧客正確找零,返回 true ,否則返回 false 。
示例 1:
輸入:[5,5,5,10,20]
輸出:true
解釋:
前 3 位顧客那里,我們按順序收取 3 張 5 美元的鈔票。
第 4 位顧客那里,我們收取一張 10 美元的鈔票,并返還 5 美元。
第 5 位顧客那里,我們找還一張 10 美元的鈔票和一張 5 美元的鈔票。
由于所有客戶都得到了正確的找零,所以我們輸出 true。
示例 2:
輸入:[5,5,10]
輸出:true
示例 3:
輸入:[10,10]
輸出:false
示例 4:
輸入:[5,5,10,10,20]
輸出:false
解釋:
前 2 位顧客那里,我們按順序收取 2 張 5 美元的鈔票。
對于接下來的 2 位顧客,我們收取一張 10 美元的鈔票,然后返還 5 美元。
對于最后一位顧客,我們無法退回 15 美元,因?yàn)槲覀儸F(xiàn)在只有兩張 10 美元的鈔票。
由于不是每位顧客都得到了正確的找零,所以答案是 false。

    public boolean lemonadeChange(int[] bills) {
        int five = 0,ten = 0,twenty = 0;
        for (int bill : bills) {
            if (bill == 5){
                five++;
            }else if(bill == 10){
                if (five>0) {
                    five--;
                    ten++;
                }else return false;
            }else if(bill == 20){
                if (ten>0 && five > 0){
                    ten --;
                    five --;
                    twenty ++;
                }else if(five >=3){
                    five -= 3;
                    twenty ++;
                }else{
                    return false;
                }
            }
        }
        return true;
    }

coin change題目-零錢兌換

給定不同面額的硬幣 coins 和一個總金額 amount。編寫一個函數(shù)來計(jì)算可以湊成總金額所需的最少的硬幣個數(shù)。如果沒有任何一種硬幣組合能組成總金額,返回 -1。
你可以認(rèn)為每種硬幣的數(shù)量是無限的。
示例 1:
輸入:coins = [1, 2, 5], amount = 11
輸出:3
解釋:11 = 5 + 5 + 1
示例 2:
輸入:coins = [2], amount = 3
輸出:-1
示例 3:
輸入:coins = [1], amount = 0
輸出:0
示例 4:
輸入:coins = [1], amount = 1
輸出:1
示例 5:
輸入:coins = [1], amount = 2
輸出:2

  int minAns = Integer.MAX_VALUE;
    public int coinChange(int[] coins, int amount){
        if (amount == 0) return 0;
        Integer[] coinArray = Arrays.stream(coins).boxed().toArray(Integer[]::new);
        Arrays.sort(coinArray,Collections.reverseOrder());
        coinChangeBfs(coinArray,amount,0,0);
        minAns = (minAns == Integer.MAX_VALUE)?-1:minAns;
        return minAns;
    }
    //coinArray:硬幣數(shù)組 amount:目標(biāo)值 index: 有效硬幣數(shù)組范圍 count: 已使用硬幣數(shù)  miniAns: 當(dāng)前最小值
    public void coinChangeBfs(Integer[] coinArray,int amount,int index,int count){
        if (amount == 0) {//遍歷到最后有解
            minAns = Math.min(minAns,count);
            return;
        }
        if (coinArray.length == index) return;//遍歷到最后無解
        for (int maxCoinNum = amount/coinArray[index];maxCoinNum>=0 && maxCoinNum+count<minAns;maxCoinNum--){
            coinChangeBfs(coinArray,amount-maxCoinNum*coinArray[index],index+1,count+maxCoinNum);
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容