LeetCode 贪心专题 java代码 思路 题解

不断更新中

题解

455 分发饼干问题

题目详情:假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

题目分析:该题是一个很经典的,也很简单的贪心问题,可以很舒服的引入贪心的思想。
我们的目标是满足尽可能更多的孩子。即,我们只考虑满足孩子的数量,而孩子本身并没有优先性。那么此时,我们先满足胃口小的孩子,从小尺寸的饼干开始,如此进行

所以这里我们贪心的思路是:从小开始。
代码中是用到了双指针,这样会更快,双指针的使用基于这样一个事实,满足过的孩子或饼干不能再用了,并且没法使用的饼干就不再考虑了。

class Solution {
    public int findContentChildren(int[] g,int[] s){
        Arrays.sort(g);
        Arrays.sort(s);
        int result = 0;
        int gi=0,si=0;
        while (gi < g.length && si < s.length) {
            if (g[gi] <= s[si]) {
                gi++;
            }
            si++;
        }

        return gi;
    }
}

435 无重叠区间

题目详情:给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

题目分析:该题是一个区域重叠问题,后面的很多问题都是它的一个变体,需要十分十分注意注意,好好理解。当然本题也可以用动态规划做,不过时间效率略低,还是贪心更适合一些,同时它的变体们用贪心也是很好做的。

这里因为是区域,可以说是分布在一维空间上,所以这里我们需要对它们进行排序。这样才便于我们去分析。不然问题会十分复杂。
排序的规则是先按照右边界排序,右边界相等的话按照左边界排序,都是升序。

进行排序之后,我们的区域可以说就是很有序的分布在一维空间上了。有这样一个简单的事实,我们从第一区域开始,它会有几种情况。
第一种情况是,后面的左边界都大于它(右边界大于他是显然,因为我们是这样排序的)
在这里插入图片描述

那么很明显,我们需要在于区域1重叠区域保留一个(或者说是删除其他,来保证不重叠),那我们保留谁呢,2,3都与1重叠,删除哪个呢,那么很明显,保留1最好,因为它的右边界最小,它会留给右边的空白区域更多,它让后面的重叠可能更小所以这里保留1,删除2,3个。
在完成这个操作后,我们不去考虑前面的了,直接将不与区域1重叠的区域4当做新区域1 。如此便一直迭代下去,直到最后一个便可以找完所有的。

当然也会遇到一种不一样的情况:
在这里插入图片描述
后面的区域中有的左边界比区域1的左边界更左,此时也很简单,还是一样的处理,我们不用在乎左边界,因为我们的目标是从左边的1,2,3中挑选出最靠左的区域。然后让区域4作为新的区域1,如此迭代下去。

讲到这,应该你已经明白了,这里的贪心就是在于,从左往右,在重叠的区域们中每次都保留靠左的区域,使得右边的空白空间尽可能大,这样右边重叠的可能性也就会小。(其实也有点像上面的饼干分配问题,从小开始)

class Solution {
    public int eraseOverlapIntervals(int[][] intervals){
        if(intervals.length==0){//判断数组是否为空
            return 0;
        }
        //进行排序,按照开始点进行排序
        Arrays.sort(intervals, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {//按照右边界排序,如果做边界相等,则按照左边界重复。最终左边界小的,
                if(o1[1]==o2[1]){
                    return o1[0]-o2[0];
                }
                return o1[1]-o2[1];
            }
        });
        int count = 0;
        int temp=0;
        //贪心的思想,每次寻找最短的,因为已经排好序
        //对于这个贪心的思想,怎么说呢,如果重复,则选择当前情况下结束最早的,那么肯定比其他的要好
        for(int i =0;i<intervals.length;i++){
            if(i==0){
                count++;
            }
            //进行比较,看看是否重复
            else {
                if(intervals[i][0]>=intervals[temp][1]){//这么比较的原因在于,按照右边界排好了序,只用考虑左边界
                    temp = i;
                    count++;
                }
            }
        }
        return intervals.length-count;
    }
}

452. 用最少数量的箭引爆气球

题目详情:在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。

一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量

在这里插入图片描述
题目分析:这题可以说是435题的变体,也是个区域重叠问题。弄懂了435,这题也就很简单了。对于本题,它的目标实际就是,找哪些区域同时重复的最多。

题目思路:对该题,我们也首先进行排序,跟435一样的排序,此处不赘述。
然后从左往右遍历每个区域,我们秉承这样一个思想,因为已经排序好了,区域们都映射到了一维空间上,那么我们当前遍历的区域是肯定要射一箭的,那么能找到与它重复的越多越好,那么可以将它们一起给射了。以这样一个思路,不断的进行,就能最少次数的将所有的气球射玩。

所以这里的贪心在于,从左往右,对于遍历到的每个未破的气球,每次将与它重复的气球都射爆(遍历到该气球时,尽可能射爆更多的气球,或者说射的位置尽可能靠右,这样才能射爆的更多)。

class Solution {
    public int findMinArrowShots(int[][] points) {
        if (points.length == 0) {
            return 0;
        }
        //对数组进行排序
        Arrays.sort(points, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {//按照右边界排序,如果做边界相等,则按照左边界重复。最终左边界小的,
                if (o1[1] == o2[1]) {
                    return o1[0] - o2[0];
                }
                return o1[1] - o2[1];
            }
        });
        int result = 0;//结果
        int area = 0;//当前访问到的区域
        while(area<points.length){
            //判断和当前区域重复的区域
            int i =area+1;
            while(i<points.length){
                if(points[i][0]<=points[area][1]){
                    i++;
                }
                else {//一旦没有了重叠的可能,就停止
                    break;
                }
            }
            result++;
            area=i;
        }
        return result;
    }
}

当然,这里的排序可以用左边界来(对这个问题可以,不过结果不变)。

406. 根据身高重建队列

问题详情:假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。

问题分析:关于这个问题,有个很巧妙的一点就是:高的人对矮的人是视而不见的,矮的人排在它们右边不影响,排在左边也不影响,删掉也不影响,高的人只会在乎比他更高或者一样高的人。
所以这就决定了我们先放置谁的问题–最高的人,最高的人谁也不在乎,自己影响自己,所以从他们开始,然后是次高,一旦最高的确定,次高的只需要考虑最高的和自己,其他矮的也不需要考虑,如此从高到低来放置,从而可以保证一点,放置后面的时,不会导致先前放置的出问题。
所以在贪心问题中,我们时常得考虑一点,哪一类会对哪一类有影响,哪一类是顶多只考虑自己的,被影响的越少,越应该被我们先考虑。

所以,我们将所有的元素按照身高h分成最多h类,在同一类中,明显是k小的在前面,k大的在后面,当然,在同一类中的顺序也就相等于他们在真实队列中的相对顺序。

如此一来,我们便进行好了排序。下面就是进行重建队列了。
明显,这就是n个序列进行组合的问题。

那么很简单的一个思路就是,我先排最高的人,它们的相对顺序是k从小到大,然后再排次高的,次高的需要考虑最高的人,所以需要挨个的比较大小,然后找到属于自己的位置进行插入。然后再是次次高,如此迭代下去。
这确实是没问题的,但是,我们是不是可以简化一下呢?
是的,可以简化。我就以次高来举例,当我要插入次高的人们中第一人(h,k)时,应该插到哪里呢?
按照之前的我需要一个个去比较,但实际上,不用比较,直接插到第k个就行,为什么呢?
很简单,因为之前已经安排好的人都比该人高或者一样高,所以理所应当这个人就放在第k个位置。同样,次高的第二个人要是放在他的第“k”个位置,当然,此时第二人要插入的队列是第一个人已经插入过的队列,是现在时刻的队列。
如此一来,对于每个人来说,它就直接插到第k个位置就好了,因为它需要考虑的所有人都比它高或者和它一样高

    public int[][] reconstructQueue(int[][] people){
        //对数组进行排序
        Arrays.sort(people, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {//按照右边界排序,如果做边界相等,则按照左边界重复。最终左边界小的,
                if (o2[0] == o1[0]) {
                    return o1[1] - o2[1];
                }
                return o2[0] - o1[0];
            }
        });
        List<int[]> queue = new ArrayList<>();
        for(int[] p:people){
            queue.add(p[1],p);
        }

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

121. 买卖股票的最佳时机

问题详情:给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

注意:你不能在买入股票前卖出股票。

问题分析:该题最简单的思路就是两层for循环的O(n^2)解法了,这个肯定很慢,没办法。
更好的思路是O(n)的。
假设第一个是最小的,选中第一个,让后往后遍历,差值越来越大,则利润越来越高,一旦出现负值,则说明遇到了当前遍历情况下的最小值,即最低成本价,选中该点,继续进行。
这个思路能够保证出现负值之前,最大值求出来了已经,就是p[i-1]-p[0]那么,以负值那个元素为最小值,往后继续寻找就行。
这里的贪心在于,差值尽可能大。

public int maxProfit(int[] prices){
        //该方法的时间复杂度是O(n)
        int max = 0;
        int index_min = 0;
        int i = 0;
        while(i<prices.length){
            if(prices[i]-prices[index_min]>max){
                max = prices[i]-prices[index_min];
            }
            if(prices[i]-prices[index_min]<0){//出现负值,出现更小值
                index_min = i;
            }
            i++;
        }
        return max;
    }

122. 买卖股票的最佳时机 II
问题详情:给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

问题分析:本题就是在前一题的基础上进行的,就是多个买卖操作。套用前一题的思路,假设第一个就是最低价,然后往后遍历一旦出现负值就在之前一步卖掉,等等,好像不对的样子。
嗯,这里不是到负值就卖,我们实际的基金股票都希望在哪个点卖呢?最高点,对的,所以需要在最高点卖,什么时候后一个值比前一个小,就说明到最高点了,就可以卖了。然后剩下的就当做一开始那样继续进行。如此便完成了整个求解过程。
其次,之前的代码是比较最大的max,而这里是个累加和,所以需要累加。
总结一下,这里就是低买高卖。
这里的贪心过程就是每一小段都是从最小值开始,从最大值结束,否则就不买。

    public int maxProfit(int[] prices){
        //这个问题就是一个分段的前一个问题了
        //按照之前121的思路,当假设第一个最低价,从第一个开始,往后走,到负值之前,那肯定是一直不卖最好
        //然后到达负值之前卖了,负值再买,然后再看,这个是一样的,如果一直跌,就不买,
        //把前面的比较max改成max+max
        int profit = 0;
        int index_min = 0;//可以理解为买入点更合适
        int i = 0;
        int max = 0;
        while(i<prices.length){
            if(prices[i]-prices[index_min]>max){
                max = prices[i]-prices[index_min];
            }
            if(prices[i]-prices[index_min]<max){//出现负值,出现更小值,买入
                profit += max;
                index_min = i;
                max = 0;
            }
            i++;
            //会有一个问题,一直没有出现负值,这样需要额外对profit赋值一次
        }
        profit += max;
        
        return profit;
    }
### 回答1: 这道题是一道字符串处理题。给定一个字符串 s,它的所有的字符都是小写的英文字母。要求把这个字符串变成一个按照字典序排序的字符串,并且要求在变换过程中只能在原来的字符串中交换两个相邻的字符。 解题思路: - 从前往后扫描字符串 s,找到第一个不满足字典序的字符,记为字符 x。 - 从 x 往后扫描,找到最后一个比 x 大的字符 y,将 x 与 y 交换。 - 将 x 后面的字符串倒序排列,这样就得到了字典序更大的字符串。 下面是 Java 代码的实现: ``` class Solution { public String nextPermutation(String s) { char[] chars = s.toCharArray(); // 从后往前找到第一个不满足字典序的字符 x int i = chars.length - 2; while (i >= 0 && chars[i] >= chars[i + 1]) { i--; } // 如果 i < 0,说明原来的字符串已经是字典序最大的字符串,直接返回倒序排列的字符串 if (i < 0) { reverse(chars, 0, chars.length - 1); return new String(chars); } // 从 x 往后扫描,找到最后一个比 x 大的字符 y int j = chars.length - 1; while (j > i && chars[j] <= chars[i]) { j--; } // 将 x 与 y 交换 swap(chars, i ### 回答2: 题目:LeetCode第38题:报数 题目描述: 给定一个正整数n,输出报数序列前n个数。 报数规则:从1开始报数,数到3的倍数时报Fizz,数到5的倍数时报Buzz,数到同时是3和5的倍数时报FizzBuzz,其他情况下则直接报数。 解题思路: 使用循环遍历1到n的所有数字,按照报数规则进行判断并输出。 具体步骤如下: 1. 创建一个StringBuilder对象res,用于存储报数序列。 2. 使用for循环从1遍历到n。 3. 判断当前数字是否同时是3和5的倍数,如果是,则将"FizzBuzz"添加到res中。 4. 判断当前数字是否是3的倍数,如果是,则将"Fizz"添加到res中。 5. 判断当前数字是否是5的倍数,如果是,则将"Buzz"添加到res中。 6. 如果以上条件都不满足,则将当前数字转换为字符串并添加到res中。 7. 循环结束后,将res转换为字符串并返回。 Java代码如下: ```java public String countAndSay(int n) { StringBuilder res = new StringBuilder(); for (int i = 1; i <= n; i++) { if (i % 3 == 0 && i % 5 == 0) { res.append("FizzBuzz"); } else if (i % 3 == 0) { res.append("Fizz"); } else if (i % 5 == 0) { res.append("Buzz"); } else { res.append(Integer.toString(i)); } } return res.toString(); } ``` 以上代码可以将1到n的报数序列输出,并按照题目要求进行相应转换。 ### 回答3: 题目要求是根据给定的正整数 n,返回一个字符串,该字符串包含从 1 到 n 的所有数字对应的字符串,并且满足以下条件: 1. 如果数字能被 3 整除,则使用字母 "Fizz" 替代该数字。 2. 如果数字能被 5 整除,则使用字母 "Buzz" 替代该数字。 3. 如果数字能同时被 3 和 5 整除,则使用字母 "FizzBuzz" 替代该数字。 解题思路: 利用循环遍历从 1 到 n 的所有数字,使用条件语句判断每个数字是否满足以上三个条件,然后根据条件替换数字并存入结果字符串中,最后返回结果。 Java代码如下: ```java class Solution { public String fizzBuzz(int n) { StringBuilder result = new StringBuilder(); for (int i = 1; i <= n; i++) { if (i % 3 == 0 && i % 5 == 0) { result.append("FizzBuzz"); } else if (i % 3 == 0) { result.append("Fizz"); } else if (i % 5 == 0) { result.append("Buzz"); } else { result.append(i); } if (i != n) { result.append(" "); } } return result.toString(); } } ``` 这段代码使用StringBuilder来构建结果字符串,判断每个数字是否满足条件,并根据条件拼接对应的字符串,每个数字之间用空格隔开。最后将StringBuilder转换成String并返回。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值