LeetCode有代表性的題解---貪心思想(三)

1. 分配餅乾

455.分發餅乾

Input: grid[1,3], size[1,2,4]
Output: 2

題目描述:每個孩子都有一個滿足度 grid,每個餅乾都有一個大小 size,只有餅乾的大小大於等於一個孩子的滿足度,該孩子纔會獲得滿足。求解最多可以獲得滿足的孩子數量。

  1. 給一個孩子的餅乾應當儘量小並且又能滿足該孩子,這樣大餅乾才能拿來給滿足度比較大的孩子。
  2. 因爲滿足度最小的孩子最容易得到滿足,所以先滿足滿足度最小的孩子。

解題思想:在以上的解法中,我們只在每次分配時餅乾時選擇一種看起來是當前最優的分配方法,但無法保證這種局部最優的分配方法最後能得到全局最優解。我們假設能得到全局最優解,並使用反證法進行證明,即假設存在一種比我們使用的貪心策略更優的最優策略。如果不存在這種最優策略,表示貪心策略就是最優策略,得到的解也就是全局最優解。

JAVA代碼:

class Solution {
    //貪心問題
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);//先對孩子的胃口值進行排序
        Arrays.sort(s);//再對餅乾值進行排序
        int gi=0;
        int si=0;
        while(gi<=g.length-1&&si<=s.length-1){
            if(g[gi]<=s[si]){
                gi++;
            }
            si++;
        }
        return gi;
    }
}

2.不重疊的區間個數

435.無重疊區間  (452類似)

    給定一個區間的集合,找到需要移除區間的最小數量,使剩餘區間互不重疊。

注意:

    可以認爲區間的終點總是大於它的起點。區間 [1,2] 和 [2,3] 的邊界相互“接觸”,但沒有相互重疊。
示例 1:

    輸入: [ [1,2], [2,3], [3,4], [1,3] ]

    輸出: 1

    解釋: 移除 [1,3] 後,剩下的區間沒有重疊。
示例 2:

    輸入: [ [1,2], [1,2], [1,2] ]

    輸出: 2

    解釋: 你需要移除兩個 [1,2] 來使剩下的區間沒有重疊。
示例 3:

    輸入: [ [1,2], [2,3] ]

    輸出: 0

    解釋: 你不需要移除任何區間,因爲它們已經是無重疊的了。

解題思想:貪心策略:統計不重合的最大區間集合總數,所有任務的數量-不重合的最大區間集合總數=刪除的重疊區間數。 按照每個區間的end排序(結束時間最早,比如你一天要參加幾個活動,這個活動開始的多早其實不重要,重要的是你結束的多早,早晨7點就開始瞭然後一搞搞一天,那你今天也就只能參加這一個活動;但如果這個活動開始的不早,比如9點纔開始,但是隨便搞搞10點就結束了,那你接下來就還有大半天的時間可以參加其他活動),將第一個區間的end設置爲默認end,依次遍歷排序後的其他區間,進行重疊性判斷。

JAVA代碼:

import java.util.Arrays;
import java.util.Comparator;

/**
 * Created by 高先森 on 2020/6/20.
 */
public class leetcode_435_Non_overlappingIntervals {
    public static void main(String[] args){
        int[][] ints={{1,2},{2,3},{3,4},{1,3}};
        //解題思想:貪心策略
        //    統計不重合的最大區間集合總數,所有任務的數量-不重合的最大區間集合總數=刪除的重疊區間數。
        //    按照每個區間的end排序,將第一個區間的end設置爲默認end,依次遍歷排序後的其他區間,進行重疊性判斷
        System.out.println(eraseOverlapIntervals(ints));

    }
    public static int eraseOverlapIntervals(int[][] intervals) {
        if(intervals.length==0)
            return 0;
        //1.二維數組N*2存放的每個任務的起止時間star和end,先按照end對二位數組進行從小到大排序
        Arrays.sort(intervals, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[1]-o2[1];//從小到大排序
            }
        });

        //2.選取第一個區間的end爲默認end,依次遍歷intervals其它的區間,進行重疊性判斷
        int temp_end=intervals[0][1]; ////第一個任務end最小,所以第一個選出第一個任務的end
        int start,end,count=1;
        for(int[] inte:intervals){
            start=inte[0];
            end=inte[1];
            //如果當前區間的start在已經選個的區間中最後一個end之前,則當前區間重疊,忽略處理
            if(start<temp_end)//相等不屬於重合
                continue;
            //否則:當前區間不重疊,作爲可選任務區間,總數加1,所有區間的end發生改變
            count++;
            temp_end=end;
        }
        //3.返回要刪除的區間數目
        return  intervals.length-count;
    }
}

3. 射箭刺破氣球

452. 用最少數量的箭引爆氣球

       在二維空間中有許多球形的氣球。對於每個氣球,提供的輸入是水平方向上,氣球直徑的開始和結束座標。由於它是水平的,所以y座標並不重要,因此只要知道開始和結束的x座標就足夠了。開始座標總是小於結束座標。平面內最多存在10的4次個氣球。一支弓箭可以沿着x軸從不同點完全垂直地射出。在座標x處射出一支箭,若有一個氣球的直徑的開始和結束座標爲 xstart,xend, 且滿足  xstart ≤ x ≤ xend,則該氣球會被引爆。可以射出的弓箭的數量沒有限制。 弓箭一旦被射出之後,可以無限地前進。我們想找到使得所有氣球全部被引爆,所需的弓箭的最小數量。

Example:

輸入:
[[10,16], [2,8], [1,6], [7,12]]

輸出:
2

解釋:
對於該樣例,我們可以在x = 6(射爆[2,8],[1,6]兩個氣球)和 x = 11(射爆另外兩個氣球)。

解題思想:使用貪心策略,因爲一支箭可以射穿重疊區間內的氣球,每個不重疊區間內的氣球都額外需要一直箭射穿,將此問題轉換爲不重合的最大區間集合總數(不重疊區間的數目也就是所需要的最少弓箭數目),算法如下:

算法:
    根據 x_end 將氣球進行排序。
    初始化 first_end 爲第一個氣球結束的座標 points[0][1]。
    初始化箭的數量 arrows = 1。
    遍歷所有的氣球:
        如果氣球的開始座標小於等於first_end:(邊界可以算重合,能射穿)
            跳過當前氣球區間(重疊:同一支箭可以射穿)
        否則
            增加箭的數量(需要額外一支弓箭射穿當前氣球)。
            將 first_end 設置爲當前氣球的 x_end。
    返回 arrows。

JAVA代碼如下;

   public static int findMinArrowShots(int[][] points) {
        //貪心策略:
        //1.先按照氣球的end進行排序
        Arrays.sort(points, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[1]-o2[1];//從小到大排序
            }
        });
        //2.遍歷每個氣球區間,進行重疊性判斷(用最少數量的箭引爆氣球)
//        算法:
//            根據 x_end 將氣球進行排序。
//            初始化 first_end 爲第一個氣球結束的座標 points[0][1]。
//            初始化箭的數量 arrows = 1。
//            遍歷所有的氣球:
//                如果氣球的開始座標大於 first_end:
//                    則增加箭的數量。
//                    將 first_end 設置爲當前氣球的 x_end。
//            否則跳過當前氣球區間(重疊:同一支箭可以射穿)
//            返回 arrows。

        int temp_end=points[0][1],num=1;//對temp_end進行初始話賦值,一開始弓箭的數目爲1
        int start,end;
        for(int[] p:points) {
            start = p[0];
            end = p[1];
            if (start <= temp_end) {//如果當前氣球的start區間小於temp_end,說明可以用同一支弓箭射穿,此處邊界處相等也算射穿
                continue;
            }
            num++;//否則還得額外增加一支弓箭才能射穿當前氣球
            temp_end = end;
        }
        return num;
    }

4. 根據身高和序號重組隊列

406. 根據身高重建隊列

假設有打亂順序的一羣人站成一個隊列。 每個人由一個整數對(h, k)表示,其中h是這個人的身高,k是排在這個人前面且身高大於或等於h的人數。 編寫一個算法來重建這個隊列。

注意:
總人數少於1100人。

示例

輸入:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]

輸出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]

解題思路:

     解題思路:先排序再插入
     1.排序規則:按照身高從大到小排序,身高相同的按照K值從小到大排序(因爲身高相同時候,k的值依次遞增才符合題意)
     2.遍歷排序後的數組,根據K插入到K的位置上
     核心思想:高個子先站好位,矮個子插入到K位置上,對於當前矮個子而言:前面肯定有K個高個子以及站好位,
              對於當前高個子而言:矮個子的插入對高個子本身的k不受影響

JAVA代碼如下:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * Created by 高先森 on 2020/6/21.
 */
public class leetcode_406_QueueReconstructionbyHeight {
    public static void main(String[] args){
        int[][] ints={{7,0},{4,4}, {7,1}, {5,0}, {6,1}, {5,2}};
        //[9,0],[7,0],[1,9],[3,0],[2,7],[5,3],[6,0],[3,4],[6,2],[5,2]
        int[][] ints1={{9,0},{7,0},{1,9},{3,0},{2,7},{5,3},{6,0},{3,4},{6,2},{5,2}};
        reconstructQueue(ints1);

    }
//     解題思路:先排序再插入
//     1.排序規則:按照身高從大到小排序,身高相同的按照K值從小到大排序(因爲身高相同時候,k的值一次遞增才符合題意)
//     2.遍歷排序後的數組,根據K插入到K的位置上
//     核心思想:高個子先站好位,矮個子插入到K位置上,對於當前矮個子而言:前面肯定有K個高個子,
//              對於高個子而言:矮個子的插入對高個子的k不受影響

//示例一:
//[9,0],[7,0],[1,9],[3,0],[2,7],[5,3],[6,0],[3,4],[6,2],[5,2]
//排序:[9,0],[7,0],[6,0],[6,2],[5,2],[5,3],[3,0],[3,4],[2,7],[1,9]
//一個一個插入到自己的k位置上
//[9,0]
//[7,0],[9,0]
//[6,0],[7,0],[9,0]
//[6,0],[7,0],[6,2],[9,0]
//[6,0],[7,0],[5,2],[6,2],[9,0]
//[6,0],[7,0],[5,2],[5,3],[6,2],[9,0]
//[3,0],[6,0],[7,0],[5,2],[5,3],[6,2],[9,0]
//[3,0],[6,0],[7,0],[5,2],[3,4],[5,3],[6,2],[9,0]
//[3,0],[6,0],[7,0],[5,2],[3,4],[5,3],[6,2],[2,7],[9,0]
//[3,0],[6,0],[7,0],[5,2],[3,4],[5,3],[6,2],[2,7],[9,0],[1,9]

//示例二:
    // [7,0], [7,1], [6,1], [5,0], [5,2], [4,4]
    // 再一個一個插入。
    // [7,0]
    // [7,0], [7,1]
    // [7,0], [6,1], [7,1]
    // [5,0], [7,0], [6,1], [7,1]
    // [5,0], [7,0], [5,2], [6,1], [7,1]
    // [5,0], [7,0], [5,2], [6,1], [4,4], [7,1]

    public static int[][] reconstructQueue(int[][] people) {
        //1.按照身高從大到小排序,身高相同的按照K值從小到大排序(因爲身高相同時候,k的值一次遞增才符合題意)
        Arrays.sort(people, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                //身高不等,按照身高從高到低排序;身高相等按照k值從小到大排序
                return o1[0]==o2[0]?o1[1]-o2[1]:o2[0]-o1[0]; //個人理解:return返回大於0時,o1和o2交換
            }
        });
        //2.在K位置上插入當前(h,k)
        List<int[]> list=new ArrayList<>();
        for(int[] p:people)
            list.add(p[1],p);//參數1爲index(當前人(h,k)的k),參數2爲添加到list中的人(h,k)

        return list.toArray(new int[list.size()][2]);
    }
}

5. 買賣股票最大的收益

121.買賣股票的最佳時機

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

如果你最多隻允許完成一筆交易(即買入和賣出一支股票一次),設計一個算法來計算你所能獲取的最大利潤。

注意:你不能在買入股票前賣出股票。

示例 1:

輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
     注意利潤不能是 7-1 = 6, 因爲賣出價格需要大於買入價格;同時,你不能在買入前賣出股票。
示例 2:

輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。

解題思想:

      用minPrice記錄最低的股票買入價格,初始話爲第一支股票的價格,依次遍歷剩餘的股票,用maxValue記錄當前股票價賣出獲得的最大價值。

JAVA代碼如下:

class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length<=0)
            return 0;
        int minPrice=prices[0];
        int maxValue=0;
        for(int price:prices){
            if(price<minPrice)
                minPrice =price;
            else
                maxValue=maxValue>(price-minPrice)?maxValue:(price-minPrice);
        }
        return maxValue;
    }
}

6. 買賣股票的最大收益 II

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

設計一個算法來計算你所能獲取的最大利潤。你可以儘可能地完成更多的交易(多次買賣一支股票)。

注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

 

示例 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 天接連購買股票,之後再將它們賣出。
     因爲這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。
示例 3:

輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。

解題思想:

      循環遍歷每支股票,買入時機:如果當前手裏不持有股票並且之後的股票價會長則買入(flag==0&&prices[i]<prices[i+1]),賣出時機:手裏持有股票並且當前股票價格大於持有股票的買入價格,並且後續發生降價(如果繼續增值prices[i]<prices[i+1],不會選擇賣出)。

JAVA代碼如下:

/**
 * Created by 高先森 on 2020/6/21.
 */
public class leetcode_121_121_bestTimeToBuyAndSellStock {
    public static void main(String[] args){
        int[] ints={7,1,5,3,6,4};
        int[] ints1={1,2,3,4,5};
        System.out.println(maxProfit122(ints1));
    }

    public static int maxProfit122(int[] prices) {
        if(prices.length<=0)
            return 0;
        int buyPrice=0;
        int value=0;
        int flag=0;//用於標記手裏是否持有股票(要求手裏同時只能持有一支股票)
        int i;
        for(i=0;i<prices.length-1;i++){
            //1.如果當前手裏不持有股票並且之後的股票價會長則買入
            if(flag==0&&prices[i]<prices[i+1]){
                buyPrice=prices[i];
                flag=1;//當前手裏持有股票
            }else if(flag==1&&prices[i]>buyPrice&&prices[i]>prices[i+1]){
                //2.賣出:手裏持有股票並且當前股票價格大於持有股票的買入價格,並且發生降價(如果繼續增值prices[i]<prices[i+1],不會選擇賣出)
                value=value+prices[i]-buyPrice;//盈利
                flag=0;
            }
        }
        if(flag==1) //因爲i遍歷到最後一直股票會退出,如果手裏仍持有股票,則最後一直股票價格一定大於持有股票價格,賣出
            value=value+(prices[i]-buyPrice);
        return value;
    }
}

7.種花問題

     假設你有一個很長的花壇,一部分地塊種植了花,另一部分卻沒有。可是,花卉不能種植在相鄰的地塊上,它們會爭奪水源,兩者都會死去。給定一個花壇(表示爲一個數組包含0和1,其中0表示沒種植花,1表示種植了花),和一個數 n 。能否在不打破種植規則的情況下種入 n 朵花?能則返回True,不能則返回False。

示例 1:

輸入: flowerbed = [1,0,0,0,1], n = 1
輸出: True
示例 2:

輸入: flowerbed = [1,0,0,0,1], n = 2
輸出: False

注意:

數組內已種好的花不會違反種植規則。
輸入的數組長度範圍爲 [1, 20000]。
n 是非負整數,且不會超過輸入數組的大小

解題思想:使用left,right分別標記當前位置前面和後面的種花情況,如果都是0,則當前位置可以種。

JAVA代碼:

/**
 * Created by 高先森 on 2020/6/24.
 */
public class leetcode_605_canPlaceFlowers {
    public static void main(String[] args){
        int[] ints={1,0,0,0,1};
         System.out.println(canPlaceFlowers(ints,2));

    }
    public static boolean canPlaceFlowers(int[] flowerbed, int n) {
        int maxnum=0,left,right;
        int len=flowerbed.length;

        for(int i=0;i<len;i++){
            //1.已經種植的直接略過
            if(flowerbed[i]==1)
                continue;
            //2.確定left值
            left=(i==0?flowerbed[i]:flowerbed[i-1]);
            //3.確定right值
            right=(i==len-1?flowerbed[i]:flowerbed[i+1]);
            //4.當前位置i可以種植
            if(left==0&&right==0){
                flowerbed[i]=1;
                maxnum++;
            }
        }
        return maxnum>=n?true:false;
    }
}

8.判斷是否爲子序列

給定字符串 s 和 t ,判斷 s 是否爲 t 的子序列。

你可以認爲 s 和 t 中僅包含英文小寫字母。字符串 t 可能會很長(長度 ~= 500,000),而 s 是個短字符串(長度 <=100)。

字符串的一個子序列是原始字符串刪除一些(也可以不刪除)字符而不改變剩餘字符相對位置形成的新字符串。(例如,"ace"是"abcde"的一個子序列,而"aec"不是)。

示例 1:
s = "abc", t = "ahbgdc"

返回 true.

示例 2:
s = "axc", t = "ahbgdc"

返回 false.

解題思想:1.使用雙指針分別遍歷s和t進行字符判斷 2.遍歷短串s,利用t.indexof(參數1,參數2)進行字符匹配判斷

JAVA代碼:

/**
 * Created by 高先森 on 2020/6/24.
 */
public class leetcode_392_isSubsequence {
    public static void main(String[] args){
        isSubsequence1("abE","ahbgdc");
    }
    public static boolean isSubsequence(String s, String t) {
        int sLen=s.length();
        int tLen=t.length();
        int i=0,j=0;
        while (i<sLen&&j<tLen){
            if(s.charAt(i)==t.charAt(j)){
                i++;
                j++;
            }else
                j++;
        }
        return i==sLen?true:false;
    }
    public static boolean isSubsequence1(String s, String t) {
        int index=-1;
        for(char ch:s.toCharArray()){
            //從index+1的位置開始匹配ch,返回匹配到的t中ch的下標
            index=t.indexOf(ch,index+1);
            if(index==-1)//如果沒有匹配到則返回-1
                return false;
        }
        return true;
    }
}

9.修改數組中的一個數使其變爲非遞減

給你一個長度爲 n 的整數數組,請你判斷在 最多 改變 1 個元素的情況下,該數組能否變成一個非遞減數列。

我們是這樣定義一個非遞減數列的: 對於數組中所有的 i (0 <= i <= n-2),總滿足 nums[i] <= nums[i + 1]。

示例 1:

輸入: nums = [4,2,3]
輸出: true
解釋: 你可以通過把第一個4變成1來使得它成爲一個非遞減數列。
示例 2:

輸入: nums = [4,2,1]
輸出: false
解釋: 你不能在只改變一個元素的情況下將其變爲非遞減數列。

說明:

1 <= n <= 10 ^ 4
- 10 ^ 5 <= nums[i] <= 10 ^ 5

解題思路:對於給定的數組,如果出現num[i]>num[i+1],需要變更數字的位置爲i或者i+1。(1)當num[i-1]<num[i+1] eg:1(i-1) 3(i) 2(i+1),最優的變更爲num[i]=num[i+1](1 2 2) ,因爲如果變更爲num[i+1]=num[i](1 3 3)則更違反非遞減的規則。(2)當num[i-1]>num[i+1] eg:2 3 1,則只能使 num[i+1]=num[i](2 3 3)。還需要設置個變量count統計變更次數,如果次數超過1則直接返回false。

JAVA代碼:

/**
 * Created by 高先森 on 2020/6/24.
 */
public class leetcode_665_NoneDecreasingArray {
    public static void main(String[] args){
        int[] ints={4,2,1};
        System.out.println(checkPossibility(ints));
    }
    public static boolean checkPossibility(int[] nums) {
       int len=nums.length;
       int count=0;//用於標記交換的次數,超過一次爲不符合
       //1.如果數組的長度小於三,只要通過一次變換一定能滿足非遞減
       if(len<3)
           return true;
       //2.大於三時對 i-1,i,i+1位置的數字就行判斷
       //1 3 2
       //2 3 1
       for(int i=0;i<len-1;i++){
           if(nums[i]<=nums[i+1])
               continue;
           //存在nums[i]>nums[i+1],需要增加一次交換
           count++;
           if(count>1)//交換次數大於一直接返回
               return false;
           if(i==0)
               nums[i]=nums[i+1];
           //如果i>0且num[i-1]<nums[i+1],eg:1 3 2 儘量將i位置的數字置小更易滿足非遞減1 2 2 (1 3 3 更容易引起num[i+1]>num[i+2])
           else if(i>0&&nums[i-1]<nums[i+1])
               nums[i]=nums[i+1];
           else{ //eg:2 3 1 只能將num[i+1]置爲num[i] 2 3 3才符合
               nums[i+1]=nums[i];
           }
       }
       return true;
    }
}

10.最大子數組和

給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。

示例:

輸入: [-2,1,-3,4,-1,2,1,-5,4],
輸出: 6
解釋: 連續子數組 [4,-1,2,1] 的和最大,爲 6。
進階:

如果你已經實現複雜度爲 O(n) 的解法,嘗試使用更爲精妙的分治法求解。

解題思想:動態規劃:dp[i]爲到i位置最大子序列的和 dp[i]=Math.max(dp[i-1]+num[i],num[i])

JAVA代碼:

/**
 * Created by 高先森 on 2020/6/24.
 */
public class leetcode_53_MaxinumSubarray {
    public static void main(String[] args){
        int[] ints={-2,1,-3,4,-1,2,1,-5,4};
        //System.out.println(maxSubArray(ints));
        System.out.println(maxSubArray1(ints));
    }
    //1.動態規劃:dp[i]爲到i位置最大子序列的和
    //nums:{-2,1,-3,4,-1,2,1,-5,4}
    //dp[i]內容:-2,1,-2,4,3,5,6,1,5
    public static int maxSubArray(int[] nums) {
        if(nums.length==0||nums==null)
            return 0;
        int[] dp=new int[nums.length];
        int maxRes=nums[0];//隨機初始化
        dp[0]=nums[0];
        for(int i=1;i<nums.length;i++){
            //dp[i]爲到i位置的最大子序列和,dp[i]的值爲到i-1位置最大子序列的和加上當前數和當前數比較的最大值
            dp[i]=Math.max(dp[i-1]+nums[i],nums[i]);
            if(maxRes<dp[i])
                maxRes=dp[i];
        }
        return maxRes;
    }
    //2.降低空間複雜度的動態規劃,每個狀態只與前一個狀態有關,所以爲了降低空間複雜度只用一個變量來保存
    public static int maxSubArray1(int[] nums) {
        if(nums.length==0||nums==null)
            return 0;
        //sum初始化nums[0],sum用於記錄每個子序列的和,maxRes用於記錄所有子序列中的最大值
        int sum=nums[0],maxRes=sum;
        for(int i=1;i<nums.length;i++){
            //dp[i]爲到i位置的最大子序列和,dp[i]的值爲到i-1位置最大子序列的和加上當前數和當前數比較的最大值
            sum=Math.max(sum+nums[i],nums[i]);
            if(maxRes<sum)//maxRes爲所有子序列中的最大子序列和
                maxRes=sum;
        }
        return maxRes;
    }
}

11.劃分字母區間使相同字母出現在同一區間

       字符串 S 由小寫字母組成。我們要把這個字符串劃分爲儘可能多的片段,同一個字母只會出現在其中的一個片段。返回一個表示每個字符串片段的長度的列表。

示例 1:

輸入:S = "ababcbacadefegdehijhklij"
輸出:[9,7,8]
解釋:
劃分結果爲 "ababcbaca", "defegde", "hijhklij"。
每個字母最多出現在一個片段中。
像 "ababcbacadefegde", "hijhklij" 的劃分是錯誤的,因爲劃分的片段數較少。

解決思路:

     定義數組 charAt[26] 來表示字符 字符串中每個char最後一次出現的下標。定義 left和right來表示當前區間的左邊界和右邊界。如果遇到的字符最後一次出現的位置下標大於 right, 就讓 right=charAt[i] 來拓展當前的區間。當遍歷到了當前區間的末尾時(即 i==right ),把當前區間加入答案,同時將 left 設爲 i+1 去找下一個區間。

JAVA代碼:

import java.util.ArrayList;
import java.util.List;

/**
 * Created by 高先森 on 2020/6/24.
 */
public class leetcode_763_PartitionLabels {
    public static void main(String[] args){
        System.out.println(partitionLabels1("ababcbacadefegdehijhklij"));
    }
    //方法一:常規解法
    public static List<Integer> partitionLabels(String S) {
        int len=S.length();
        //1.定義一個list存放結果
        List<Integer> list=new ArrayList<>();
        //2.將String轉換爲字符數組
        char[] strChars=S.toCharArray();
        //3.left標記當前區間的最左端,right標記當前區間的最右端,j>=left&&j<=right,通過遍歷j來擴大(區間)right的範圍
        int left,right,j;
        //4.尋找區間
        for(int i=0;i<len;){
            left=i;
            right=S.lastIndexOf(strChars[left]);
            j=left;
            while (j<right){
                j++;
                right=right>S.lastIndexOf(strChars[j])?right:S.lastIndexOf(strChars[j]);
            }
            //將劃分的區間結果進行存儲
            list.add(right-left+1);
            i=right+1;//下一個子區間左邊界
        }
        return list;
    }
    //方法二:貪心思想
    public static List<Integer> partitionLabels1(String S) {
        //1.使用charAt數組標記每個字符在串中出現的最後位置
        int[] charAt=new int[26];
        List<Integer> list=new ArrayList<>();
        int len=S.length();
        for(int i=0;i<len;i++){
            charAt[S.charAt(i)-'a']=i;
        }
        //2.進行最大子區間的劃分
        int left=0,right=0;//left標記當前可劃分的最大子區間的左端,right爲右端
        for(int i=0;i<len;i++){
            //在left和right區間內的字母可能會擴大區間的右邊界 eg:defegde 開始:left=0,right=5,下一次遍歷到e,right擴充爲6
            right=Math.max(right,charAt[S.charAt(i)-'a']);
            if(i==right){//當前最大子區間確定完畢
                list.add(right-left+1);
                left=i+1;//下一個子區間的左邊界
            }
        }
        return list;
    }
}

 

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