贪心问题
*题目1:455. 分发饼干
1)贪心法
思路:
class Solution {
public int findContentChildren(int[] g, int[] s) {
//对胃口排序
Arrays.sort(g);
//对饼干排序
Arrays.sort(s);
int start = 0;
int count = 0;
//遍历饼干
for (int i = 0; i < s.length&&start<g.length; i++) {
//如果饼干值大于胃口值 证明满足了孩子的胃口
if (s[i] >= g[start]) {
start++;
count++;
}
}
return count;
}
}
*题目2:376. 摆动序列
1)贪心法
思路:统计波纹 如果序列一直递增 那么down的值一直不变 up也不会变
class Solution {
public int wiggleMaxLength(int[] nums) {
int up=1;
int down=1;
for(int i=1;i<nums.length;i++){
if(nums[i]>nums[i-1]){
up=down+1;
}
if(nums[i]<nums[i-1]){
down=up+1;
}
}
return Math.max(up,down);
}
}
*题目3:53. 最大子数组和
思路:暴力法挺好想 贪心法不太好想
1)暴力解法
class Solution {
public int maxSubArray(int[] nums) {
int result=Integer.MIN_VALUE;
int sum=0;
for(int i=0;i<nums.length;i++){
sum=0;
for(int j=i;j<nums.length;j++){
sum+=nums[j];
if(sum>result){
result=sum;
}
}
}
return result;
}
}
2)贪心法
class Solution {
public int maxSubArray(int[] nums) {
if (nums.length == 1){
return nums[0];
}
int sum = Integer.MIN_VALUE;
int count = 0;
for (int i = 0; i < nums.length; i++){
count += nums[i];
sum = Math.max(sum, count); // 取区间累计的最大值(相当于不断确定最大子序终止位置)
if (count <= 0){
count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
}
}
return sum;
}
}
*题目4:122. 买卖股票的最佳时机 II
1)贪心法
思路:画出利润
class Solution {
public int maxProfit(int[] prices) {
int res=0;
for(int i=1;i<prices.length;i++){
int sum=prices[i]-prices[i-1];
res+=Math.max(sum,0);
}
return res;
}
}
*题目5:55. 跳跃游戏
1)贪心法
思路:
class Solution {
public boolean canJump(int[] nums) {
int coverRange = 0;
for(int i=0;i<=coverRange;i++){
coverRange = Math.max(coverRange, i + nums[i]);
if(coverRange>=nums.length-1){
return true;
}
}
return false;
}
}
*题目6:45. 跳跃游戏 II
1)贪心法
思路:
class Solution {
public int jump(int[] nums) {
if(nums.length==0||nums.length == 1) return 0;
//记录跳跃的次数
int count=0;
//当前的覆盖最大区域
int curDistance = 0;
//最大的覆盖区域
int maxDistance = 0;
for(int i=0;i<nums.length;i++){
//在可覆盖区域内更新最大的覆盖区域
maxDistance = Math.max(maxDistance,i+nums[i]);
//说明当前一步,再跳一步就到达了末尾
if (maxDistance>=nums.length-1){
count++;
break;
}
//走到当前覆盖的最大区域时,更新下一步可达的最大区域
if (i==curDistance){
curDistance = maxDistance;
count++;
}
}
return count;
}
}
题目7:1005. K 次取反后最大化的数组和
1)贪心法
思路:
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
// 将数组从大到小排序,注意要按照绝对值的大小
nums = IntStream.of(nums)
.boxed()
.sorted((o1, o2) -> Math.abs(o2) - Math.abs(o1))
.mapToInt(Integer::intValue).toArray();
int len = nums.length;
for (int i = 0; i < len; i++) {
//从前向后遍历,遇到负数将其变为正数,同时K--
if (nums[i] < 0 && k > 0) {
nums[i] = -nums[i];
k--;
}
}
//奇数 再反转一下最小值
if (k % 2 == 1) nums[len - 1] = -nums[len - 1];
return Arrays.stream(nums).sum();
}
}
*题目8:134. 加油站
1)贪心法
思路:
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int curSum = 0;
int totalSum = 0;
int index = 0;
for (int i = 0; i < gas.length; i++) {
curSum += gas[i] - cost[i];
totalSum += gas[i] - cost[i];
if (curSum < 0) {
index=i+1;
curSum = 0;
}
}
if (totalSum < 0) return -1;
return index;
}
}
*题目9:135. 分发糖果
1)贪心法
思路:
class Solution {
public int candy(int[] ratings) {
int len = ratings.length;
int[] candyVec = new int[len];
candyVec[0] = 1;
for (int i = 1; i < len; i++) {
candyVec[i] = (ratings[i] > ratings[i - 1]) ? candyVec[i - 1] + 1 : 1;
}
for (int i = len - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1]) {
candyVec[i] = Math.max(candyVec[i], candyVec[i + 1] + 1);
}
}
return Arrays.stream(candyVec).sum();
}
}
题目10:860. 柠檬水找零
1)贪心法
思路:分 五、十、二十的情况来看
class Solution {
public boolean lemonadeChange(int[] bills) {
int five = 0;
int ten = 0;
for (int i = 0; i < bills.length; i++) {
if (bills[i] == 5) {
five++;
} else if (bills[i] == 10) {
five--;
ten++;
} else if (bills[i] == 20) {
if (ten > 0 && five>0) {
ten--;
five--;
} else {
five -= 3;
}
}
if (five < 0 || ten < 0) return false;
}
return true;
}
}
区间问题
*题目11:406. 根据身高重建队列
1)贪心法
思路:1.排序规则:按照先H高度降序,K个数升序排序
2.遍历排序后的数组,根据K插入到K的位置上
class Solution {
public int[][] reconstructQueue(int[][] people) {
// 身高从大到小排(身高相同k小的站前面)
Arrays.sort(people, (a, b) -> {
if (a[0] == b[0]) return a[1] - b[1]; // a - b 是升序排列
return b[0] - a[0]; //b - a 是降序排列
});
LinkedList<int[]> que = new LinkedList<>();
for (int[] p : people) {
que.add(p[1],p);
}
return que.toArray(new int[people.length][]);
}
}
题目12:452. 用最少数量的箭引爆气球
1)贪心法
思路:当气球出现重叠,一起射,所用弓箭最少
class Solution {
public int findMinArrowShots(int[][] points) {
// 根据气球直径的开始坐标从小到大排序
Arrays.sort(points, (a, b) -> Integer.compare(a[0], b[0]));
int count = 1; // points 不为空至少需要一支箭
for (int i = 1; i < points.length; i++) {
// 气球i和气球i-1不挨着,注意这里不是>=
if (points[i][0] > points[i - 1][1]) {
// 需要一支箭
count++;
} else {
// 气球i和气球i-1挨着
// 更新重叠气球最小右边界
points[i][1] = Math.min(points[i][1], points[i - 1][1]);
}
}
return count;
}
}
题目13:435. 无重叠区间
1)贪心法
思路:代码以左边界排序 用总区间数-重叠区间数=未重叠区间数
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, (a,b)-> {
return Integer.compare(a[0],b[0]);
});
int count = 1;
for(int i = 1;i < intervals.length;i++){
if(intervals[i][0] < intervals[i-1][1]){
intervals[i][1] = Math.min(intervals[i - 1][1], intervals[i][1]);
continue;
}else{
count++;
}
}
return intervals.length - count;
}
}
题目14:763. 划分字母区间
1)贪心法
思路:如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了。
1)统计每一个字符最后出现的位置
2)从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点
class Solution {
public List<Integer> partitionLabels(String s) {
List<Integer> list = new LinkedList<>();
int[] edge = new int[26];
char[] chars = s.toCharArray();
//找到每个字符最后出现的位置
for (int i = 0; i < chars.length; i++) {
edge[chars[i] - 'a'] = i;
}
int right = 0;
int left = 0;
for (int i = 0; i < chars.length; i++) {
//找到字符出现的最远边界
right = Math.max(right,edge[chars[i] - 'a']);
//找到分割处了
if (i == right) {
list.add(right - left + 1);
left = i+1;
}
}
return list;
}
}
*题目15:56. 合并区间
1)贪心法
思路:判断区间重叠
class Solution {
public int[][] merge(int[][] intervals) {
List<int[]> res = new LinkedList<>();
//按照左边界排序
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
//initial start 是最小左边界
int start = intervals[0][0];
int rightmostRightBound = intervals[0][1];
//从第二个元素开始
for (int i = 1; i < intervals.length; i++) {
//如果第2个左边界大于第1个最大右边界,说明无重叠
if (intervals[i][0] > rightmostRightBound) {
//把第一个元素加入区间 并且更新start
res.add(new int[]{start, rightmostRightBound});
start = intervals[i][0];
rightmostRightBound = intervals[i][1];
} else {
//说明有重叠
//更新最大右边界
rightmostRightBound = Math.max(rightmostRightBound, intervals[i][1]);
}
}
res.add(new int[]{start, rightmostRightBound});
return res.toArray(new int[res.size()][]);
}
}
*题目16:738. 单调递增的数字
1)贪心法
思路:例如:98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]–,然后strNum[i]给为9,这样这个整数就是89,即小于98的最大的单调递增整数,从后向前遍历,就可以重复利用上次比较得出的结果了,从后向前遍历332的数值变化为:332 -> 329 -> 299
class Solution {
public int monotoneIncreasingDigits(int n) {
String s = String.valueOf(n);
char[] chars = s.toCharArray();
int start = s.length();
//从后向前遍历
for (int i = s.length() - 1; i > 0; i--) {
if (chars[i-1] > chars[i]) {
chars[i-1]--;
start = i;
}
}
//把strNum[i]变为9
for (int i = start; i < s.length(); i++) {
chars[i] = '9';
}
return Integer.parseInt(String.valueOf(chars));
}
}
*题目17:968. 监控二叉树
1)贪心法
思路:从低到上,先给叶子节点的父节点放个摄像头,然后隔两个节点放一个摄像头,直至到二叉树头结点
1)可以使用后序遍历也就是左右中的顺序,这样就可以在回溯的过程中从下到上进行推导了
2)情况1:左右节点都有覆盖:左孩子有覆盖,右孩子有覆盖,那么此时中间节点应该就是无覆盖的状态了。
情况2:左右节点至少有一个无覆盖的情况:
如果是以下情况,则中间节点(父节点)应该放摄像头:
left == 0 && right == 0 左右节点无覆盖
left == 1 && right == 0 左节点有摄像头,右节点无覆盖
left == 0 && right == 1 左节点有无覆盖,右节点摄像头
left == 0 && right == 2 左节点无覆盖,右节点覆盖
left == 2 && right == 0 左节点覆盖,右节点无覆盖
情况3:左右节点至少有一个有摄像头
如果是以下情况,其实就是 左右孩子节点有一个有摄像头了,那么其父节点就应该是2(覆盖的状态)
left == 1 && right == 2 左节点有摄像头,右节点有覆盖
left == 2 && right == 1 左节点有覆盖,右节点有摄像头
left == 1 && right == 1 左右节点都有摄像头
情况4:头结点没有覆盖
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int res=0;
public int minCameraCover(TreeNode root) {
// 对根节点的状态做检验,防止根节点是无覆盖状态 .
if(minCame(root)==0){
res++;
}
return res;
}
/**
节点的状态值:
0 表示无覆盖
1 表示 有摄像头
2 表示有覆盖
后序遍历,根据左右节点的情况,来判读 自己的状态
*/
public int minCame(TreeNode root){
if(root==null){
// 空节点默认为 有覆盖状态,避免在叶子节点上放摄像头
return 2;
}
int left=minCame(root.left);
int right=minCame(root.right);
// 如果左右节点都覆盖了的话, 那么本节点的状态就应该是无覆盖,没有摄像头
if(left==2&&right==2){
//(2,2)
return 0;
}else if(left==0||right==0){
// 左右节点都是无覆盖状态,那 根节点此时应该放一个摄像头
// (0,0) (0,1) (0,2) (1,0) (2,0)
// 状态值为 1 摄像头数 ++;
res++;
return 1;
}else{
// 左右节点的 状态为 (1,1) (1,2) (2,1) 也就是左右节点至少存在 1个摄像头,
// 那么本节点就是处于被覆盖状态
return 2;
}
}
}