LeetCode刷题——贪心问题

贪心问题

问题描述:贪心算法或贪心思想采用贪心的策略,保证每次操作都是局部最优的,从而使最后得到的结果是全局最优的。

455. 分饼干(Easy)

题目描述
有一群孩子和一堆饼干,每个孩子有一个饥饿度,每个饼干都有一个大小。每个孩子只能吃最多一个饼干,且只有饼干的大小大于孩子的饥饿度时,这个孩子才能吃饱。求解最多有多少孩子可以吃饱。
输入输出样例
输入两个数组,分别代表孩子的饥饿度和饼干的大小。输出最多有多少孩子可以吃饱的数量。

Input: [1,2], [1,2,3]
Output: 2

思路
将孩子饥饿度和饼干大小两个数组都按从小到大排序,每次让最小的饼干满足饥饿度最小的孩子,两个指针分别指向孩子饥饿度和饼干大小,遍历饼干数组,如果饼干大小能满足孩子饥饿度,则孩子饥饿度指针走一步。返回孩子饥饿度指针走的步数。

代码

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

135. 分发糖果(Hard)

题目描述
一群孩子站成一排,每一个孩子有自己的评分。现在需要给这些孩子发糖果,规则是如果一个孩子的评分比自己身旁的一个孩子要高,那么这个孩子就必须得到比身旁孩子更多的糖果;所有孩子至少要有一个糖果。求解最少需要多少个糖果。
输入输出样例
输入是一个数组,表示孩子的评分。输出是最少糖果的数量。

Input: [1,0,2]
Output: 5

思路
先从左往右满足高个子比矮个子糖多这个规则,再从右往左满足高个子比矮个子糖多。具体来说,先每个孩子给一颗糖。从左往右遍历孩子,如果a[i]>a[i-1],则让icandy[i]糖果增加为candy[i-1]+1;再从右往左遍历,如果a[i-1]>a[i]&&candy[i-1]<=candy[i],则candy[i-1]=candy[i+1],再求解candy数组之和即可。

代码

class Solution {
    public int candy(int[] ratings) {
        int n = ratings.length;
        int[] candies = new int[n];
        Arrays.fill(candies,1); //初始化
        for(int i=1;i<n;i++){
            if(ratings[i]>ratings[i-1]){ //从左往右遍历,右大于左,则右糖+1
                candies[i]=candies[i-1]+1;
            }
        }
        for(int i=ratings.length-1;i>0;i--){ //从右往左遍历,左大于右,且左糖少
            if(ratings[i-1]>ratings[i] && candies[i-1]<=candies[i]){
                candies[i-1]=candies[i]+1;
            }
        }
        int sum=0;
        for (int candy :
                candies) {
            sum += candy;
        }
        return  sum;
    }
}

435. 无重叠区间(Medium)

题目描述
给定多个区间,计算让这些区间互不重叠所需要移除区间的最少个数。起止相连不算重叠。
输入输出样例
输入是一个数组,数组由多个长度固定为2 的数组组成,表示区间的开始和结尾。输出一个
整数,表示需要移除的区间数量。

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

注意:区间类型题,需要根据实际情况判断按区间开头排序还是按区间结尾排序。

思路
将区间按照右端点大小进行排序,遍历区间对,如果某区间左断点比上一个的右端点小,说明有重叠,移除该区间,计数加1。使用一个pre指针,每次指向不重复区间序列最右侧区间对的右端点,即每次保证有最多个不重叠的区间。

代码

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        Comparator<int[]> comparator = new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[1]-o2[1];
            }
        };
        Arrays.sort(intervals,comparator);
        int pre = intervals[0][1]; //每次指向无重叠区间的最后一个区间右端点
        int count = 0;
        for(int i=1;i<intervals.length;i++){
            if(intervals[i][0]<pre){ //说明添加该区间时就重叠了
                count++;
            }else {
                pre = intervals[i][1]; //不重叠时,更新pre
            }
        }
        return count;
    }
}

605. 间隔种花问题 (Easy)

题目描述
给你一个整数数组 flowerbed 表示花坛,由若干 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,同时n-1。如果当前元素i为0,看i-1和i+1,如果a[i-1]和a[i+1]都为零,则满足插入条件。考虑条件:i若为首元素,此时只看a[i+1],i若为末尾元素,此时只看a[i-1]

代码

class Solution {
    public boolean canPlaceFlowers(int[] flowerbed, int n) {
	for(int i=0;i<flowerbed.length;i++){
	//巧妙地规避了判断是否是头和尾
        if(flowerbed[i]==0 && (i==0 || flowerbed[i-1]==0) && (i==flowerbed.length-1 || flowerbed[i+1]==0)){
                flowerbed[i]=1;
                n--;
            }
        }
        return n<=0;
    }
}

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

问题描述
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。

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

给你一个数组 points ,返回引爆所有气球所必须射出的 最小弓箭数。

输入输出样例
示例 1:

输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
在x = 6处射出箭,击破气球[2,8]和[1,6]。
输入:points = [[10,16],[2,8],[1,6],[7,12]]

示例 2:

输入:points = [[1,2],[3,4],[5,6],[7,8]]
输出:4
解释:每个气球需要射出一支箭,总共需要4支箭。

示例 3:

输入:points = [[1,2],[2,3],[3,4],[4,5]]
输出:2
解释:气球可以用2支箭来爆破:
在x = 2处发射箭,击破气球[1,2]和[2,3]。
在x = 4处射出箭,击破气球[3,4]和[4,5]。

思路
和区间相邻的435有些类似。将区间按照左端点从小到大排列,定义一个pre指向重叠区域的右端点,如果有其他气球的左端点points[i][0]<=pre,pre更新为较小的右端点,则这些区间可以用同一支箭引爆。

代码

class Solution {
    public int findMinArrowShots(int[][] points) {
        Comparator<int[]> comparator = new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return Integer.compare(o1[0], o2[0]);
            }
        };
        Arrays.sort(points,comparator);
        int pre=points[0][1];
        int count=1;
        for(int i=1;i<points.length;i++){
            if(points[i][0]<=pre){
                pre = Math.min(pre,points[i][1]);
                continue;
            }
            count++;
            pre = points[i][1];
        }
        return count;
    }
}

763. 划分字母区间 (Medium)

问题描述
字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。

输入输出样例
示例:

输入:S = “ababcbacadefegdehijhklij”
输出:[9,7,8]
解释:
划分结果为 “ababcbaca”, “defegde”, “hijhklij”。
每个字母最多出现在一个片段中。
像 “ababcbacadefegde”, “hijhklij” 的划分是错误的,因为划分的片段数较少。

思路
先遍历一次字符串,记录每个字符最后一次出现的下标。再次遍历字符串,使用一个end指向第一个片段结尾(初始为0),每次遍历一个字符,比较end和最后一次出现的位置,如果该位置比end远,使用该位置更新end。当元素i遍历到end时,划分为第一个片段。

代码

class Solution {
    public List<Integer> partitionLabels(String s) {
        List<Integer> list = new LinkedList<>();
        int[] last = new int[26]; //last每个位置存放对应字母最后一次出现位置
        for(int i=0;i<s.length();i++){
            last[s.charAt(i)-'a']=i;
        }
        int start=0,end=0;
        for(int i=0;i<s.length();i++){
            end = Math.max(end,last[s.charAt(i)-'a']);
            if(i==end){ //找到了片段尾
                list.add(end-start+1);
                start = end+1; //更新头和尾
                end = start;
            }
        }
        return list;
    }
}

122. 买卖股票的最佳时机 II(Medium)

问题描述
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

输入输出样例

示例 1:

输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。

思路
每次交易利益都最大化,从而保证最终利益最大化。遍历价格数组,遇到price[i+1]>price[i]时,在第i天买入,遇到跌price[i+1]<price[i]之前卖出去。后面重复操作,把每次挣的钱相加,即为最大利润。

代码

class Solution {
    public int maxProfit(int[] prices) {
        int s = 0; //总利润
        for(int i=0;i<prices.length-1;i++){
            if(prices[i]<prices[i+1]){//可以买入的时机
                int j = i+1;
                int low = prices[i];
				//j找到下一个开始下降的拐点
                while (j<prices.length && prices[j]>=low){
                    low = Math.max(low,prices[j]); //迭代low
                    j++;
                }
                s += prices[j-1]-prices[i]; //j退回最高点j-1卖出
                i = j -1; //更新i到价格的最高点,之后i自动加1,开启下一轮投资
            }
        }
        return s;
    }
}

406. 根据身高重建队列 (Medium)

问题描述

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)

输入输出样例

示例 1:

输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。

思路
贪心:保证每次都将身高小的插到身高高的前面,不破坏规则。先按身高从大到小进行排序,(如果身高相同,ki小的人排在前面)。再次遍历数组people,如果当前人peole[i][ki]<i,即前面比他高的人比ki多,此时就把当前people[i]插到people[ki]的位置。(有插入操作,考虑用链表)

代码

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Comparator<int[]> comparator = new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                if(o1[0]==o2[0]){
                    return Integer.compare(o1[1],o2[1]);
                }else return Integer.compare(o2[0],o1[0]);
            }
        };

        Arrays.sort(people,comparator);
        List<int[]> list = new LinkedList<>();
        for(int i=0;i<people.length;i++){
            list.add(people[i][1],people[i]); //利用链表的优势,直接将其插到ki的位置
        }
        return list.toArray(new int[list.size()][]); //list集合转化为数组
    }
}

665. 非递减数列 (Easy)

问题描述
给你一个长度为 n 的整数数组 nums ,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。

我们是这样定义一个非递减数列的: 对于数组中任意的 i (0 <= i <= n-2),总满足 nums[i] <= nums[i + 1]。

输入输出样例

示例 1:

输入: nums = [4,2,3]
输出: true
解释: 你可以通过把第一个 4 变成 1 来使得它成为一个非递减数列

思路
遍历数组,每次如果遇到a[i]>a[i+1]时,就判断是否满足题目条件。有如下几种情况:


可见只有前三种情况满足删除一个元素是非递减数列。代码中,可以将“拐点”拉平表示删除,然后来判断是否是递增。
代码

class Solution {
    public boolean checkPossibility(int[] nums) {
        for (int i = 0; i < nums.length-1; i++) {
            int x=nums[i],y=nums[i+1];
            if(y<x){
                nums[i]=y;
                if(isSorted(nums))return true;
                nums[i]=x;
                nums[i+1]=x;
                return isSorted(nums);
            }

        }
        return true;
    }
    public boolean isSorted(int[] nums){
        for (int i = 0; i < nums.length-1; i++) {
            if(nums[i+1]<nums[i])return false;
        }
        return true;
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值