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;
}
posted @   lv6laserlotus  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
点击右上角即可分享
微信分享提示