5.1 贪心算法

贪心算法

  • 最自然智慧的算法
  • 用一种局部最功利的标准,总是做出在当前看来是最好的选择
  • 难点在于证明局部最功利的标准可以得到全局最优解
  • 对于贪心算法的学习主要以增加阅历和经验为主

1. 组成最小字典序

给定一个由字符串组成的数组strs, 必须把所有的字符串拼接起来,返回所有可能的拼接结果中,字典序最小的结果

需要证明这两个点。太难了,证明麻烦

  1. a b c 表示 3 个字符串,a + b 表示 a 拼接上 b
    若 a + b <= b + a, b + c <= c + b 证明:a + c <= c + a
  2. 数据按照 1 的方式进行排序后得到的字符串是最小的字符串,交换任意两个位置,都会导致字典序变大

    public String minDictStr(List<String> strs) {
        List<String> strs2 = strs.stream()
	        .filter(it -> !"".equals(it))
	        .sorted((a, b) -> (a + b).compareTo(b + a))
	        .collect(Collectors.toList());
        return String.join("", strs2);
    }

    public String minDictStrCompare(List<String> strs) {
        if (strs.size() == 0) {
            return "";
        }
        Stack<String> vals = new Stack<>();
        List<String> res = new ArrayList<>();
        process(strs, vals, res);
        res.sort(String::compareTo);
        return res.get(0);
    }

    private void process(List<String> strs, Stack<String> vals, List<String> res) {
        if (strs.size() == 0) {
            res.add(String.join("", vals));
            return;
        }
        for (int i = 0; i < strs.size(); i++) {
            String str = strs.get(i);
            strs.remove(i);
            vals.add(str);
            process(strs, vals, res);
            strs.add(i, str);
            vals.pop();
        }
    }

    @Test
    public void test1() {
        for (int i = 0; i < 10000; i++) {
            List<String> strs = Reduce.stringList(8, 10);
            List<String> strs1 = new ArrayList<>(strs);
            String r1 = minDictStr(strs);
            String r2 = minDictStrCompare(strs);
            if (!r1.equals(r2)) {
                System.out.println(strs);
                System.out.println(r1);
                System.out.println(r2);
                r1 = minDictStr(strs1);
                r2 = minDictStrCompare(strs1);
                return;
            }
        }
    }

2. 安排最多会议

给一个二维数组,每个元素 [s, end] 表示会议的开始、结束时间,单位是秒,从 0 - 86400,一个会议室,一次只能安排一个会议,问:一天内,最多安排多少个会议。

    public int maxMeetNum(int[][] arr) {
        int res = 0;
        // 先按照结束时间进行排序
        Arrays.sort(arr, Comparator.comparingInt(a -> a[1]));
        // 当前时间点
        int t = 0;
        for (int[] se : arr) {
        	// 开始时间小于当前时间可以安排会议,如果大于,不能安排
            if (t <= se[0]) {
            	// 会议数加1
                res++;
                // 当前时间来到会议结束时间
                t = se[1];
            }
        }
        return res;
    }

    public int maxMeetNumCompare(int[][] arr) {
        return processMeetNum(arr, new int[arr.length], 0);
    }

    private int processMeetNum(int[][] arr, int[] decide, int i) {
        if (i == arr.length) {
            List<int[]> arr2 = new ArrayList<>();
            for (int j = 0; j < decide.length; j++) {
                if (decide[j] == 1) {
                    arr2.add(arr[j]);
                }
            }
            arr2.sort(Comparator.comparingInt(a -> a[1]));
            int t = 0;
            for (int[] ints : arr2) {
                if (t <= ints[0]) {
                    t = ints[1];
                } else {
                    return 0;
                }
            }
            return arr2.size();
        }
        decide[i] = 0;
        int max = processMeetNum(arr, decide, i + 1);
        decide[i] = 1;
        max = Math.max(max, processMeetNum(arr, decide, i + 1));
        decide[i] = 0;
        return max;
    }

    @Test
    public void test2() {
        for (int i = 0; i < 10000; i++) {
            int[][] arr = Reduce.lineArray(10, 0, 10);
            int r1 = maxMeetNum(arr);
            int r2 = maxMeetNumCompare(arr);
            if (r1 != r2) {
                System.out.println(Printer.print(arr));
                System.out.println(r1);
                System.out.println(r2);
                return;
            }
        }
    }

3. 切分金条(哈弗汉编码问题)

一块金条切成两半,是需要花费和长度数值一样的铜板的。
比如长度为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铜板。输入一个数组,返回分割的最小代价。

public int minMoney(int[] arr) {
        if (arr.length == 0) {
            return 0;
        }
        if (arr.length == 1) {
            return arr[0];
        }
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        for (int len : arr) {
            queue.offer(len);
        }
        int money = 0, v;
        while (queue.size() > 1) {
            // 每次取出最上面的两个进行合并
            v = queue.poll() + queue.poll();
            money += v;
            queue.offer(v);
        }
        return money;
    }

    public int minMoneyCompare(int[] arr) {
        return processMinMoney(arr, 0);
    }

    private int processMinMoney(int[] arr, int pre) {
        if (arr.length == 0) {
            return 0;
        }
        if (arr.length == 1) {
            return pre == 0 ? arr[0] : pre;
        }
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = i + 1; j < arr.length; j++) {
                min = Math.min(min, processMinMoney(mergeTwo(arr, i, j), pre + arr[i] + arr[j]));
            }
        }
        return min;
    }

    private int[] mergeTwo(int[] arr, int i, int j) {
        int[] arr2 = new int[arr.length - 1];
        int m = arr[i] + arr[j], idx = 0;
        for (int k = 0; k < arr.length; k++) {
            if (k != i && k != j) {
                arr2[idx++] = arr[k];
            }
        }
        arr2[idx] = m;
        return arr2;
    }

    @Test
    public void test3() {
        for (int i = 0; i < 1000; i++) {
            int[] arr = Reduce.array(8, 1, 10);
            int r1 = minMoney(arr);
            int r2 = minMoneyCompare(arr);
            if (r1 != r2) {
                System.out.println(Printer.print(arr));
                System.out.println(r1);
                System.out.println(r2);
                return;
            }
        }
    }

4. 最大收益数

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

  1. 选择所有我可以做的项目
  2. 从我可以做的项目中选择收益最高的一个项目,资金 = 已有资金 + 收益
   public int maxBenefit(int[] cost, int[] profits, int K, int M) {
        PriorityQueue<int[]> projects = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
        PriorityQueue<int[]> benefits = new PriorityQueue<>((a, b) -> b[1] - a[1]);
        for (int i = 0; i < cost.length; i++) {
            int[] v = new int[]{cost[i], profits[i]};
            projects.offer(v);
        }
        for (int i = 0; i < K; i++) {
            while (!projects.isEmpty() && projects.peek()[0] <= M) {
                benefits.add(projects.poll());
            }
            if (benefits.isEmpty()) {
                return M;
            }
            M += benefits.poll()[1];
        }
        return M;
    }

5. 放灯问题

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


    /**
     * 给定一个字符串str,只由 ‘X’ 和 ‘:’ 两种字符构成。
     * 'X’ 表示墙,不能放灯,也不需要点亮
     * ∵’ 表示居民点,可以放灯,需要点亮
     * 如果灯放在i位置,可以让i-1,i和i+1三个位置被点亮返回如果点亮str中所有需要点亮的位置,至少需要几盏灯
     */
    public int minLight(char[] chars) {
        int light = 0;
        int i = 0;
        while (i < chars.length) {
            if (chars[i] == 'X') {
                i++;
            } else {
                light++;
                if (i + 1 == chars.length) {
                    return light;
                } else if (chars[i + 1] == '.') {
                    i = i + 3;
                } else {
                    i = i + 2;
                }
            }
        }
        return light;
    }

    public int minLightCompare(char[] chars) {
        return processMinLight(chars, 0, 0, 0);
    }

    private int processMinLight(char[] chars, int p, int pre, int f) {
        if (p == chars.length - 1 && f == 0 && chars[p] == '.') {
            return pre + 1;
        }
        if (p >= chars.length) {
            return pre;
        }
        if (chars[p] == 'X') {
            if (f == 0) {
                return processMinLight(chars, p + 1, pre, 0);
            }
            return processMinLight(chars, p + 1, pre + 1, 0);
        }
        int min = processMinLight(chars, p + 2, pre + 1, 0);
        if (f == 1) {
            min = Math.min(min, processMinLight(chars, p + 2, pre + 1, 0));
        } else {
            min = Math.min(min, processMinLight(chars, p + 1, pre, 1));
        }
        return min;
    }

    @Test
    public void test5() {
        char[] arr1 = new char[]{'X', '.'};
        for (int i = 0; i < 10000; i++) {
            char[] arr = Reduce.chars(10, arr1);
            int r1 = minLight(arr);
            int r2 = minLightCompare(arr);
            if (r1 != r2) {
                System.out.println(new String(arr));
                System.out.println(r1);
                System.out.println(r2);
                r1 = minLight(arr);
                r2 = minLightCompare(arr);
                return;
            }
        }
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值