LeetCode 贪心
基础部分
455. 分发饼干
简单
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
注意:
你可以假设胃口值为正。
一个小朋友最多只能拥有一块饼干。
示例 1:
输入: [1,2,3], [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
示例 2:
输入: [1,2], [1,2,3]
输出: 2
解释:
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.
class Solution {
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int res = 0;
int i = g.length-1;
int j = s.length-1;
while (i >= 0 && j >=0){
if (s[j] >= g[i]){ //满足了拿去吃
res++;
j--;
}
i--;
}
return res;
}
}
435. 无重叠区间
中等
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:
- 可以认为区间的终点总是大于它的起点。
- 区间 [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
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
int len = intervals.length;
if (len < 2) return 0;
Arrays.sort(intervals,(a,b)->(a[0]-b[0])); //以第一位升序排列
List<int[]> list = new LinkedList<>();
list.add(intervals[0]);
for (int i = 0; i < intervals.length; i++) {
int tail = list.get(list.size()-1)[1];
if (tail > intervals[i][0]){
if (tail > intervals[i][1]) //留尾巴短的,后边不易重叠
list.get(list.size()-1)[1] = intervals[i][1];
}else list.add(intervals[i]);
}
return len - list.size();
}
}
452. 用最少数量的箭引爆气球
中等
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。
一支弓箭可以沿着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(射爆另外两个气球)。
class Solution {
public int findMinArrowShots(int[][] points) {
if (points.length < 2) return points.length;
Arrays.sort(points,(a,b)->(a[0]-b[0]));
int res = 0;
int i = 0;
while (i < points.length){
res++;
int last = points[i][1];
do {
i++;
if (i < points.length && points[i][1] < last)
last = points[i][1]; //整体的尾巴往前才能都中
}while (i < points.length && points[i][0] <= last);
}
return res;
}
}
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]]
class Solution {
public int[][] reconstructQueue(int[][] people) {
Arrays.sort(people,(a,b)->a[1]-b[1]); //第二位正序
Arrays.sort(people,(a,b)->b[0]-a[0]); //第一位倒序
for (int i = 0; i < people.length; i++)
if (people[i][1] != i) { //people[i][1]:该去的位置
int j = i;
int[] tmp = people[j];
while (j != tmp[1]) { //tmp很关键,因为i和j都变化了,必须tmp
people[j] = people[j - 1];
j--;
}
people[j] = tmp;
}
return people;
}
}
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。
class Solution {
public int maxProfit(int[] prices) {
int res = 0;
int prof = 0;
for (int i = 1; i < prices.length; i++) {
int minus = prices[i] - prices[i-1];
prof += minus;
if (prof < 0) prof = 0; //负收益还不如不买
else if (prof > res) res = prof;
}
return res;
}
}
122. 买卖股票的最佳时机 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。
提示:
1 <= prices.length <= 3 * 10 ^ 4
0 <= prices[i] <= 10 ^ 4
class Solution {
public int maxProfit(int[] prices) {
int prof = 0;
for (int i = 1; i < prices.length; i++) {
int minus = prices[i] - prices[i-1];
prof += minus > 0 ? minus : 0;
}
return prof;
}
}
605. 种花问题
简单
假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。
给定一个花坛(表示为一个数组包含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 是非负整数,且不会超过输入数组的大小。
class Solution {
public boolean canPlaceFlowers(int[] flowerbed, int n) {
int k = 0;
for (int i = 0; i < flowerbed.length && k<n; i++) {
if (flowerbed[i] == 0){
if (i+1 == flowerbed.length || flowerbed[i+1] == 0){
flowerbed[i] = 1;
k++;
i++; //下一个地方肯定不能种,跳过
}
}else {
i++; //下一个地方肯定不能种,跳过
}
}
return k >= n;
}
}
392. 判断子序列
简单
给定字符串 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
.
后续挑战 :
如果有大量输入的 S,称作S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
class Solution {
public boolean isSubsequence(String s, String t) {
char[] schars = s.toCharArray();
char[] tchars = t.toCharArray();
int i, j;
for (i = 0,j = 0; i < schars.length; i++) {
while (j < tchars.length && tchars[j] != schars[i]) j++;
if (j >= tchars.length) break;
j++;
}
return i == schars.length;
}
}
665. 非递减数列
简单
给你一个长度为 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
class Solution {
public boolean checkPossibility(int[] nums) {
int fix = 0;
for (int i = 1; i < nums.length && fix <= 1; i++){
if (nums[i] < nums[i-1]){
fix++;
if (i-2 >= 0 && nums[i]<nums[i-2]){ //突然有个数字小了
nums[i]=nums[i-1];
}else { //前边的数字小了
nums[i-1]=nums[i];
}
}
}
return fix <= 1;
}
}
53. 最大子序和
简单
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
class Solution {
public int maxSubArray(int[] nums) {
int res = Integer.MIN_VALUE;
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
res = res > sum ? res : sum;
sum = sum < 0 ? 0 : sum;
}
return res;
}
}
763. 划分字母区间
中等
字符串 S
由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一个字母只会出现在其中的一个片段。返回一个表示每个字符串片段的长度的列表。
示例 1:
输入:S = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。
提示:
S
的长度在[1, 500]
之间。S
只包含小写字母'a'
到'z'
。
class Solution {
public List<Integer> partitionLabels(String S) {
char[] chars = S.toCharArray();
Map<Character,Integer> map = new HashMap<>();
for (int i = 0; i < chars.length; i++) {
map.put(chars[i],i);
}
List<Integer> list = new LinkedList<>();
int max = 0;
int i = 0;
int j = 0;
while (j < chars.length){
max = Math.max(max,map.get(chars[j]));
if (j++ == max) {
list.add(j-i);
i = j;
}
}
return list;
}
}
频率排序
135. 分发糖果
困难
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:
- 每个孩子至少分配到 1 个糖果。
- 相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?
示例 1:
输入: [1,0,2]
输出: 5
解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。
示例 2:
输入: [1,2,2]
输出: 4
解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这已满足上述两个条件。
class Solution {
public int candy(int[] ratings) {
int len = ratings.length;
int[] res = new int[len];
res[0] = 1;
for (int i = 1; i < len; i++) {
if (ratings[i] == ratings[i-1]) res[i] = 1;
else if (ratings[i] > ratings[i-1]) res[i] = res[i-1]+1;
else { //发现前面的孩子给少了
res[i] = 1;
int j = i;
do {
j--;
if (res[j] <= res[j+1]){
res[j] = res[j+1] + 1;
}else {
break;
}
}while (j > 0 && ratings[j] < ratings[j-1]);
}
}
int candys = 0;
for (int candy : res) candys += candy;
return candys;
}
}
316. 去除重复字母 ※
困难
给你一个仅包含小写字母的字符串,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
示例 1:
输入: "bcabc"
输出: "abc"
示例 2:
输入: "cbacdcbc"
输出: "acdb"
public class Solution {
public String removeDuplicateLetters(String s) {
int[] cnt = new int[26];
int pos = 0;
for (int i = 0; i < s.length(); i++) //字母出现次数的字典
cnt[s.charAt(i) - 'a']++;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) < s.charAt(pos)) pos = i; //后边的比pos的字典顺序小
if (--cnt[s.charAt(i) - 'a'] == 0) break; //字典里减没了,pos可以放第一位
}
return s.length() == 0 ? "" : s.charAt(pos) +
removeDuplicateLetters(s.substring(pos + 1) //递归
.replaceAll("" + s.charAt(pos), "") //这个字符统计完了,全部去掉
);
}
}
45. 跳跃游戏 II
困难
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明:
假设你总是可以到达数组的最后一个位置。
class Solution {
public int jump(int[] nums) {
int res = 0;
for (int i = nums.length - 1; i > 0; ) {
int pos = i - 1;
for (int j = 0; j < i; j++)
if (nums[j] >= i-j) pos = Math.min(pos,j);
res++;
i = pos;
}
return res;
}
}
714. 买卖股票的最佳时机含手续费
中等
给定一个整数数组 prices
,其中第 i
个元素代表了第 i
天的股票价格 ;非负整数 fee
代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
示例 1:
输入: prices = [1, 3, 2, 8, 4, 9], fee = 2
输出: 8
解释: 能够达到的最大利润:
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.
注意:
0 < prices.length <= 50000
.0 < prices[i] < 50000
.0 <= fee < 50000
.
//未通过,不知道为什么
class Solution {
public int maxProfit(int[] prices, int fee) {
int sum = 0;
int[] profs = new int[prices.length-1];
for (int i = 0; i < profs.length; i++)
profs[i] = prices[i+1] - prices[i];
int prof = 0;
int drop = 0;
for (int i = 0; i < profs.length; i++) {
if (prof == 0 && profs[i] < 0) continue;
if (profs[i] < 0){
drop += profs[i];
if (drop <= -fee) { //赔fee块钱,还不如交管理费卖了
if (prof > fee) sum += prof-fee;
drop = 0;
prof = 0;
}
}else {
prof += drop + profs[i];
drop = 0;
}
}
sum += prof > fee ? prof - fee : 0; //最后一波没加上
return sum;
}
}
402. 移掉K位数字
中等
给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。
注意:
- num 的长度小于 10002 且 ≥ k。
- num 不会包含任何前导零。
示例 1 :
输入: num = "1432219", k = 3
输出: "1219"
解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。
示例 2 :
输入: num = "10200", k = 1
输出: "200"
解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。
示例 3 :
输入: num = "10", k = 2
输出: "0"
解释: 从原数字移除所有的数字,剩余为空就是0。
class Solution {
public String removeKdigits(String num, int k) {
LinkedList<Character> stack = new LinkedList<>();
for(char digit : num.toCharArray()) {
while(!stack.isEmpty() && k > 0 && stack.peekLast() > digit) {
//stack不为空,k大于0,前面的数大于后面的数
stack.removeLast(); //去掉前面的数
k -= 1; //去掉一次少一次机会
}
stack.addLast(digit);
}
for(int i=0; i<k; ++i) stack.removeLast(); //还有机会没用完
StringBuilder res = new StringBuilder();
while (stack.peekFirst() == '0') stack.pollFirst();
for (Character c : stack) res.append(c);
return res.length() > 0 ? res.toString() : "0";
}
}
1029. 两地调度
简单
公司计划面试 2N
人。第 i
人飞往 A
市的费用为 costs[i][0]
,飞往 B
市的费用为 costs[i][1]
。
返回将每个人都飞到某座城市的最低费用,要求每个城市都有 N
人抵达。
示例:
输入:[[10,20],[30,200],[400,50],[30,20]]
输出:110
解释:
第一个人去 A 市,费用为 10。
第二个人去 A 市,费用为 30。
第三个人去 B 市,费用为 50。
第四个人去 B 市,费用为 20。
最低总费用为 10 + 30 + 50 + 20 = 110,每个城市都有一半的人在面试。
提示:
1 <= costs.length <= 100
costs.length
为偶数1 <= costs[i][0], costs[i][1] <= 1000
class Solution {
public int twoCitySchedCost(int[][] costs) {
int potA = costs.length/2;
int potB = costs.length/2;
Arrays.sort(costs,(x, y)->Math.abs(y[0]-y[1])-Math.abs(x[0]-x[1])); //相差越多权重越高,,权重高的人先挑
int res = 0;
for (int i = 0; i < costs.length; i++) {
int a = costs[i][0];
int b = costs[i][1];
if (a < b) {
if (potA-- > 0) res += a;
else res += b;
} else {
if (potB-- > 0) res += b;
else res += a;
}
}
return res;
}
}
//官方的,(A地-B地)的升序,这么简单吗,凭什么
class Solution {
public int twoCitySchedCost(int[][] costs) {
Arrays.sort(costs,(a,b)->a[0]-a[1]-b[0]+b[1]);
int total = 0;
int n = costs.length / 2;
for (int i = 0; i < n; ++i) total += costs[i][0] + costs[i + n][1];
return total;
}
}