(基于java) 算法笔记 —— 贪心算法

这篇博客详细介绍了贪心算法的概念和应用,通过分析LeetCode中的典型题目,如分发饼干、无重叠区间、种花问题等,阐述了解题思路和Java实现。贪心算法将大问题分解为局部最优,以达到全局最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

贪心算法的学习,简单笔记

1、算法解释

贪心算法/贪心思想:

  • 将全局大问题,分解成若干局部小问题
  • 使局部小问题为最优
  • 全局大问题成为最优

2、分配问题

① LeetCode 455 分发饼干

解题思想——贪心思想

  • 对于【孩子】、【饼干】数组,先进行排序
  • 从左往右,用饼干【满足】孩子
    • 如果满足,当前的孩子和饼干就“移出”
    • 如果不满足,当前饼干“移出”,让后面的饼干来
  • 返回输出满足孩子的总数量

Java解答——贪心解法

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int child = 0, cookie = 0;
        while(child < g.length && cookie < s.length){
            if(g[child] <= s[cookie]){ // 孩子能吃饱
                child++;    // 孩子后移
            } 
            cookie++;   // 饼干后移
        }
        return child;
    }
}

② LeetCode 135 分发糖果

解题思想——贪心思想

  • 创建两个临时数组,用于存放分数高而多获得的糖果
  • 从左往右、从右往左,两次遍历,相邻的比较,更新糖果数量
  • 最后将两个临时数组中,每个孩子获得最多的糖果数加和
  • 如:
    • // Input [1 2 3 2 1]
    • // candy_left [0 1 2 0 0]
    • // candy_right [0 0 2 1 0]
    • // sum = len + 0 + 1 + 2 + 1 + 0 = 9

Java解答——贪心解法

class Solution {
    public int candy(int[] ratings) {
        int len = ratings.length;
        int sum_candy = len;	// 每位孩子至少得一颗糖果
        int[] candy_left = new int[len];
        int[] candy_right = new int[len];

        for(int i = 0; i < len - 1; i++){	// for循环,确定两个临时数组的值
            if(ratings[i] < ratings[i+1]){  // 右边分数比左边大
                candy_left[i+1] = candy_left[i] + 1;  // 右边糖果比左边的多1
            }
            if(ratings[len-1-i]<ratings[len-2-i]){  // 左边分数比右边大
                candy_right[len-2-i] = candy_right[len-1-i] + 1;
            }
        }
        for(int i = 0; i < len; i++){	// for循环,用于计算糖果总和
            sum_candy+= Math.max(candy_left[i], candy_right[i]);
        }
        return sum_candy;
    }
}

3、区间问题

① LeetCode 435 无重叠区间

解题思想——贪心思想

  • 首先将各个区间,根据右边界的大小重新排序
  • 如果下个区间的左边界比当前区间的右边界大,说明无重叠
  • 关键在于对各区间的排序,即对二维数组的排序
    Java中的二维数组排序
/*
注意compare 排序中默认升序:
     返回 1 == true 代表降序,我想调整顺序
     返回 -1 代表升序
*/

 Arrays.sort(arrays, new Comparator<int[]>() {
     @Override
     public int compare(int[] a, int[] b) {
		//a[i]里面的i指的是按照每一行的第i列进行排序
		// 所以a[0]就是把按照第0列的那个数对所有行进行排序,b[0]既代表其他行的数字
		//     a[1]就是按照第一列进行排序。
         if(a[0]==b[0]){
		//如果第 0列的数字相等,那么按第 1列的降序
             return b[1]-a[1];
         }else{
		//如果第 0列的数字不等,那么按第 0列的升序
             return a[0]-b[0];
         }
     }
 });

Java解答——贪心解法

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        if (intervals.length == 0) {
            return 0;
        }
        
        Arrays.sort(intervals, new Comparator<int[]>() {    // 对二维数组进行排序
            public int compare(int[] interval1, int[] interval2) {
                return interval1[1] - interval2[1];
            }
        });

        int n = intervals.length;
        int right = intervals[0][1];
        int ans = 1;
        for (int i = 1; i < n; ++i) {
            if (intervals[i][0] >= right) { // 下个区间左边界比当前区间有边界大
                ++ans;      // 无重叠,加一
                right = intervals[i][1];
            }
        }
        return n - ans;
    }
}

4、练习

① LeetCode 605 种花问题

解题思想——贪心思想

  • 从左至右挨个判断,当前位置是否合适种植
  • 若当前位置已经种植,则位置指针加2;若当前位置无,再结合判断临近位置是否没有,以此确定是否种植

Java解答——贪心解法

class Solution {
	public boolean canPlaceFlowers(int[] flowerbed, int n) {
		// 如果花种完了,或者花床检查完了,都停止遍历
		for (int i = 0, len = flowerbed.length; i < len && n > 0;) {
			if (flowerbed[i] == 1) {
            	//即如果,当前i位置已经种植了话,那么下一个可以种花的位置是 i+2
				i += 2;
			} else if (i == flowerbed.length - 1 || flowerbed[i + 1] == 0) {
            	//这里很关键
            	//如果是最后一个位置了,那么肯定能够种植(i==flowerbed.length-1)
            	//如果不是,则还需要确保 可种花的位置(i+2)紧邻其后的(i+2+1)的位置没有种植(flowerbed[i+1]==0)
	            //只有这样才可以种植
				n--;
        	    //同时找出下一个可以种植的位置
				i += 2;
			} else {
    	        //这种情况是flowerbed[i+2+1]=1,所以下次循环就从这里重新开始判断其后可种植的位置
				i += 3;
			}
		}
		return n <= 0;
	}
}

② LeetCode 452 用最少数量的箭引爆气球

解题思想——贪心思想

  • 类似于435题的区间问题
  • 先根据各区间右端点排序
  • 根据当前区间右端点与下个区间左端点判断是否重叠
  • 找到重叠范围的右端点,再继续与下下个区间比较判断

Java解答——贪心解法

class Solution {
    public int findMinArrowShots(int[][] points) {
        if (points.length == 0) {
            return 0;
        }
        Arrays.sort(points, new Comparator<int[]>() {
            public int compare(int[] point1, int[] point2) {
                if (point1[1] > point2[1]) {
                    return 1;
                } else if (point1[1] < point2[1]) {
                    return -1;
                } else {
                    return 0;
                }
            }
        });
        int pos = points[0][1];
        int ans = 1;
        for (int[] balloon: points) {
            if (balloon[0] > pos) {
                pos = balloon[1];
                ++ans;
            }
        }
        return ans;
    }
}


LeetCode435 和 LeetCode452 的二维数组区间排序,
一个是

        Arrays.sort(points, new Comparator<int[]>() {
            public int compare(int[] point1, int[] point2) {
                return point1[1] - point2[1];
            }
        });

另一个是

        Arrays.sort(points, new Comparator<int[]>() {
            public int compare(int[] point1, int[] point2) {
                if (point1[1] > point2[1]) {
                    return 1;
                } else if (point1[1] < point2[1]) {
                    return -1;
                } else {
                    return 0;
                }
            }
        });

因为前者无法处理[[-2147483646,-2147483645],[2147483646,2147483647]]用例,会出错

③ LeetCode 763 划分字母区间

解题思想——贪心思想

  • 从左到右遍历字符串,遍历的同时维护当前片段的开始下标 start 和结束下标 end,初始时start=end=0。
  • 对于每个访问到的字母 c,得到当前字母的最后一次出现的下标位置 endc ,则当前片段的结束下标一定不会小于endc ,因此令end=max(end,endc)。
  • 当访问到下标 end 时,当前片段访问结束,当前片段的下标范围是[start,end],长度为end−start+1,将当前片段的长度添加到返回值,然后令 start=end+1,继续寻找下一个片段。
  • 重复上述过程,直到遍历完字符串。

Java解答——贪心解法

class Solution {
    public List<Integer> partitionLabels(String S) {
        int[] last = new int[26];
        int length = S.length();
        for (int i = 0; i < length; i++) {
            last[S.charAt(i) - 'a'] = i;
        }
        List<Integer> partition = new ArrayList<Integer>();
        int start = 0, end = 0;
        for (int i = 0; i < length; i++) {
            end = Math.max(end, last[S.charAt(i) - 'a']);
            if (i == end) {
                partition.add(end - start + 1);
                start = end + 1;
            }
        }
        return partition;
    }
}

④ LeetCode 122 买卖股票的最佳时机 II

解题思想——贪心思想

  • 贪心算法只能用于计算最大利润,计算的过程并不是实际的交易过程

Java解答——贪心解法

class Solution {
    public int maxProfit(int[] prices) {
        int ans = 0;
        int n = prices.length;
        for (int i = 1; i < n; ++i) {
            ans += Math.max(0, prices[i] - prices[i - 1]);
        }
        return ans;
    }
}

⑤ LeetCode 406 根据身高重建队列

解题思想——贪心思想

  • 核心思想:高个子先站好位,矮个子插入到K位置上,前面肯定有K个高个子,矮个子再插到前面也满足K的要求
  • 渔(套路):一般这种数对,还涉及排序的,根据第一个元素正向排序,根据第二个元素反向排序,或者根据第一个元素反向排序,根据第二个元素正向排序,往往能够简化解题过程

Java解答——贪心解法

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        //按数组第一个元素进行降序,按第二个元素进行升序
        Arrays.sort(people, new Comparator<int[]>() {
            public int compare(int[] person1, int[] person2){
                if (person1[0] != person2[0]){
                    //第一个元素不相等时,第一个元素降序
                    return person2[0] - person1[0];
                }else{
                    //第一个元素相等时,第二个元素升序
                    return person1[1] - person2[1];
                }
            }
        });
        //新建一个list,用于保存结果集
        List<int[]> list = new LinkedList<>();
        for (int i = 0; i < people.length; i++) {
            if (list.size() > people[i][1]){
                //结果集中元素个数大于第i个人前面应有的人数时,将第i个人插入到结果集的 people[i][1]位置
                list.add(people[i][1],people[i]);
            }else{
                //结果集中元素个数小于等于第i个人前面应有的人数时,将第i个人追加到结果集的后面
                list.add(list.size(),people[i]);
            }
        }
        //将list转化为数组,然后返回
        return list.toArray(new int[list.size()][]);
    }
}

⑥ LeetCode 655 非递减数列

解题思想——贪心思想

  • 比较临近两个值的大小,若后者大于等于前者则继续循环
  • 否则计数加1,并且后者值修改为等于前者

Java解答——贪心解法

class Solution {
    public boolean checkPossibility(int[] nums) {
        int count = 0;

        for (int i = 1; i < nums.length; i++) {
            if (nums[i] < nums[i - 1]) {
                if (i == 1 || nums[i] >= nums[i - 2]) {
                    nums[i - 1] = nums[i];
                } else {
                    nums[i] = nums[i - 1];
                }
                if (++count > 1) {
                    return false;
                }
            }
        }
        return true;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值