LeetCode - 8. 贪心
刷题顺序来自:代码随想录
目录
简单贪心
455. 分发饼干
思路:将小饼干喂给胃口小的。
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int count = 0;
int feeded = -1; // 记录上一个被喂饱的
for(int i = 0; i < s.length; i++) {
for(int j = feeded+1; j < g.length; j++) {
if(s[i] >= g[j]) {
feeded = j;
count++;
break;
}
}
}
return count;
}
优化版:
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;
}
1005. K 次取反后最大化的数组和
public int largestSumAfterKNegations(int[] nums, int k) {
Arrays.sort(nums); // 排序
// 找到最后一个负数的下标
int nIndex = -1;
for(int i = 0; i < nums.length; i++) {
if(nums[i] >= 0) {
break;
}
nIndex++;
}
// 先尽量让绝对值大的负数变正
for(int i = 0; i <= nIndex && k > 0; i++, k--) {
nums[i] = -nums[i];
}
// 如果剩余k的次数为0或者为偶数,则不用操作
// 如果k为奇数,则选择nIndex和nIndex+1之间绝对值更小的数字变负
if(k > 0 && k % 2 == 1) {
if(nIndex >= 0 && (nIndex + 1 >= nums.length || nums[nIndex] <= nums[nIndex+1])) {
nums[nIndex] = -nums[nIndex];
}
else {
nums[nIndex+1] = -nums[nIndex+1];
}
}
// 计算累加和
int sum = 0;
for(int i = 0; i < nums.length; i++) {
sum += nums[i];
}
return sum;
}
860. 柠檬水找零
public boolean lemonadeChange(int[] bills) {
int five = 0; // 当前5美元的数量
int ten = 0; // 当前10美元的数量
for(int i = 0; i < bills.length; i++) {
switch(bills[i]) {
case 5:
five++;
break;
case 10:
five--;
ten++;
break;
case 20:
if(ten > 0) { // 优先用10美元找零
ten--;
five--;
}
else {
five -= 3;
}
break;
}
if(five < 0) {
return false;
}
}
return true;
}
序列问题
376. 摆动序列
需要记录所有的极大值和极小值点。
public int wiggleMaxLength(int[] nums) {
int count = 1;
int preDiff = 0;
int currDiff = 0;
for(int i = 0; i < nums.length-1; i++) {
currDiff = nums[i] - nums[i+1]; // 计算当前节点和下一个节点的差值
if(preDiff <= 0 && currDiff > 0 || preDiff >= 0 && currDiff <0) {
count++;
preDiff = currDiff;
}
}
return count;
}
738. 单调递增的数字
public int monotoneIncreasingDigits(int n) {
StringBuilder num = new StringBuilder(String.valueOf(n));
// 找到位数最大的需要-1的位数
int target = -1;
for(int i = num.length() - 2; i >= 0; i--) {
if(num.charAt(i) > num.charAt(i + 1)) {
target = i;
num.setCharAt(target, (char) (num.charAt(target) - 1));
}
}
if(target == -1) {
return n;
}
// 如果有位数需要-1,它之后的位都设置为9
for(int i = target + 1; i < num.length(); i++) {
num.setCharAt(i, '9');
}
return Integer.parseInt(num.toString());
}
股票问题
122. 买卖股票的最佳时机 II
相邻的两天一旦上涨,就累加。
public int maxProfit(int[] prices) {
int max = 0;
for(int i = 1; i < prices.length; i++) {
int diff = prices[i] - prices[i-1];
if(diff > 0) {
max += diff;
}
}
return max;
}
714. 买卖股票的最佳时机含手续费
DP的做法:
public int maxProfit(int[] prices, int fee) {
int sale = 0; // 记录前一天如果不持有股票,最大利润
int hold = -prices[0]; // 记录前一天如果持有股票,最大利润
for(int i = 1; i < prices.length; i++) {
// 更新sale方法:二者中选较大的 1.前一天就不持有股票 2. 前一天持有股票,今天卖掉
int currSale = Math.max(sale, hold + prices[i] - fee);
// 更新hold的方法类似
int currHold = Math.max(hold, sale - prices[i]);
sale = currSale;
hold = currHold;
}
return Math.max(sale, hold);
}
两个维度权衡问题
135. 分发糖果
public int candy(int[] ratings) {
int[] candies = new int[ratings.length];
Arrays.fill(candies, 1);
// 先从左到右遍历,保证当右边比左边更大时,candies更多
for(int i = 1; i < ratings.length; i++) {
if(ratings[i] > ratings[i-1]) {
candies[i] = candies[i-1] + 1;
}
}
// 从右到左遍历,保证当左边比右边更大时,candies更多
for(int i = ratings.length - 2; i >= 0; i--) {
// candies[i] <= candies[i+1]的条件是为了保证candies不会越更新越少
if(ratings[i] > ratings[i+1] && candies[i] <= candies[i+1]) {
candies[i] = candies[i+1] + 1;
}
}
int count = 0;
for(int i = 0; i < candies.length; i++) {
count += candies[i];
}
return count;
}
406. 根据身高重建队列
public int[][] reconstructQueue(int[][] people) {
// 按照身高从大到小,位置从小到大的顺序排序,例如
// input: [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
// output: [[7,0],[7,1],[6,1],[5,0],[5,2],[4,4]]
Arrays.sort(people, new Comparator<int[]>() {
public int compare(int[] p1, int[] p2) {
if(p1[0] == p2[0]) {
return Integer.compare(p1[1], p2[1]);
}
return -Integer.compare(p1[0], p2[0]);
}
});
// 根据排序后的数组,按照位置插入新节点
LinkedList<int[]> res = new LinkedList<>();
for(int i = 0; i < people.length; i++) {
res.add(people[i][1], people[i]);
}
// 将链表转化为数组
int[][] array = new int[people.length][2];
return res.toArray(array);
}
区间问题
55. 跳跃游戏
public boolean canJump(int[] nums) {
int maxIndex = 0; // 记录当前所能到达的最远下标
for(int i = 0; i < nums.length && i <= maxIndex; i++) {
if(i + nums[i] > maxIndex) {
maxIndex = i + nums[i];
}
if(maxIndex >= nums.length - 1) {
break;
}
}
return maxIndex >= nums.length - 1;
}
45. 跳跃游戏 II
public int jump(int[] nums) {
if(nums.length <= 1) {
return 0;
}
int startIndex = 0;
int count = 0;
while(startIndex < nums.length - 1) {
int jump = nums[startIndex];
int maxIndex = startIndex;
// 找出从startIndex能到达的下标中,能跳的最远的下标,记录为maxIndex
for(int i = startIndex + 1; i <= startIndex + jump; i++) {
if(i >= nums.length - 1) {
return ++count;
}
if(maxIndex + nums[maxIndex] < i + nums[i]) {
maxIndex = i;
}
}
startIndex = maxIndex;
count++;
}
return count;
}
452. 用最少数量的箭引爆气球
public int findMinArrowShots(int[][] points) {
// 按开始坐标排序
Arrays.sort(points, new Comparator<int[]>() {
public int compare(int[] p1, int[] p2) {
return Integer.compare(p1[0], p2[0]);
}
});
int count = points.length; // 记录最少需要的数量
int[] curr = points[0]; // 记录当前的公共数组
for(int i = 1; i < points.length; i++) {
int[] temp = commonArray(curr, points[i]); // 判断是否能出现公共数组
if(temp == null) {
curr = points[i];
}
else { // 如果有,说明可以少用一支箭
count--;
curr = temp;
}
}
return count;
}
// 返回2个数组的公共数组,如果没有则返回null
private int[] commonArray(int[] p1, int[] p2) {
int min = Math.max(p1[0], p2[0]);
int max = Math.min(p1[1], p2[1]);
if(min <= max) {
return new int[]{min, max};
}
return null;
}
435. 无重叠区间
重点是要按照右边界排序。
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, new Comparator<int[]>() {
public int compare(int[] i1, int[] i2) {
return Integer.compare(i1[1], i2[1]);
}
});
int count = 0;
int start = 0;
for(int i = 1; i < intervals.length; i++) {
// 是否需要删除当前的区间
if(intervals[i][0] < intervals[start][1]) {
count++;
}
else {
start = i;
}
}
return count;
}
763. 划分字母区间
public List<Integer> partitionLabels(String s) {
ArrayList<Integer> res = new ArrayList<>();
int[] hash = new int[26]; // 记录每个字母所出现的最远位置
for(int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
hash[ch - 'a'] = i;
}
int start = 0; // 记录分割点开始的位置
int right = 0; // 记录当前的最远位置
for(int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
right = Math.max(right, hash[ch - 'a']);
// 当当前的索引等于当前出现的所有字母的最远位置时,说明找到了分割点
if(i == right) {
res.add(right - start + 1);
start = i + 1;
}
}
return res;
}
56. 合并区间
public int[][] merge(int[][] intervals) {
// 按左区间排序
Arrays.sort(intervals, new Comparator<int[]>() {
public int compare(int[] i1, int[] i2) {
return Integer.compare(i1[0], i2[0]);
}
});
LinkedList<int[]> res = new LinkedList<>();
int[] curr = intervals[0]; // 当前合并后的区间
for(int i = 1; i < intervals.length; i++) {
// 判断intervals[i]能否和当前区间合并
if(intervals[i][0] <= curr[1]) {
curr[1] = Math.max(intervals[i][1], curr[1]);
}
else {
res.add(curr);
curr = intervals[i];
}
}
res.add(curr);
int array[][] = new int[res.size()][2];
return res.toArray(array);
}
其他贪心
53. 最大子数组和
贪婪
不累加负的count
。
public int maxSubArray(int[] nums) {
int max = nums[0];
int count = 0;
for(int i = 0; i < nums.length; i++) {
count += nums[i];
if(count > max) {
max = count;
}
count = Math.max(count, 0);
}
return max;
}
动态规划
不需要DP数组,只需要记录前一个索引的累加最大值即可。
public int maxSubArray(int[] nums) {
int max = nums[0];
int pre = max; // pre用来记录nums[i]的累加最大值(必须包含nums[i])
for(int i = 1; i < nums.length; i++) {
pre = Math.max(nums[i], nums[i] + pre);
if(pre > max) {
max = pre;
}
}
return max;
}
134. 加油站
由于题目规定至多存在一个解,找到第一个可行的位置即可
public int canCompleteCircuit(int[] gas, int[] cost) {
int currSum = 0; // 记录从start到当前位置的差值总和
int totalSum = 0; // 记录差值的总和
int start = 0; // 记录开始的地点
for(int i = 0; i < gas.length; i++) {
currSum += gas[i] - cost[i];
totalSum += gas[i] - cost[i];
// 更新start位置
if(currSum < 0) {
start = i + 1;
currSum = 0;
}
}
return totalSum >= 0 && start < gas.length ? start : -1;
}
968. 监控二叉树
后序遍历,从叶子节点的父亲开始放摄像头,然后每隔2层放置一次。节点共有3种状态:0表示没被覆盖;1表示放置了摄像头;2表示被摄像头覆盖了。
int count = 0;
public int minCameraCover(TreeNode root) {
traversal(root);
// 根节点可能未被覆盖,需要特殊考虑
if(root.val == 0) {
count++;
}
return count;
}
// 后续遍历
public void traversal(TreeNode root) {
if(root == null) {
return;
}
traversal(root.left);
traversal(root.right);
// 左右节点都为空,不操作
if(root.left == null && root.right == null) {
return;
}
// 左右节点有一个为空,则判断非空节点的状态
else if(root.left == null) {
// 1. 非空节点未被覆盖,当前节点需要放摄像头来覆盖子节点
if(root.right.val == 0) {
root.val = 1;
count++;
}
// 2. 非空节点有摄像头,则当前节点被覆盖
else if(root.right.val == 1) {
root.val = 2;
}
// 3. 非空节点已经被覆盖了,则当前节点保持未被覆盖的状态,等待父节点覆盖
}
else if(root.right == null) {
if(root.left.val == 0) {
root.val = 1;
count++;
}
else if(root.left.val == 1) {
root.val = 2;
}
}
// 左右节点都不为空,类似上面的操作
else {
if(root.left.val == 1 || root.right.val == 1) {
root.val = 2;
}
if(root.left.val == 0 || root.right.val == 0) {
root.val = 1;
count++;
}
}
}
精简版:
int count = 0;
public int minCameraCover(TreeNode root) {
traversal(root);
// 根节点可能未被覆盖,需要特殊考虑
if(root.val == 0) {
count++;
}
return count;
}
// 后续遍历
public int traversal(TreeNode root) {
if(root == null) {
return -1;
}
int left = traversal(root.left); // 获取左节点遍历后的值
int right = traversal(root.right); // 获取右节点遍历后的值
// 左右节点如果有一个放置了摄像头,当前节点被覆盖
if(left == 1 || right == 1) {
root.val = 2;
}
// 左右节点如果有一个是未被覆盖,则需要当前节点放置摄像头来覆盖子节点
if(left == 0 || right == 0) {
count++;
root.val = 1;
}
return root.val;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?