算法

五大常用算法——贪心算法

2020-03-29  本文已影响0人  yaco

汇总几个常见的贪心算法实现的问题


概述

这里主要总结一些涉及贪心算法的题目,对于贪心算法原理不作阐述。简单罗列一下搜集的资料。

贪心算法的定义:
贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。

解题的一般步骤是:
1.建立数学模型来描述问题;
2.把求解的问题分成若干个子问题;
3.对每一子问题求解,得到子问题的局部最优解;
4.把子问题的局部最优解合成原来问题的一个解。

以上较为官方的一些阐述,那么个人认为贪心最大的工作就是尝试,在稿子上尝试写出几个简单示例的贪心策略,验证成功直接coding即可。


IPO问题

LeeCode502. IPO

假设 力扣(LeetCode)即将开始其 IPO。为了以更高的价格将股票卖给风险投资公司,力扣 希望在 IPO 之前开展一些项目以增加其资本。 由于资源有限,它只能在 IPO 之前完成最多 k 个不同的项目。帮助 力扣 设计完成最多 k 个不同项目后得到最大总资本的方式。

给定若干个项目。对于每个项目 i,它都有一个纯利润 Pi,并且需要最小的资本 Ci 来启动相应的项目。最初,你有 W 资本。当你完成一个项目时,你将获得纯利润,且利润将被添加到你的总资本中。

总而言之,从给定项目中选择最多 k 个不同项目的列表,以最大化最终资本,并输出最终可获得的最多资本。

    /*
    -----------------------------------创建Node辅助贪心算法----------------------------------
    */
    // 创建项目类
    class Node {
        int c;      // 资本
        int p;      // 利润

        public Node(int c, int p) {
            this.c = c;
            this.p = p;
        }
    }

    public int findMaximizedCapital(int k, int W, int[] Profits, int[] Capital) {
        // 首先创建大小堆
        PriorityQueue<Node> cHeap = new PriorityQueue<Node>(new MinCapitalComparator());
        PriorityQueue<Node> pHeap = new PriorityQueue<Node>(new MaxProfitComparator());
        // 将capitalHeap构造成为资本的小顶堆
        for (int i = 0; i < Capital.length; i++) {
            cHeap.add(new Node(Capital[i], Profits[i]));
        }
        int ans = W;
        // 遍历k,进行k次交易
        for (int j = 0; j < k; j++) {
            while (!cHeap.isEmpty() && cHeap.peek().c <= ans) {
                pHeap.add(cHeap.poll());
            }
            if (pHeap.isEmpty()) return ans;
            ans += pHeap.poll().p;
        }
        // 返回利润
        return ans;
    }

    // 小顶堆改造
    public class MinCapitalComparator implements Comparator<Node> {

        @Override
        public int compare(Node o1, Node o2) {
            return o1.c - o2.c;
        }
    }

    // 大顶堆改造
    public class MaxProfitComparator implements Comparator<Node> {

        @Override
        public int compare(Node o1, Node o2) {
            return o2.p - o1.p;
        }
    }

金砖最小分割代价

传送门——分割最小代价

题目:
一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的 金条,不管切成长度多大的两半,都要花费20个铜板。一群人想整分整块金 条,怎么分最省铜板?例如,给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30=60. 金条要分成10,20,30三个部分。 如果, 先把长度60的金条分成10和50,花费50 再把长度50的金条分成20和30,花费50 一共花费110铜板。但是如果, 先把长度60的金条分成30和30,花费60 再把长度30金条分成10和20,花费30 一共花费90铜板。输入一个数组,返回分割的最小代价.

    public static int lessMoney(int[] arr) {
        //创建一个优先级队列(默认情况下按照升序进行排列)
        PriorityQueue<Integer> PQ = new PriorityQueue<Integer>(); 
        for (int i = 0; i < arr.length; i++) {
            PQ.add(arr[i]);
        }
        int res = 0;
        int sum = 0;
        while(PQ.size() > 1) {
            sum = PQ.poll() + PQ.poll();   // 弹出两个最小值求和
            res += sum;                            
            PQ.add(sum);                         // 累加sum之后再扔回堆中
        }
        return res;
    }

会议室相关问题

一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。 给你每一个项目开始的时间和结束的时间(给你一个数组,里面 是一个个具体的项目),你来安排宣讲的日程,要求会议室进行 的宣讲的场次最多。返回这个最多的宣讲场次。

    // 创建项目的实体类
    public static class Program{
        int start;  //项目开始时间
        int end;    //项目结束时间

        public Program(int start, int end){
            this.start = start;
            this.end = end;
        }
    }

    // 创建一个项目按照结束时间从小到大的排列顺序
    public static class MinEndTimeComparator implements Comparator<Program> {
        @Override
        public int compare(Program o1, Program o2) {
            return o1.end - o2.end;
        }
    }

    /**
     * 主函数入口
     * @param programs  给定项目的数组(包含项目的开始时间和结束时间)
     * @param start     会议室可以使用的开始时间
     * @return          最多会议室的使用次数
     */
    public static int bestArrange(Program[] programs, int start) {
        PriorityQueue<Program> queue = new PriorityQueue<>(new MinEndTimeComparator());
        for (int i = 0; i < programs.length; i++) {
            queue.add(programs[i]);
        }
        int res = 0;
        while(!queue.isEmpty()){
            if(start <= queue.peek().start) {
                res++;
                start = queue.poll().end;
            }else{
                queue.poll();
            }
        }
        return res;
    }

LintCode920——会议室

给定一系列的会议时间间隔,包括起始和结束时间[[s1,e1],[s2,e2],…(si < ei),确定一个人是否可以参加所有会议。

思路: 类似上一题(使用优先级队列)

/**
 * Definition of Interval:
 * public class Interval {
 *     int start, end;
 *     Interval(int start, int end) {
 *         this.start = start;
 *         this.end = end;
 *     }
 * }
 */

public class Solution {
    /**
     * @param intervals: an array of meeting time intervals
     * @return: if a person could attend all meetings
     */
    public boolean canAttendMeetings(List<Interval> intervals) {
        // Write your code here
        if(intervals.size() < 2) return true;
        PriorityQueue<Interval> heap = new PriorityQueue<Interval>(new MyConparator());
        for(Interval pro: intervals){
            heap.add(pro);
        }
        // h构建小顶堆结束,遍历检查后一项的start是否>=前一项的end
        int end = heap.poll().end;
        while(!heap.isEmpty()){
            if(heap.peek().start < end){
                return false;
            }
            end = heap.poll().end;
        }
        return true;
    }
    
    // n比较器,按照开始时间排序
    class MyConparator implements Comparator<Interval> {
        @Override
        public int compare(Interval o1, Interval o2){
            return o1.start - o2.start;
        }
    }
}

LeeCode1353. 最多可以参加的会议数目(经典)

给你一个数组 events,其中 events[i] = [startDayi, endDayi] ,表示会议 i 开始于startDayi ,结束于 endDayi 。

你可以在满足 startDayi <= d <= endDayi 中的任意一天 d 参加会议 i 。注意,一天只能参加一个会议。

请你返回你可以参加的 最大 会议数目


    public int maxEvents(int[][] events) {
        // 首先将所有会议按照会议开始时间进行排序
        Arrays.sort(events, new Comparator<int[]>(){
            @Override
            public int compare(int[] a, int[] b){
                return a[0] - b[0];
            }
        });
        // 创建优先级队列
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        int cur = 0;     // 当前处于第0天
        int index = 0;      // 会议按照最早开始时间存储的编号
        int n = events.length;   // 会议总数
        int ans = 0;             // 结果集
        // 循环结束条件
        while(index < n || !queue.isEmpty()){
            // 如果队列为空,将当前的项目结束时间扔进去,day调整为当前的时间
            if(queue.isEmpty()){
                queue.add(events[index][1]);
                cur = events[index++][0];
            }
            // 遍历后面的会议。如果开始时间在day时间之间,都将他们入独对列
            while(index < n && events[index][0] <= cur){
                queue.add(events[index++][1]);
            }
            // 如果当前会议可以进行
            if(queue.peek() >= cur){
                cur++;
                ans++;
            }
            //弹出会议
            queue.poll();
        }
        return ans;
    }


分发糖果

135. 分发糖果

老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。

你需要按照以下要求,帮助老师给这些孩子分发糖果:

每个孩子至少分配到 1 个糖果。
相邻的孩子中,评分高的孩子必须获得更多的糖果。

那么这样下来,老师至少需要准备多少颗糖果呢?

思路分析:(贪心策略)

class Solution {
    // 贪心算法求解
    public int candy(int[] ratings) {
        if (ratings.length == 1) return 1;
        int n = ratings.length;
        int[] candy = new int[n];
        Arrays.fill(candy,1);// 首先需要保证每个孩子手上有一颗糖
        // 从左至右遍历数组
        for (int i = 1; i < n; i++) {
            if(ratings[i] > ratings[i - 1]){
                candy[i] = candy[i - 1] + 1;
            }
        }
        // 从右到左再来一遍
        for (int i = n - 2; i >= 0; i--) {
            if(ratings[i] > ratings[i + 1] && candy[i] <= candy[i + 1]){
                candy[i] = candy[i + 1] + 1;
            }
        }
        // 返回结果集
        int ans = 0;
        for (int c : candy) {
            ans += c;
        }
        return ans;
    }
}

柠檬水找零

LeeCode860. 柠檬水找零

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。

顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

思路:只会收到5,10,20三种面个的货币,20无论如何都不会用作找零的面额不考虑。没后每次找零的时候优先用大额面额(5,10) , 如果找零后5元成负数,则一定不可能找零。
代码:

class Solution {
    public boolean lemonadeChange(int[] bills) {
        int five = 0;   // 代表5元面额
        int ten = 0;    // 代表10元面额
        //遍历数组
        for(int m : bills){
            if(m == 5){
                five++;
                continue;
            }
            if(m == 10){
                five--;
                ten++;
            }else{
                if(ten != 0){
                    five--;
                    ten--;
                }else{
                    five -= 3;
                }
            }
            // 每次找零之后,如果five变成了负,则无法找零
            if(five < 0) return false;
        }
        return true;
    }
}

分发饼干

LeeCode455. 分发饼干
难度简单145假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

思路: 典型贪心策略,尽可能的用小饼干喂饱胃口小的孩子

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        // 首先将两个数组进行排序
        Arrays.sort(g);
        Arrays.sort(s);
        int i = 0, j = 0, ans = 0;
        while(i < g.length && j < s.length){
            // 满足了一个小孩,s和g均后移,ans加1
            if(s[j] >= g[i]){
                i++;
                ans++;
            }
            // 不管当前饼干是否可以满足此小孩,指针都向后移
            j++;
        }
        return ans;
    }
}

上一篇下一篇

猜你喜欢

热点阅读