貪心算法--常見問題總結

什麼是貪心算法?

貪婪算法(貪心算法)是指在對問題進行求解時,在每一步選擇中都採取最好或者最優(即最有利)的選擇,從而希望能夠導致結果是最好或者最優的算法。
貪婪算法所得到的結果往往不是最優的結果(有時候會是最優解),但是都是相對近似(接近)最優解的結果。

貪心解決問題的基本思路?

1.建立數學模型來描述問題
2.把求解的問題分成若干個子問題
3.對每一子問題求解,得到子問題的局部最優解
4.把子問題對應的局部最優解合成原來整個問題的一個近似最優解

例題

1. 分配餅乾
題目描述:每個孩子都有一個滿足度,每個餅乾都有一個大小,只有餅乾的大小大於等於一個孩子的滿足度,該孩子纔會獲得滿足。求解最多可以獲得滿足的孩子數量。
給一個孩子的餅乾應當儘量小又能滿足該孩子,這樣大餅乾就能拿來給滿足度比較大的孩子。因爲最小的孩子最容易得到滿足,所以先滿足最小的孩子。

/*
    *每個孩子都有一個滿足度,每個餅乾都有一個大小,只有餅乾的大小大於等於一個孩子的滿足度,該孩子纔會獲得滿足。求解最多可以獲得滿足的孩子數量。
    * */
    public int findContentChildren(int[] g, int[] s) {
        /*
        * 思路:將所有 餅乾,孩子的願望 分別從小到大排序。
        * 從小到大遍歷每一個孩子的願望, 按照餅乾從小到大的順序來滿足從小到大的孩子的願望。
        * 判斷當前塊的餅乾是否可以滿足孩子的願望。如果不能(那麼後面有更大願望的孩子就更不能滿足了),所以刨除此塊,繼續用下一塊來判斷;如果能,則累計count。
        * 直到餅乾遍歷結束或者孩子的願望遍歷結束。
        * */
        Arrays.sort(g);
        Arrays.sort(s);
        int gi = 0;
        int si = 0;
        int count = 0;
        while (gi < s.length && si < s.length) {
            if (s[si] >= g[gi]) {
                count++;
                gi++;
            }
            si++;
        }
        return count;
    }

2. 不重疊的區間個數
題目描述:計算讓一組區間不重疊所需要移除的區間個數

    /*
    * 計算讓一組區間不重疊所需要移除的區間個數
    * */
    public int eraseOverlapIntervals(int[][] intervals) {
        /*
        * 思路:讓一組區間不重疊,優先選擇區間末尾值小的,這樣才能讓後面的區間最可能不重疊。
        * 換句話說 優先選擇區間末尾值小的區間,才能拼接更多的區間,移除更少的區間
        * 做法:
        * a.首先根據區間末尾值由小到大的順序 將區間排序
        * b.維持一個區間末尾值end
        * c.按照排序後的順序遍歷所有區間,如果當前區間不會與上一個區間重疊(判斷方式:當前區間的首值是否大於等於end),就更新end
        * */
        Arrays.sort(intervals, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[1] - o2[1];
            }
        });
        int end = Integer.MIN_VALUE;
        int removeCount = 0;
        for (int i = 0; i < intervals.length; i++) {
            if (intervals[i][0] >= end) {
                end = intervals[i][1];
            } else {
                removeCount++;
            }
        }
        return removeCount;
    }

相同思路的題目:
a.假設有如下課程,希望儘可能多的將課程安排在一間教室裏:
在這裏插入圖片描述
解題思路:選擇結束最早的課

b.氣球在一個水平數軸上擺放,可以重疊,飛鏢垂直投向座標軸,使得路徑上的氣球都被刺破。求解最小的投飛鏢次數使所有氣球都被刺破。也是計算不重疊的區間個數,不過和Non-overlapping Intervals 的區別在於,[1, 2] 和 [2, 3] 在本題中算是重疊區間。

3.根據身高和序號重組隊列
題目描述:一個學生用兩個分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 個學生的身高比他高或者和他一樣高。

 /*
    * 根據身高和序號重組隊列
    * 題目描述:一個學生用兩個分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 個學生的身高比他高或者和他一樣高。
    * */
    public int[][] recombination(int[][] people) {
        /*
        * 解題思路:優先向隊伍中插入最高的。原因是,確保了最高的學生的組隊條件,後面再向隊伍中插入比他矮的學生時,都不會影響他的條件。
        * 因爲他的條件是:排在他前面的k個學生 身高比他高或者和他一樣高。即使比他矮的學生插入到了他的前面也不會影響他,當然插到他後面更不會影響他了
        * 做法:
        * a.將所有的學生按照身高由大到小的順序排序,身高相同的情況下,將k小的排在前面。
        * b.按照排序後的順序 不斷將學生插入到隊列中
        * */
        Arrays.sort(people, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o2[0] == o1[0] ? o2[1] - o2[1] : o2[0] - o1[0];
            }
        });
        LinkedList<int[]> list = new LinkedList<>();
        list.add(people[0]);
        for (int i = 1; i < people.length; i++) {
            list.add(people[i][1], people[i]);
        }
        return list.toArray(new int[list.size()][]);
    }

4.買賣股票最大的收益

/*
    * 題目描述:一次股票交易包含買入和賣出,只進行一次交易,求最大收益。
    * */
    public int maxProfit(int[] prices) {
        int minBuy = Integer.MAX_VALUE;
        int maxProfit = 0;
        for (int i = 0; i < prices.length; i++) {
            if (prices[i] < minBuy) {
                minBuy = prices[i];
                continue;
            }
            maxProfit = Math.max(maxProfit, prices[i] - minBuy);
        }
        return maxProfit;
    }

    /*
    * 題目描述:可以進行多次交易,多次交易之間不能交叉進行,可以進行多次交易。
    * 做法:當遇到a[i]-a[i-1]>0時,就把這個差值累計算的利潤中
    * 思路:
    * 1.會不會出現a[i]前面有比a[i-1]還小的情況,不應該累計最多的利潤嗎?
    * 例如:2~5~9。 (5-2)+(9-5)=9-5  這種情況時,已經將5-2的利潤累計了,所以不必擔心累計不到9-2這種更大利潤的情況。寬泛的說:當a<b<c<d時,(b-a)+(c-b)+(d-c)=d-a
    * 2. 萬一出現2~5~10~9這種情況呢?
    * 9之前出現比它更大的元素,這種情況出現時,這段區間的最大利潤已經被10截斷了,跟9沒關係了。 最大利潤就是(5-2)+(10-5)=10-2.
    * 3.那麼9還有可能被累計嗎?
    * 當2~5~10~9~11時會被累計(5-2)+(10-5)+(11-9)。當2~5~10~9~8時,就直接捨棄9了。
    * */
    public int maxProfit2(int[] prices) {
        int maxProfit = 0;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] - prices[i - 1] > 0) {
                maxProfit += prices[i] - prices[i - 1];
            }
        }
        return maxProfit;
    }

5.種植花朵
題目描述:flowerbed 數組中 1 表示已經種下了花朵。花朵之間至少需要一個單位的間隔,求解是否能種下 n 朵花。

    /*
    *種植花朵
    * 題目描述:flowerbed 數組中 1 表示已經種下了花朵。花朵之間至少需要一個單位的間隔,求解是否能種下 n 朵花。
    * */
    public boolean canPlaceFlower(int[] flowerbed, int n) {
        /*
        * 解題思路:如果當前元素爲0,判斷當前元素左右是否也爲0
         * 注意邊界值就好
        * */
        if (flowerbed.length == 1) {
            if (flowerbed[0] == 0) n--;
        } else {
            for (int i = 0; i < flowerbed.length; i++) {
                if (flowerbed[i] == 0) {
                    if (i == 0) {
                        if (flowerbed[i + 1] == 0) {
                            n--;
                            flowerbed[i] = 1;
                        }
                    } else if (i == flowerbed.length - 1) {
                        if (flowerbed[i - 1] == 0) {
                            n--;
                            flowerbed[i] = 1;
                        }
                    } else if (flowerbed[i - 1] == 0 && flowerbed[i + 1] == 0) {
                        n--;
                        flowerbed[i] = 1;
                    }
                }
            }
        }
        if (n <= 0) return true;
        return false;
    }

6.判斷是否爲子序列

/*
    *判斷是否爲子序列
    * */
    public boolean isSubsequence(String s, String t) {
        /*做法一*/
        char []strT=t.toCharArray();
        int index=0;
        for(int i=0;i<strT.length;i++){
            if(index==s.length()) return true;
            if(s.charAt(index)==strT[i]){
                index++;
            }
        }
        if(index==s.length()){
            return true;
        }
        return false;
    }
    public boolean isSubsequence2(String s, String t) {
        /*做法二*/
        int index=-1;
        for(char c:s.toCharArray()){
            index=t.indexOf(c,index+1);
            if(index==-1) return false;
        }
        return true;
    }

7.修改一個數成爲非遞減數組
題目描述: 修改一個數,能否成爲非遞減數組

    /*
    * 修改一個數成爲非遞減數組
    * 思路:當nums[i]>nums[i+1]時,有兩種修改方案,修改nums[i]或nums[i+1]。但要判斷修改後,非遞減數組這個條件能否成立,成立纔可
    * 想要修改nums[i],要確保nums[i-1]<=nums[i+1]
    * 想要修改nums[i+1],要確保nums[i]<=nums[i+2]
    * */
    public boolean checkPossibility(int[] nums) {
        boolean isModify=false;
        for(int i=0;i<nums.length-1;i++){
            if(nums[i]>nums[i+1]){
                if(isModify) return false;
                if(i+1==nums.length-1) return true;
                if(nums[i]<=nums[i+2]) {
                    nums[i+1]=nums[i];
                    isModify=true;
                }else if(i==0||nums[i-1]<=nums[i+1]){
                    nums[i]=nums[i+1];
                    isModify=true;
                }else {
                    return false;
                }
            }
        }
        return true;
    }

8.子數組最大的和

/*
    * 子數組最大的和
    * 思路:
    * a.max作爲最大的和,隨時更新
    * b.用curSum累計,考慮某元素nums[i]時,如果curSum<0,一定會curSum+nums[i]<nums[i]。所以如果curSum<0,就將curSum置爲0
    * */
    public int maxSubArray(int[] nums) {
        int max=Integer.MIN_VALUE;
        int curSum=0;
        for (int i=0;i<nums.length;i++){
            curSum+=nums[i];
            max=Math.max(max,curSum);
            if(curSum<0) curSum=0;
        }
        return max;
    }

9. 分隔字符串使同種字符出現在一起

    /*
     分隔字符串使同種字符出現在一起
    Input: S = "ababcbacadefegdehijhklij"
    Output: [9,7,8]
    Explanation:
    The partition is "ababcbaca", "defegde", "hijhklij".
    This is a partition so that each letter appears in at most one part.
    A partition like "ababcbacadefegde", "hijhklij" is incorrect, because it splits S into less parts.
    * */
    public List<Integer> partitionLabels(String S) {
        /*
        * 思路:每次遍歷到一個元素時,就要考慮後面還有沒有相同的元素,如果有,就要分到一組。
        * 做法:遍歷整個字符串,遍歷到元素curElem時,就從後向前查找,找到最後一個與之相同的元素,此時,這兩個區間爲一組,還要考慮在這個區間的元素
        * */
        char str[]=S.toCharArray();
        char label[]=new char[30];
        for(int i=0;i<str.length;i++){
            label[str[i]-'a']++;
        }
        List<Integer> list=new LinkedList<>();
        int firstIndex=0;//區間首元素索引
        int lastIndex=0;//區間尾元素索引
        int curIndex=0;//當前元素
        while (firstIndex<str.length){
            lastIndex=Math.max(lastIndex,S.lastIndexOf(S.charAt(curIndex)));//每次遍歷一個元素,就要找到字符串中最後一個與之相同的元素。並更新區間尾元素索引
            if(curIndex==lastIndex){//直到當前元素是區間尾元素(與當前元素相同的最後一個元素是自己,說明同種字符都在一個區間內了),此時首尾元素之間的所有可以分爲一組。
                list.add(lastIndex-firstIndex+1);
                firstIndex=lastIndex+1;//更新下一個區間的首元素索引
                curIndex=lastIndex+1;//更新下一個區間的尾元素索引
            }else {
                curIndex++;
            }
        }
        return list;
    }

10.集合覆蓋問題:

假設存在如下表的需要付費的廣播臺,以及廣播臺信號可以覆蓋的地區。 如何選擇最少的廣播臺,讓所有的地區都可以接收到信號。
在這裏插入圖片描述
解題思路:
1.將每個地區可以接收到信號的廣播臺 的數量 放入到map裏。
ID–>2
NV–>3

2.遍歷每個廣播,如果該廣播臺能覆蓋的地區,都可以由其他 廣播臺覆蓋到(map中的value都大於1),那麼拋棄該廣播臺,同時將map中 該廣播臺能覆蓋的地區的value值減1
每次都要選擇一個 能覆蓋到其他廣播臺覆蓋不到的地區的 廣播臺。

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