贪心思想
455.分发饼干
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i
,都有一个胃口值 g[i]
,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j
,都有一个尺寸 s[j]
。如果 s[j] >= g[i]
,我们可以将这个饼干 j
分配给孩子 i
,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例 1:
输入: g = [1,2,3], s = [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
- 思路
- 每个孩子应该给予的饼干应该最刚好满足这个孩子,这样更大的饼干可以给胃口更大的孩子
- 典型的贪心思想
class Solution {
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g); //孩子的胃口排序
Arrays.sort(s); //饼干大小排序
int idx = 0; //idx=0开始,因为先满足胃口最小的孩子
// 从i=0开始排序,也就是先把最小的饼干满足给孩子
for(int i = 0; i < s.length; ++i) {
if(g[idx] <= s[i]) ++idx;
// 如果饼干都满足孩子且还有剩余的时候会有越界的情况,加以判断
if(idx == g.length) break;
}
return idx;
}
}
435.无重叠区间
给定一个区间的集合 intervals
,其中 intervals[i] = [starti, endi]
。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
示例 1:
输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
示例 2:
输入: intervals = [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
示例 3:
输入: intervals = [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
- 思路
- 需要移除区间的最小数量,也就是说在这个intervals里用尽可能多的数组来组成一个不重叠的区间,然后把总长度减去不重叠区间的个数,就是最终答案
- 可以对区间右边界进行排序
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
/**
1.用lambda表达式会有额外耗时
2.不要写return o1[1] - o2[1],有溢出风险!
*/
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return (o1[1] < o2[1]) ? -1 : ((o1[1] == o2[1]) ? 0 : 1);
}
});
int end = intervals[0][1]; //初始化end为第一个区间的右边界
int count = 1; //计算不重叠区间的组成个数
for(int i = 1; i < intervals.length; ++i) {
if(intervals[i][0] >= end) {
++count;
end = intervals[i][1];
}
}
return intervals.length - count;
}
}
452.使用最少数量箭射爆气球
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points
,其中points[i] = [xstart, xend]
表示水平直径在 xstart
和 xend
之间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x
处射出一支箭,若有一个气球的直径的开始和结束坐标为 x``start
,x``end
, 且满足 xstart ≤ x ≤ x``end
,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组 points
,返回引爆所有气球所必须射出的 最小 弓箭数 。
示例 1:
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
-在x = 6处射出箭,击破气球[2,8]和[1,6]。
-在x = 11处发射箭,击破气球[10,16]和[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]。
- 思路
- 贪心思想,希望每次射出去的箭都能恰好射到最多的气球,也就是重叠的区间
- 最终需要多少支箭就是求不重叠区间的个数,和无重叠区间是一样的
class Solution {
public int findMinArrowShots(int[][] points) {
int n = points.length;
Arrays.sort(points, new Comparator<int[]>() {
public int compare(int[] o1, int[] o2) {
return o1[1] < o2[1] ? -1 : (o1[1] == o2[1]) ? 0 : 1;
}
});
int end = points[0][1];
int cnt = 1;
for(int i = 1; i < n; ++i) {
if(points[i][0] > end) {
cnt++;
end = points[i][1];
}
}
return cnt;
}
}
406.根据身高重建队列
You are given an array of people, people
, which are the attributes of some people in a queue (not necessarily in order). Each people[i] = [hi, ki]
represents the ith
person of height hi
with exactly ki
other people in front who have a height greater than or equal to hi
.
Reconstruct and return the queue that is represented by the input array people
. The returned queue should be formatted as an array queue
, where queue[j] = [hj, kj]
is the attributes of the jth
person in the queue (queue[0]
is the person at the front of the queue).
Example 1:
Input: people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
Output: [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
Explanation:
Person 0 has height 5 with no other people taller or the same height in front.
Person 1 has height 7 with no other people taller or the same height in front.
Person 2 has height 5 with two persons taller or the same height in front, which is person 0 and 1.
Person 3 has height 6 with one person taller or the same height in front, which is person 1.
Person 4 has height 4 with four people taller or the same height in front, which are people 0, 1, 2, and 3.
Person 5 has height 7 with one person taller or the same height in front, which is person 1.
Hence [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] is the reconstructed queue.
Example 2:
Input: people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
Output: [[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]
- 思路
- 既然p[1]表示的是大于等于本身身高的人,那我们就先把身高较高的几个先插进去,如果身高一样就看p[1]
- 后续插入的people再根据自己的p[1]来插入
class Solution {
public int[][] reconstructQueue(int[][] people) {
// 身高降序,个数值升序
Arrays.sort(people, (p1, p2) ->
p1[0] != p2[0] ? (p2[0] - p1[0]) : p1[1] - p2[1]
);
List<int[]> ans = new LinkedList();
// 不会出现越界的问题,因为比你高的people已经插入了List,那你最多插入的位置也就是List.size
for(int[] p : people) {
ans.add(p[1], p);
}
// 转数组的方法
return ans.toArray(new int[ans.size()][]);
}
}
121.买股票最佳时机
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
1 <= prices.length <= 105
0 <= prices[i] <= 104
思路
-
第一想法是通过枚举,暴力搜索,显然超时
-
贪心,想着肯定是在某一天买到最便宜的票,并在未来的某一天卖出最贵
-
动态规划
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int[][] dp = new int[n][2]; // dp[i][0]是购入,dp[i][1]是出售
// 在这步先初始化,可读性更强
dp[0][0] = -prices[0];
dp[0][1] = 0;
for(int i = 1; i < n; ++i) {
// 判断当天购入便宜还是之前某天购入便宜?
dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);
// 判断当天出售更赚还是之前某天出售更赚
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
}
// 最后dp[n - 1][1]存的就是最大的利息
return dp[n - 1][1];
}
}
122.买股票最佳时机Ⅱ
给你一个整数数组 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 。
总利润为 4 + 3 = 7 。
示例 2:
输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
总利润为 4 。
示例 3:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0 。
提示:
1 <= prices.length <= 3 * 104
0 <= prices[i] <= 104
思路
- 有买股票的一天,只要之后有赚,就直接卖,如果未来的日子里还能有如此效果就继续做(贪心的想法)
- 与股票Ⅰ问题不一样,股票Ⅱ可以进行多次交易,在中间的日子能赚就赚,不用等到最后一天
public class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
if (len < 2) {
return 0;
}
int res = 0;
for (int i = 1; i < len; i++) {
// 有赚就卖
/*
此题说明可以当天卖,当天买
*/
int diff = prices[i] - prices[i - 1];
if (diff > 0) {
res += diff;
}
}
return res;
}
}
605.种花问题
假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。
给你一个整数数组 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 <= flowerbed.length <= 2 * 104
flowerbed[i]
为0
或1
flowerbed
中不存在相邻的两朵花0 <= n <= flowerbed.length
思路
- 贪心思想,从左往右,能种就种,就优解肯定是花尽量靠在一起(也就是中间最多只隔一格)
- 模拟这个过程,如果当前盆有花(1),那就往后第二盆判断
- 注:模拟过程是题目说到,所给flowerbed没有相邻的花!
- 如果当前盆没有花(0),判断其后一个花盆是不是有花
- 如果有花,就往后第三盆判断
- 如果没有花,由于我们是一直往后2盆看的花,所以前面肯定没有花,n–
- 注意如果是最后一盆花就直接n–,避免数组越界问题
class Solution {
public boolean canPlaceFlowers(int[] flowerbed, int n) {
int len = flowerbed.length;
for(int i = 0; i < len;) {
if(flowerbed[i] == 1) {
i += 2; // 如果有花,看后第二盆
}else if(i == len - 1 || flowerbed[i + 1] == 0) { //没有花,看后一盆
n--;
i += 2;
}else { // 有花,看后第三盆
i += 3;
}
}
return n <= 0;
}
}
392.判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"
是"abcde"
的一个子序列,而"aec"
不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
致谢:
特别感谢 @pbrother 添加此问题并且创建所有测试用例。
示例 1:
输入:s = "abc", t = "ahbgdc"
输出:true
示例 2:
输入:s = "axc", t = "ahbgdc"
输出:false
提示:
0 <= s.length <= 100
0 <= t.length <= 10^4
- 两个字符串都只由小写字母组成
思路
- 题目问到若有大量的输入需要如何快速判断他是T序列的子序列
- 第一想法就是求出T序列的所有子序列放在map当中,在这样判断的时间复杂度为O(1)
- 0 <= t.length <= 10^4 所以肯定超出内存限制
-
- 代码附上
class Solution {
Map<String, Integer> map = new HashMap();
int len = 0;
public boolean isSubsequence(String s, String t) {
len = t.length();
dfs(t, 0, "");
return map.get(s) != null;
}
/**
求所有子序列的递归函数
*/
public void dfs(String str, int i, String cur) {
if(i == len) {
map.put(cur, 1);
return;
}
char c = str.charAt(i);
dfs(str, i + 1, new String(cur + c));
dfs(str, i + 1, new String(cur));
}
}
- 乖乖回到贪心思想的做法,总是从最左边的字符先判断,双指针
class Solution {
public boolean isSubsequence(String s, String t) {
int n = s.length(), m = t.length();
int i = 0, j = 0;
while (i < n && j < m) {
if (s.charAt(i) == t.charAt(j)) {
i++;
}
j++;
}
return i == n;
}
}
665.非递减数列
给你一个长度为 n
的整数数组 nums
,请你判断在 最多 改变 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
解释: 你不能在只改变一个元素的情况下将其变为非递减数列。
提示:
n == nums.length
1 <= n <= 104
-105 <= nums[i] <= 105
思路
- 用flag来标记是否已经做过修改了
- 如果发现了nums[i] > nums[i + 1],此时做修改
-
- 如果修改nums[i] = num[i + 1] 那么nums[i]就变小,有可能会破坏前面已经遍历的非递减数列
-
- 如果修改nums[i + 1] = nums[i] 那么nums[i + 1]就变大,有可能使得后面的非递减数列破坏
-
- 所以我们要考虑nums[i - 1]
- 若nums[i + 1] >=nums[i - 1] 说明修改方案一并不会破坏前面的非递减数列
- 若nums[i + 1] < nums[i - 1] 那就实在没办法只能用修改方案2
- 修改后flag取反
class Solution {
public boolean checkPossibility(int[] nums) {
if(nums.length == 1) return true;
// flag一开始就做判断,是因为for循环中有i-1的操作,防止越界
boolean flag = nums[0] <= nums[1] ? false : true;
// for循环的终止是到nums.length-1,因为有i+1
for(int i = 1; i < nums.length - 1; ++i) {
if(nums[i] > nums[i + 1]) {
if(!flag) {
flag = true;
if(nums[i + 1] >= nums[i - 1]) {
nums[i] = nums[i - 1]; // 修改方案1
}else {
nums[i + 1] = nums[i]; // 修改方案2
}
}else {
return false;
}
}
}
return true;
}
}
53.最大子数组和
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
思路
- 看liweiwei1419,用的是动态规划来完成的
- 子问题定义:以nums[i]结尾的最大连续子数组之和
- dp数组记录和,dp[i]代表以nums[i]结尾的最大子数组之和
- 若dp[i - 1] < 0 ,那么dp[i] = nums[i]就行
- 个人理解:既然你前面i-1个数的连续子数组的和最大都是小于0,那我nums[i]带上你们就是拖累我,那就直接自己做一个连续子数组的和都比他们大
- 如果dp[i - 1] > 0 说明前面还是有点用,可以留着,就dp[i] = nums[i] + dp[i - 1]
class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
dp[0] = nums[0];
for(int i = 1; i < n; ++i) {
if(dp[i - 1] < 0) {
dp[i] = nums[i];
}else {
dp[i] = nums[i] + dp[i - 1];
}
}
int ans = Integer.MIN_VALUE;
for(int i = 0; i < n; ++i) {
ans = Math.max(ans, dp[i]);
}
return ans;
}
}
763.划分字母区间
给你一个字符串 s
。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s
。
返回一个表示每个字符串片段的长度的列表。
示例 1:
输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。
示例 2:
输入:s = "eccbbbbdec"
输出:[10]
提示:
1 <= s.length <= 500
s
仅由小写英文字母组成
思路
- 题目的意思就是希望同一个字母只能出现在一个片段当中
- 可以记录每个字母最后出现的下标位置
- 因为了分出更多的片段,所以我们要在一个片段当中放尽可能少的字母,同时又保证这些字母不会出现在别的片段
class Solution {
public List<Integer> partitionLabels(String s) {
int[] lastIndex = new int[26]; //记录字母最后出现的下标
int len = s.length();
for(int i = 0; i < len; ++i) {
lastIndex[s.charAt(i) - 'a'] = i;
}
List<Integer> ans = new LinkedList();
int start = 0, end = 0;
for(int i = 0; i < len; ++i) {
/**
这步我觉得要好好理解一下为什么这么写
首先我们在遍历到某个字母的时候,我们先记下这个字母的最大下标
一个片段的所有字母都遍历过,我们能得到这个片段里最大的下标
那么此时若i==end就说明这里面的字母只可能出现在这个片段,因为他们的最后一次出现的下标都比end来的小,而end此时是这个片段里最大的一个下标
*/
end = Math.max(end, lastIndex[s.charAt(i) - 'a']);
if(i == end) {
ans.add(end - start + 1);
start = end + 1;
}
}
return ans;
}
}