LeetCode有代表性的题解---贪心思想(三)

这篇博客详细解析了使用贪心策略解决LeetCode中的一系列问题,包括分配饼干、不重叠区间个数、射箭刺破气球、根据身高和序号重组队列等,展示了贪心算法在解决这些问题上的应用和优势。
摘要由CSDN通过智能技术生成

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]; //个人理解:o1是右侧数,o2是左侧数,当o1[1]-o2[1]<0时交换,eg:  2、1排列,o1==1,o2==2,o1-o2<0交换,实现从小到大排序
            }
        });
        //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;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值