4.9 贪心

This commit is contained in:
zhangsan 2025-04-09 12:50:45 +08:00
parent d28142efa1
commit 60c5f6969f
15 changed files with 632 additions and 7 deletions

View File

@ -0,0 +1,78 @@
package greedy;
/**
* 题目 134. 加油站 (canCompleteCircuit)
* 描述在一条环路上有 n 个加油站其中第 i 个加油站有汽油 gas[i]
* 你有一辆油箱容量无限的的汽车从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 你从其中的一个加油站出发开始时油箱为空
* 给定两个整数数组 gas cost 如果你可以按顺序绕环路行驶一周则返回出发时加油站的编号否则返回 -1 如果存在解 保证 它是 唯一
*
* 示例 1
输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
输出: 3
解释:
3 号加油站(索引为 3 )出发可获得 4 升汽油此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站你需要消耗 5 升汽油正好足够你返回到 3 号加油站
因此3 可为起始索引
* 链接https://leetcode.cn/problems/gas-station/
*/
public class CanCompleteCircuit {
public int canCompleteCircuit1(int[] gas, int[] cost) {
int sumgas=0,sumcost=0;
int[] diff=new int[gas.length];
for (int i = 0; i < gas.length; i++) {
sumgas+=gas[i];
sumcost+=cost[i];
diff[i]=gas[i]-cost[i];
}
if(sumcost>sumgas)
return -1;
for (int i = 0; i < diff.length; i++) {
if(diff[i]>=0){
int j=(i+1)%diff.length;
int cur=diff[i];
boolean flag=true;
while(j!=i){
cur+=diff[j];
if(cur<0) {
flag=false;
break;
}
j=(j+1)%diff.length;
}
if(flag)
return i;
}
}
return -1;
}
public int canCompleteCircuit(int[] gas, int[] cost) {
int total = 0; // 总盈余记录所有加油站油量差值的总和
int tank = 0; // 当前油箱剩余当前起点下的油量累计
int start = 0; // 起始加油站索引
// 遍历所有加油站
for (int i = 0; i < gas.length; i++) {
// 当前加油站的油量差值加油量 - 行驶消耗
int diff = gas[i] - cost[i];
// 累计总盈余
total += diff;
// 当前油箱剩余更新
tank += diff;
// 如果在当前起点从起点到 i 的累计油量不足以到达 i+1
// 那么必然不能从该起点出发完成环路所以把起点更新为 i+1
// 并且重置当前油箱剩余为 0
if (tank < 0) {
start = i + 1;
tank = 0;
}
}
// 如果总盈余小于 0说明全局油量不足返回 -1否则返回更新后的起点
return total < 0 ? -1 : start;
}
}

View File

@ -0,0 +1,58 @@
package greedy;
import java.util.Arrays;
/**
* 题目 55. 跳跃游戏 (canJump)
* 描述给你一个非负整数数组 nums 你最初位于数组的 第一个下标 数组中的每个元素代表你在该位置可以跳跃的最大长度
* 判断你是否能够到达最后一个下标如果可以返回 true 否则返回 false
* 示例 1
输入nums = [2,3,1,1,4]
输出true
解释可以先跳 1 从下标 0 到达下标 1, 然后再从下标 1 3 步到达最后一个下标
* 链接https://leetcode.cn/problems/jump-game/
*/
public class CanJump {
//接近超时记忆化搜索记录每一个状态这里是索引 i是否能成功跳到最后
public boolean canJump1(int[] nums) {
// -1 表示未计算0 表示不能到达1 表示能到达
int[] memo = new int[nums.length];
Arrays.fill(memo, -1);
return helper(nums, 0, memo);
}
boolean helper(int[] nums, int i, int[] memo) {
if (i >= nums.length - 1) return true;
if (memo[i] != -1) return memo[i] == 1; //-1代表没访问过0代表失败1代表成功
int max = nums[i];
for (int j = 1; j <= max; j++) {
if (helper(nums, i + j, memo)) {
memo[i] = 1;
return true;
}
}
memo[i] = 0; //尝试了所有从当前位置 i 开始能跳的范围后都没有跳到终点说明从 i 出发是失败的
return false;
}
//太妙了
public boolean canJump(int[] nums) {
int maxReach = 0; // 表示当前能到达的最远位置
for (int i = 0; i < nums.length; i++) {
// 如果当前位置超过了当前能到达的最远位置则无法到达该位置返回 false
if (i > maxReach) {
return false;
}
// 更新当前能到达的最远位置
maxReach = Math.max(maxReach, i + nums[i]);
// 如果能到达最后一个位置则返回 true
if (maxReach >= nums.length - 1) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,51 @@
package greedy;
/**
* 题目 135. 分发糖果 (candy)
* 描述n 个孩子站成一排给你一个整数数组 ratings 表示每个孩子的评分
* 你需要按照以下要求给这些孩子分发糖果
* 每个孩子至少分配到 1 个糖果
* 相邻两个孩子评分更高的孩子会获得更多的糖果
* 请你给每个孩子分发糖果计算并返回需要准备的 最少糖果数目
示例 2
输入ratings = [1,2,2]
输出4
解释你可以分别给第一个第二个第三个孩子分发 121 颗糖果
第三个孩子只得到 1 颗糖果这满足题面中的两个条件
* 链接https://leetcode.cn/problems/candy/
*/
//没想到想复杂了
public class Candy {
public int candy(int[] ratings) {
int n = ratings.length;
int[] candies = new int[n];
// 第一步初始化每个孩子至少1颗糖果
for (int i = 0; i < n; i++) {
candies[i] = 1;
}
// 第二步从左到右遍历如果右侧评分高给右侧多1颗
for (int i = 1; i < n; i++) {
if (ratings[i] > ratings[i - 1]) {
candies[i] = candies[i - 1] + 1;
}
}
// 第三步从右到左遍历如果左侧评分高确保左侧孩子糖果更多
for (int i = n - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1]) {
candies[i] = Math.max(candies[i], candies[i + 1] + 1);
}
}
// 求出总糖果数
int sum = 0;
for (int candy : candies) {
sum += candy;
}
return sum;
}
}

View File

@ -0,0 +1,34 @@
package greedy;
import java.util.Arrays;
import java.util.HashMap;
/**
* 题目 452. 用最少数量的箭引爆气球 (findMinArrowShots)
* 描述有一些球形气球贴在一堵用 XY 平面表示的墙面上墙面上的气球记录在整数数组 points 其中points[i] = [xstart, xend] 表示水平直径在 xstart xend之间的气球你不知道气球的确切 y 坐标
* 一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出在坐标 x 处射出一支箭若有一个气球的直径的开始和结束坐标为 xstartxend 且满足 xstart x xend则该气球会被 引爆 可以射出的弓箭的数量 没有限制 弓箭一旦被射出之后可以无限地前进
* 给你一个数组 points 返回引爆所有气球所必须射出的 最小 弓箭数
示例 2
输入points = [[10,16],[2,8],[1,6],[7,12]]
输出2
解释气球可以用2支箭来爆破:
-在x = 6处射出箭击破气球[2,8][1,6]
-在x = 11处发射箭击破气球[10,16][7,12]
* 链接https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/
*/
public class FindMinArrowShots {
public int findMinArrowShots(int[][] points) {
Arrays.sort(points, (a, b) -> Integer.compare(a[1], b[1]));
int index=0,cnt=0;
while(index<points.length){
int temp=index;
while(index<points.length&&points[temp][1]>=points[index][0])
index++;
cnt++;
}
return cnt;
}
}

View File

@ -0,0 +1,49 @@
package greedy;
/**
* 题目 45. 跳跃游戏 II (jump)
* 描述给定一个长度为 n 0 索引整数数组 nums初始位置为 nums[0]
* 每个元素 nums[i] 表示从索引 i 向后跳转的最大长度换句话说如果你在 nums[i] 你可以跳转到任意 nums[i + j] :
* 0 <= j <= nums[i]
* i + j < n
* 返回到达 nums[n - 1] 的最小跳跃次数生成的测试用例可以到达 nums[n - 1]
* 示例 1
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2
从下标为 0 跳到下标为 1 的位置 1 然后跳 3 步到达数组的最后一个位置
* 链接https://leetcode.cn/problems/jump-game-ii/
*/
//不会 https://programmercarl.com/0045.%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8FII.html#%E6%80%9D%E8%B7%AF
public class Jump {
//要从覆盖范围出发不管怎么跳覆盖范围内一定是可以跳到的以最小的步数增加覆盖范围覆盖范围一旦覆盖了终点得到的就是最少步数
public int jump(int[] nums) {
if (nums == null || 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;
}
}

View File

@ -0,0 +1,48 @@
package greedy;
import java.util.Arrays;
/**
* 题目 1005. K 次取反后最大化的数组和 (largestSumAfterKNegations)
* 描述给你一个整数数组 nums 和一个整数 k 按以下方法修改该数组
* 选择某个下标 i 并将 nums[i] 替换为 -nums[i]
* 重复这个过程恰好 k 可以多次选择同一个下标 i
* 以这种方式修改数组后返回数组 可能的最大和
*
* 示例 1
输入nums = [4,2,3], k = 1
输出5
解释选择下标 1 nums 变为 [4,-2,3]
* 链接https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/
*/
//有点绕
public class LargestSumAfterKNegations {
//让绝对值大的负数变为正数当前数值达到最大 那么如果将负数都转变为正数了K依然大于0 只找数值最小的正整数进行反转
public int largestSumAfterKNegations(int[] nums, int k) {
if (nums.length == 1) return nums[0];
// 排序先把负数处理了
Arrays.sort(nums);
for (int i = 0; i < nums.length && k > 0; i++) { // 贪心点, 通过负转正, 消耗尽可能多的k
if (nums[i] < 0) {
nums[i] = -nums[i];
k--;
}
}
// 退出循环, k > 0 || k < 0 (k消耗完了不用讨论)
if (k % 2 == 1) { // k > 0 && k is odd对于负数---
Arrays.sort(nums); // 再次排序得到最小的正数
nums[0] = -nums[0];
}
// k > 0 && k is evenflip数字不会产生影响: 对于负数: --对于正数--
int sum = 0;
for (int num : nums) { // 计算最大和
sum += num;
}
return sum;
}
}

View File

@ -0,0 +1,68 @@
package greedy;
import java.util.ArrayList;
import java.util.List;
/**
* 题目 860. 柠檬水找零 (lemonadeChange)
* 描述在柠檬水摊上每一杯柠檬水的售价为 5 美元顾客排队购买你的产品按账单 bills 支付的顺序一次购买一杯
* 每位顾客只买一杯柠檬水然后向你付 5 美元10 美元或 20 美元你必须给每个顾客正确找零也就是说净交易是每位顾客向你支付 5 美元
* 注意一开始你手头没有任何零钱
* 给你一个整数数组 bills 其中 bills[i] 是第 i 位顾客付的账如果你能给每位顾客正确找零返回 true 否则返回 false
*
* 示例 1
输入bills = [5,5,5,10,20]
输出true
解释
3 位顾客那里我们按顺序收取 3 5 美元的钞票
4 位顾客那里我们收取一张 10 美元的钞票并返还 5 美元
5 位顾客那里我们找还一张 10 美元的钞票和一张 5 美元的钞票
由于所有客户都得到了正确的找零所以我们输出 true
* 链接https://leetcode.cn/problems/lemonade-change/
*/
public class LemonadeChange {
public boolean lemonadeChange(int[] bills) {
int five = 0; // 记录 5 美元的数量
int ten = 0; // 记录 10 美元的数量
// 遍历每一笔账单
for (int bill : bills) {
switch (bill) {
case 5:
// 收到 5 美元不需找零
five++;
break;
case 10:
// 收到 10 美元需要找回 5 美元
if (five > 0) {
five--; // 找回一张 5 美元
ten++; // 保存这张 10 美元
} else {
return false;
}
break;
case 20:
// 收到 20 美元需要找回 15 美元
// 优先使用一张 10 美元和一张 5 美元找零
if (ten > 0 && five > 0) {
ten--;
five--;
}
// 如果没有 10 美元则用三张 5 美元找零
else if (five >= 3) {
five -= 3;
} else {
return false;
}
break;
default:
return false;
}
}
return true;
}
}

View File

@ -1,9 +1,5 @@
package greedy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 题目 121. 买卖股票的最佳时机 (maxProfit)
* 描述给定一个数组 prices 它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格
@ -18,8 +14,7 @@ import java.util.List;
* 链接https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/
*/
//第一道贪心学思路
//每次都假设是今天卖出然后求今天之前的历史最低点
//第一次做没想出来每次都假设是今天卖出然后求今天之前的历史最低点
public class MaxProfit {
public int maxProfit(int prices[]) {
int minprice = Integer.MAX_VALUE;
@ -27,7 +22,7 @@ public class MaxProfit {
for (int price : prices) {
if (price < minprice) {
minprice = price;
} else if (price - minprice > maxprofit) {
} else if (price - minprice > maxprofit) { //else if 是因为今日如果买进今日卖出收益为0
maxprofit = price - minprice;
}
}

View File

@ -0,0 +1,49 @@
package greedy;
/**
* 题目 122. 买卖股票的最佳时机 II (maxProfit)
* 描述给你一个整数数组 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
* 链接https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/
*/
//考虑复杂了
public class MaxProfit2 {
public int maxProfit1(int[] prices) {
int prediff=0,curdiff=0,maxprofit=0,buyprice=0;
boolean flag=false;
for (int i = 1; i < prices.length; i++) {
curdiff=prices[i]-prices[i-1];
if(!flag){
if(prediff<=0 &&curdiff>0){
flag=true;
buyprice=prices[i-1];
}
}else {
if(prediff>=0&&curdiff<0){
flag=false;
maxprofit+=prices[i-1]-buyprice;
}
}
prediff=curdiff;
}
if(flag)
maxprofit+=prices[prices.length-1]-buyprice;
return maxprofit;
}
//收集正利润的区间就是股票买卖的区间而我们只需要关注最终利润不需要记录区间
public int maxProfit(int[] prices) {
int result = 0;
for (int i = 1; i < prices.length; i++) {
result += Math.max(prices[i] - prices[i - 1], 0);
}
return result;
}
}

View File

@ -0,0 +1,88 @@
package greedy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* 题目 763. 划分字母区间 (partitionLabels)
* 描述给你一个字符串 s 我们要把这个字符串划分为尽可能多的片段同一字母最多出现在一个片段中例如字符串 "ababcc" 能够被分为 ["abab", "cc"]但类似 ["aba", "bcc"] ["ab", "ab", "cc"] 的划分是非法的
* 注意划分结果需要满足将所有划分结果按顺序连接得到的字符串仍然是 s
* 返回一个表示每个字符串片段的长度的列表
示例 2
输入s = "ababcbacadefegdehijhklij"
输出[9,7,8]
解释
划分结果为 "ababcbaca""defegde""hijhklij"
每个字母最多出现在一个片段中
"ababcbacadefegde", "hijhklij" 这样的划分是错误的因为划分的片段数较少
* 链接https://leetcode.cn/problems/partition-labels/
*/
public class PartitionLabels {
/**
* 可以通过但是时间复杂度高
预先计算字符最后出现位置
一种常见且高效的做法是在遍历字符串前用一个 Map<Character, Integer> 或者数组记录下每个字符的最后出现索引这样在后续遍历时能够通过字符直接查到其最后出现位置避免重复扫描
维护当前区间右边界
在遍历过程中维护一个 end 变量表示当前区间内所有字符最后出现位置的最大值每遇到一个新的字符就更新 end = Math.max(end, lastIndex[当前字符])当遍历索引等于 end 说明当前区间已经完整可以结束分区并记录长度
简化逻辑避免重复循环
采用上述方法后代码只需要一次遍历即可确定所有分区长度无需额外遍历区间内所有字符进行检查
*/
public List<Integer> partitionLabels1(String s) {
HashMap<Character,Integer>map=new HashMap<>();
List<Integer>list=new ArrayList<>();
for (int i = 0; i < s.length(); i++) {
map.put(s.charAt(i),map.getOrDefault(s.charAt(i),0)+1);
}
StringBuffer buffer=new StringBuffer();
for (int i = 0; i < s.length(); i++) {
boolean flag=true;
char cur=s.charAt(i);
buffer.append(cur);
int newCount = map.get(cur) - 1;
if (newCount > 0) {
map.put(cur, newCount);
} else {
map.remove(cur);
}
for (int j = 0; j < buffer.length(); j++) {
if (map.containsKey(buffer.charAt(j))) {
flag = false;
break;
}
}
if(flag) {
list.add(buffer.length());
buffer.setLength(0);
}
}
return list;
}
//看这个
public List<Integer> partitionLabels(String s) {
// 用于记录每个字符最后一次出现的索引
HashMap<Character, Integer> lastOccurrence = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
lastOccurrence.put(s.charAt(i), i);
}
List<Integer> partitionSizes = new ArrayList<>();
int start = 0, end = 0; // start 记录当前分区开始的位置
for (int i = 0; i < s.length(); i++) {
// 更新当前分区的最远截止位置
end = Math.max(end, lastOccurrence.get(s.charAt(i)));
// 当遍历到分区的最远截止位置时说明可以划分一个区间
if (i == end) {
partitionSizes.add(end - start + 1);
start = i + 1; // 下一分区的起始位置
}
}
return partitionSizes;
}
}

View File

@ -0,0 +1,41 @@
package greedy;
import java.util.Arrays;
import java.util.LinkedList;
/**
* 题目 406. 根据身高重建队列 II (maxProfit)
* 描述假设有打乱顺序的一群人站成一个队列数组 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 个身高更高或者相同的人排在他前面即编号为 0123 的人
编号为 5 的人身高为 7 1 个身高更高或者相同的人排在他前面即编号为 1 的人
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列
* 链接https://leetcode.cn/problems/queue-reconstruction-by-height/
*/
//不会 很妙
public class ReconstructQueue {
public int[][] reconstructQueue(int[][] people) {
// 身高从大到小排身高相同k小的站前面
Arrays.sort(people, (a, b) -> {
if (a[0] == b[0]) return a[1] - b[1]; // a - b 是升序排列故在a[0] == b[0]的狀況下會根據k值升序排列
return b[0] - a[0]; //b - a 是降序排列在a[0] != b[0]的狀況會根據h值降序排列
});
LinkedList<int[]> que = new LinkedList<>();
for (int[] p : people) {
que.add(p[1],p); //Linkedlist.add(index, value)會將value插入到指定index裡
}
return que.toArray(new int[people.length][]);
}
}

View File

@ -0,0 +1,16 @@
package greedy;
import org.junit.Test;
import static org.junit.Assert.*;
public class CanCompleteCircuitTest {
@Test
public void canCompleteCircuit() {
int[] gas = {1,2,3,4,5}, cost = {3,4,5,1,2};
CanCompleteCircuit solution = new CanCompleteCircuit();
int res=solution.canCompleteCircuit(gas,cost);
System.out.println(res);
}
}

View File

@ -0,0 +1,18 @@
package greedy;
import org.junit.Test;
import static org.junit.Assert.*;
public class FindMinArrowShotsTest {
@Test
public void findMinArrowShots() {
// int[][] points = {{10,16},{2,8},{1,6},{7,12}};
// int[][] points ={{1,2},{3,4},{4,5},{2,3}};
int[][] points = {{-2147483646,-2147483645},{2147483646,2147483647}};
FindMinArrowShots solution = new FindMinArrowShots();
int res=solution.findMinArrowShots(points);
System.out.println(res);
}
}

View File

@ -0,0 +1,16 @@
package greedy;
import org.junit.Test;
import static org.junit.Assert.*;
public class JumpTest {
@Test
public void jump() {
int[] nums = new int[]{2,2,1,2,4};
Jump solution = new Jump();
int res=solution.jump(nums);
System.out.println(res);
}
}

View File

@ -0,0 +1,16 @@
package greedy;
import org.junit.Test;
import static org.junit.Assert.*;
public class MaxProfit2Test {
@Test
public void maxProfit() {
int[]prices = {1,2,3,4,5};
MaxProfit2 solution = new MaxProfit2();
int res=solution.maxProfit(prices);
System.out.println(res);
}
}