贪心算法

142 阅读3分钟

在某一个标准下,优先考虑最满足标准的样本,最后考虑最不满足标准的样本,最终得到一个答案的算法,叫做贪心算法。也就是说,所做出的是在某种意义上的局部最优解。

在笔试中可能会涉及,但是在面试中不会有,因为没有区分度。

贪心策略的题目举例:(下面用到了排序)

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

    public static class Program{
        public int start;
        public int end;
        //定义每个会议的开始时间和结束时间
        public Program(int start, int end){
            this.start = start;
            this.end = end;
        }
    }

    public static class ProgramComparator implements Comparator<Program>{
        @Override
        public int compare(Program o1, Program o2) {
            //升序】
            //以结束时间进行排序
            return o1.end - o2.end;
        }
    }

    //贪心策略,以那一个会议的结束时间早进行安排
    //start是当前的时间点
    public static int bestArrange(Program[] programs, int start){
        Arrays.sort(programs, new ProgramComparator());
        int result = 0;
        for (int i = 0; i < programs.length; i++){
            if (start <= programs[i].start){
                result++;
                start = programs[i].end;
            }
        }
        return result;
    }

贪心算法需要这样做:

  1. 实现一个不依靠贪心策略的解法X,可以使用暴力解题
  2. 脑补出贪心策略A、贪心策略B、贪心策略C...
  3. 用解法X和对数器,去验证每一个贪心策略,用实验的方式得知那个贪心策略正确
  4. 不要去纠结贪心策略的证明

贪心策略在实现时,经常使用到的技巧:

  1. 根据某标准建立一个比较器来排序
  2. 根据某标准建立一个比较器来组成堆

贪心策略的题目举例:(下面用到了堆)

一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金条,不管切成长度多大的两半,都要花费20个铜板。一群人想整分整块金条,怎么分最省铜板?

例如,给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30=60。金条要分成10,20,30三个部分。如果先把长度60的金条分成10和50,花费60;再把长度50的金条分成20和30,花费50;一共花费110铜板。但是如果先把长度60的金条分成30和30,花费60;再把长度30金条分成10和20,花费30;一共花费90铜板。输入一个数组,返回分割的最小代价。

    public static void main(String[] args) {
        int[] arr = { 6, 7, 8, 9 };
        System.out.println(lessMoney(arr));
    }
    public static int lessMoney(int[] arr){
        PriorityQueue<Integer> pQ = new PriorityQueue<>();
        for (int i = 0; i < arr.length; i++){
            pQ.add(arr[i]);
        }
        int sum = 0;
        int cur = 0;
        //这个就是哈夫曼编码
        while(pQ.size() > 1){
            cur = pQ.poll() + pQ.poll();
            sum += cur;
            pQ.add(cur);
        }
        return sum;
    }

输入:正数数组 costs、正数数组 profits、正数k、正数m

含义:costs[i]表示i号项目的花费、profits[i]表示ⅰ号项目在扣除花费之后还能挣到的钱(利润)、k表示你只能串行的最多做k个项目、m表示你初始的资金

说明:你每做完一个项目,马上获得的收益,可以支持你去做下一个项目。

输出:你最后获得的最大钱数。

    public static class Node{
        public int p;
        public int c;

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

    public static class MinCostComparator implements Comparator<Node>{
        //花费小根堆来排
        @Override
        public int compare(Node o1, Node o2) {
            return o1.c - o2.c;
        }
    }

    public static class MaxProfitComparator implements Comparator<Node>{
        //利润大根堆来排
        @Override
        public int compare(Node o1, Node o2) {
            return o2.p - o1.p;
        }
    }

    public static int findMaximizedCapital(int k, int W, int[] Profits, int[] Capital){
        Node[] nodes = new Node[Profits.length];
        for (int i = 0; i < Profits.length; i++){
            nodes[i] = new Node(Profits[i], Capital[i]);
        }
        PriorityQueue<Node> minCostQ = new PriorityQueue<>(new MinCostComparator());
        PriorityQueue<Node> maxProfitQ = new PriorityQueue<>(new MaxProfitComparator());
        //小根堆的形式
        for (int i = 0; i < nodes.length; i++){
            minCostQ.add(nodes[i]);
        }
        //k是允许操作的次数
        for (int i = 0; i < k; i++){
            //peek返回顶上的节点并不删除,poll返回顶上的节点并删除
            //满足现有花费的项目才从小根堆中弹出来放进利润大根堆中
            while (!minCostQ.isEmpty() && minCostQ.peek().c <= W){
                maxProfitQ.add(minCostQ.poll());
            }
            //没有条件满足可以支撑花费了,所以对应的利润堆里也没有节点了
            if (maxProfitQ.isEmpty()){
                return W;
            }
            W += maxProfitQ.poll().p;
        }
        //返回总利润
        return W;
    }

给定一个字符串类型的数组strs,找到一种拼接方式,使得把所有字符串拼起来之后形成的 字符串具有最小的字典序

    public static void main(String[] args) {
        String[] strs1 = {"jibw", "ji", "jp", "bw", "jibw"};
        System.out.println(lowestString(strs1));
    }
    
    public static String lowestString(String[] strs){
        if (strs == null || strs.length == 0){
            return "";
        }
        Arrays.sort(strs, new MyComparator());
        //注意如果要是比较器中的是Comparator<Integer>类似这样的类型,那么写成Arrays.sort(nums,new MyComparator<Integer>())
        //,而且前提是nums要从int[]型的数组转变成Integer型的数组,可以填上这样一行代码使得int[]型的数组转变成Integer型的数组:
        //Integer[] ib= IntStream.of(nums).boxed().collect(Collectors.toList()).toArray(new Integer[0]);
        String res = "";
        for (int i = 0; i < strs.length; i++){
            res += strs[i];
        }
        return res;
    }
    
    public static class MyComparator implements Comparator<String>{
        //贪心策略,看(前 + 后)拼接和(后 + 前)拼接谁小放前面,如果在堆中就是小根堆
        @Override
        //compareTo类似于o1- o2升序排列
        public int compare(String o1, String o2) {
            return (o1 + o2).compareTo(o2 + o1);
        }
    }