贪心算法.

什么是贪心算法

1)最自然智慧的算法

2)用一种局部最功利的标准,总是做出在当前看来是最好的选择

3)难点在于证明局部最功利的标准可以得到全局最优解

4)对于贪心算法的学习主要以增加阅历和经验为主

贪心算法求解的标准过程

  1. 分析业务

  2. 根据业务逻辑找到不同的贪心策略

  3. 对于能举出反例的策略直接跳过,不能举出反例的策略要证明有效性

    这往往是特别困难的,要求数学能力很高且不具有统一的技巧性

贪心算法的解题套路

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

第一题

会议安排

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

贪心策略:选结束时间早的

package com.lzf2.class11;

import java.util.Arrays;
import java.util.Comparator;

public class BestArrange {
    //会议
    public static class Program {
        public int start;
        public int end;

        public Program(int start, int end) {
            this.start = start;
            this.end = end;
        }
    }
    // 会议的开始时间和结束时间,都是数值,不会 < 0
    public static int bestArrange2(Program[] programs) {
        if(programs == null || programs.length<0){
            return 0;
        }
        //根据结束时间排序
        Arrays.sort(programs,new ProgramCompartor());
        int curEnd = 0;
        int result = 0;
        for (Program program : programs) {
            if(program.start>=curEnd){
                result++;
                curEnd = program.end;
            }
        }
        return result;
    }

    public static class ProgramCompartor implements Comparator<Program> {
        @Override
        public int compare(Program o1, Program o2) {
            return o1.end - o2.end;
        }
    }
    //for test
    // 暴力!所有情况都尝试!
    public static int bestArrange1(Program[] programs) {
        if (programs == null || programs.length == 0) {
            return 0;
        }
        return process(programs, 0, 0);
    }

    // 还剩下的会议都放在programs里
    // done之前已经安排了多少会议的数量
    // timeLine目前来到的时间点是什么

    // 目前来到timeLine的时间点,已经安排了done多的会议,剩下的会议programs可以自由安排
    // 返回能安排的最多会议数量
    public static int process(Program[] programs, int done, int timeLine) {
        if (programs.length == 0) {
            return done;
        }
        // 还剩下会议
        int max = done;
        // 当前安排的会议是什么会,每一个都枚举
        for (int i = 0; i < programs.length; i++) {
            if (programs[i].start >= timeLine) {
                Program[] next = copyButExcept(programs, i);
                max = Math.max(max, process(next, done + 1, programs[i].end));
            }
        }
        return max;
    }

    public static Program[] copyButExcept(Program[] programs, int i) {
        Program[] ans = new Program[programs.length - 1];
        int index = 0;
        for (int k = 0; k < programs.length; k++) {
            if (k != i) {
                ans[index++] = programs[k];
            }
        }
        return ans;
    }
    public static Program[] generatePrograms(int programSize, int timeMax) {
        Program[] ans = new Program[(int) (Math.random() * (programSize + 1))];
        for (int i = 0; i < ans.length; i++) {
            int r1 = (int) (Math.random() * (timeMax + 1));
            int r2 = (int) (Math.random() * (timeMax + 1));
            if (r1 == r2) {
                ans[i] = new Program(r1, r1 + 1);
            } else {
                ans[i] = new Program(Math.min(r1, r2), Math.max(r1, r2));
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        int programSize = 12;
        int timeMax = 20;
        int timeTimes = 1000000;
        for (int i = 0; i < timeTimes; i++) {
            Program[] programs = generatePrograms(programSize, timeMax);
            if (bestArrange1(programs) != bestArrange2(programs)) {
                System.out.println("Oops!");
            }
        }
        System.out.println("finish!");
    }
}

第二题

金条分割

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

例如,给定数组{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铜板。
输入一个数组,返回分割的最小代价。

哈夫曼编码就是最优分割


import java.util.PriorityQueue;

public class LessMoneySplitGold {
    // 纯暴力!
    public static int lessMoney1(int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        return process(arr, 0);
    }

    // 等待合并的数都在arr里,pre之前的合并行为产生了多少总代价
    // arr中只剩一个数字的时候,停止合并,返回最小的总代价
    public static int process(int[] arr, int pre) {
        if (arr.length == 1) {
            return pre;
        }
        int ans = Integer.MAX_VALUE;
        for (int i = 0; i < arr.length; i++) {
            for (int j = i + 1; j < arr.length; j++) {
                ans = Math.min(ans, process(copyAndMergeTwo(arr, i, j), pre + arr[i] + arr[j]));
            }
        }
        return ans;
    }

    public static int[] copyAndMergeTwo(int[] arr, int i, int j) {
        int[] ans = new int[arr.length - 1];
        int ansi = 0;
        for (int arri = 0; arri < arr.length; arri++) {
            if (arri != i && arri != j) {
                ans[ansi++] = arr[arri];
            }
        }
        ans[ansi] = arr[i] + arr[j];
        return ans;
    }

    public static int lessMoney2(int[] arr) {
        //优先级队列,小根堆
        PriorityQueue<Integer> pQ = new PriorityQueue<>();
        //结果,最终需要的代价
        int sum = 0;
        //全部放入小根堆
        for (int i = 0; i < arr.length; i++) {
            pQ.add(arr[i]);
        }
        //弹出两个组成一个,在放回小根堆中。
        //每次组成的结果累加就是代价
        int cur = 0;
        while (pQ.size() > 1) {
            cur = pQ.poll() + pQ.poll();
            pQ.add(cur);
            sum += cur;
        }
        return sum;
    }


    // for test
    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) (Math.random() * (maxValue + 1));
        }
        return arr;
    }

    public static void main(String[] args) {
        int testTime = 100000;
        int maxSize = 6;
        int maxValue = 1000;
        for (int i = 0; i < testTime; i++) {
            int[] arr = generateRandomArray(maxSize, maxValue);
            if (lessMoney1(arr) != lessMoney2(arr)) {
                System.out.println("Oops!");
            }
        }
        System.out.println("finish!");
    }
}

第三题

做项目获得的最大钱数

输入: 正数数组costs、正数数组profits、正数K、正数M
costs[i]表示i号项目的花费
profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润)
K表示你只能串行的最多做k个项目
M表示你初始的资金
说明: 每做完一个项目,马上获得的收益,可以支持你去做下一个项目。不能并行的做项目。
输出:你最后获得的最大钱数。

package com.lzf2.class11;

import java.util.Comparator;
import java.util.PriorityQueue;

public class IPO {

    private static class Program{
        int p;
        int c;

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

    /**
     *
     * @param K 最多K个项目
     * @param W 初始资金
     * @param profits 和capital等长,表示项目的花费
     * @param capital 和profits等长,表示项目的存利润
     * @return 最终最大的资金
     */
    public static int findMaximizedCapital(int K,int W,int[] profits,int[] capital){
        PriorityQueue<Program> minCostQ = new PriorityQueue<>(new MinCostComparator());
        PriorityQueue<Program> maxProfitQ = new PriorityQueue<>(new MaxProfitComparator());
        //1,建出每个项目,放到小根堆
        for (int i = 0; i < profits.length; i++) {
            minCostQ.add(new Program(profits[i],capital[i]));
        }
        //2.做 K 个项目
        for (int i = 0; i < K; i++) {
            while (!minCostQ.isEmpty() && minCostQ.peek().c <= W){
                maxProfitQ.add(minCostQ.poll());
            }
            //没有能做的项目,提前结束
            if (maxProfitQ.isEmpty()){
                return W;
            }
            W += maxProfitQ.poll().p;
        }
        return W;
    }


    private static class MinCostComparator implements Comparator<Program> {
        @Override
        public int compare(Program o1, Program o2) {
            return o1.c - o2.c;
        }
    }
    private static class MaxProfitComparator implements Comparator<Program> {
        @Override
        public int compare(Program o1, Program o2) {
            return o2.p - o1.p;
        }
    }
}

第四题

至少要几盏灯

给定一个字符串str,只由‘X’和‘.’两种字符构成。
‘X’表示墙,不能放灯,也不需要点亮
‘.’表示居民点,可以放灯,需要点亮
如果灯放在i位置,可以让i-1,i和i+1三个位置被点亮
返回如果点亮str中所有需要点亮的位置,至少需要几盏灯

i 位置是’X’:不用管直接去 i+1

i 位置是’.’:

  		1. i + 1 位置是X,i位置必须放灯,跳到i+2位置
  			2. i + 1 位置是. ,看i + 2是x,i + 1位置放灯跳到 i+ 3
  			3. i + 1 位置是. ,看i + 2是.,i + 1位置放灯跳到 i+ 3
package com.lzf2.class11;

import java.util.HashSet;

public class Light {
    public static int minLight(String road){
        if (road == null || road.length() == 0){
            return -1;
        }
        char[] chars = road.toCharArray();
        int light = 0;
        int index = 0;
        while (index < chars.length){
            if (chars[index] == 'X'){
                index++;
            }else {//index 位置是 .
                light++;
                if (index + 1 == chars.length){
                    break;
                }
                if (chars[index + 1] == 'X'){
                    index+=2;
                }else {//index+1 位置是 .
                    index+=3;
                }
            }
        }
        return light;
    }




    // for test
    public static int minLight1(String road) {
        if (road == null || road.length() == 0) {
            return 0;
        }
        return process(road.toCharArray(), 0, new HashSet<>());
    }

    // str[index....]位置,自由选择放灯还是不放灯
    // str[0..index-1]位置呢?已经做完决定了,那些放了灯的位置,存在lights里
    // 要求选出能照亮所有.的方案,并且在这些有效的方案中,返回最少需要几个灯
    public static int process(char[] str, int index, HashSet<Integer> lights) {
        if (index == str.length) { // 结束的时候
            for (int i = 0; i < str.length; i++) {
                if (str[i] != 'X') { // 当前位置是点的话
                    if (!lights.contains(i - 1) && !lights.contains(i) && !lights.contains(i + 1)) {
                        return Integer.MAX_VALUE;
                    }
                }
            }
            return lights.size();
        } else { // str还没结束
            // i X .
            int no = process(str, index + 1, lights);
            int yes = Integer.MAX_VALUE;
            if (str[index] == '.') {
                lights.add(index);
                yes = process(str, index + 1, lights);
                lights.remove(index);
            }
            return Math.min(no, yes);
        }
    }

    public static String randomString(int len) {
        char[] res = new char[(int) (Math.random() * len) + 1];
        for (int i = 0; i < res.length; i++) {
            res[i] = Math.random() < 0.5 ? 'X' : '.';
        }
        return String.valueOf(res);
    }

    public static void main(String[] args) {
        int len = 20;
        int testTime = 100000;
        for (int i = 0; i < testTime; i++) {
            String test = randomString(len);
            int ans1 = minLight1(test);
            int ans2 = minLight(test);
            if (ans1 != ans2) {
                System.out.println("oops!");
            }
        }
        System.out.println("finish!");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值