30.贪心算法

贪心算法

贪心算法(又称贪婪算法),核心思想在求解问题的时候,总是选择当前情况的最优解。贪心算法不是所有问题都能得到整体最优解,核心在于贪心算法的策略选择,选择的贪心策略需要具备无后效性,即某个状态以前的过程不能影响以后的状态至于当前状态有关。

贪心算法总是选择当前情况下的最优选择,而不是从整体最优考虑。贪心算法是做的局部最优选择,最终的结果可能在整体环境下不是最优解,但非常接近最优解。贪心算法也会因为策略不同得到不同的结果。

应用场景

集合覆盖问题

假设存在下面需要付费的广播台,以及广播台信号可以覆盖的地区。 如何选择最少的广播台,让所有的地区都可以接收到信号

在这里插入图片描述

实现思路
  • 遍历所有电台覆盖的区域,将他装在hashSet中记为allAreas
  • 创建临时maxKey指针指向每次循环中包含最多未覆盖地区的电台Key
  • 创建一个Selector的List用于记录需要选择的电台
  • 遍历每一个电台,将K电台的覆盖地区与allAreas求交集,maxKey指向交集个数最多的一个key(此处体现贪心算法核心,每次只选择最优解)
  • 遍历一遍一次得到maxKey加入到Selector到集合中作为需要选择的电台
  • maxKey置空 将被覆盖的电台从 allAreas中移除
  • 一直循环到allAreas不存在元素 此时Selector中存放的key即是选择方式
代码示例
public class GreedyAlgorithm {

    public static void main(String[] args) {
        //创建广播电台,放入到Map
        HashMap<String, HashSet<String>> broadcasts = new HashMap<String, HashSet<String>>();
        //将各个电台放入到broadcasts
        HashSet<String> hashSet1 = new HashSet<String>();
        hashSet1.add("北京");
        hashSet1.add("上海");
        hashSet1.add("天津");

        HashSet<String> hashSet2 = new HashSet<String>();
        hashSet2.add("广州");
        hashSet2.add("北京");
        hashSet2.add("深圳");

        HashSet<String> hashSet3 = new HashSet<String>();
        hashSet3.add("成都");
        hashSet3.add("上海");
        hashSet3.add("杭州");


        HashSet<String> hashSet4 = new HashSet<String>();
        hashSet4.add("上海");
        hashSet4.add("天津");

        HashSet<String> hashSet5 = new HashSet<String>();
        hashSet5.add("杭州");
        hashSet5.add("大连");

        //加入到map
        broadcasts.put("K1", hashSet1);
        broadcasts.put("K2", hashSet2);
        broadcasts.put("K3", hashSet3);
        broadcasts.put("K4", hashSet4);
        broadcasts.put("K5", hashSet5);

        // 获取所有的地区集合
        Set<String> allAreas = new HashSet<>();
        for (HashSet<String> value : broadcasts.values()) {
            allAreas.addAll(value);
        }

        // 定义一个maxKey指针 指向当前覆盖了最多未覆盖地区的Key
        String maxKey = null;
        // 定义一个临时set用于暂存key中覆盖的电台
        Set<String> tempSet = new HashSet<>();
        // 定义一个list 用于保存求解的电台结果
        List<String> selector = new ArrayList<>();

        while (allAreas.size() > 0) {
            // 遍历所有的key 确定maxKey的值
            for (String key : broadcasts.keySet()) {
                tempSet.clear();
                tempSet.addAll(broadcasts.get(key));
                tempSet.retainAll(allAreas);
                // 如果maxKey为空直接指向当前key集合
                if (maxKey == null) {
                    maxKey = key;
                } else {
                    // 如果maxKey不为空 需要判断 maxKey所覆盖的区域 如果比当前key覆盖的区域还要少 一定maxKey指向当前电台
                    broadcasts.get(maxKey).retainAll(allAreas);
                    // 当心算法的核心 一直判断当前的交集个数是不是大于 之前maxKey的交集个数 如果是则将maxKey重新赋值
                    if (tempSet.size() > broadcasts.get(maxKey).size()) {
                        maxKey = key;
                    }
                }
            }
            // 确定了maxKey之后从allAreas中移除已经选择了的地区
            if (maxKey != null) {
                selector.add(maxKey);
                allAreas.removeAll(broadcasts.get(maxKey));
                // 重置maxKey
                maxKey = null;
            }
        }
        System.out.println(selector);
    }
}
收益最大化问题 摘自leetcode121

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

注意:你不能在买入股票前卖出股票。

  • 核心就是找出所有股票中的最小值购入,然后从之后的最大值抛出即可得到收益最大化
public class MaxProfit121 {

    public static void main(String[] args) {
        int[] input = {7, 1, 5, 3, 6, 4};
        System.out.println(maxProfit(input));
    }

    public static int maxProfit(int[] prices) {
        if (prices.length == 0) return 0;
        int min = prices[0];
        int maxProfit = 0;
        for (int i = 0; i < prices.length; i++) {
            int price = prices[i];
            if (min > price) {
                // 贪心思想 每次取最小
                min = price;
            } else {
                // 贪心思想 每次取最大
                maxProfit = Math.max(price - min, maxProfit);
            }
        }
        return maxProfit;
    }
}
收益问题变种摘自leetcode122

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

  • 与上一个案例区别在于可以多次交易
  • 核心思想 对于 [a, b, c, d],如果有 a <= b <= c <= d ,那么最大收益为 d - a。而 d - a = (d - c) + (c - b) + (b - a) ,因此当访问到一个 prices[i] 且 prices[i] - prices[i-1] > 0,那么就把 prices[i] - prices[i-1] 添加到收益中。
   public static int maxProfit(int[] prices) {
        int maxProfit = 0;
        if (prices.length <= 1) return maxProfit;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] > prices[i - 1]) maxProfit += (prices[i] - prices[i - 1]);
        }
        return maxProfit;
    }
这道题还有一种可以使用动态规划的解法
  • 假定矩阵dp横坐标为第i天的股票 纵坐标为 买或者卖股票的最大收益 0 为卖掉股票 1为买股票

    • 76431
      卖000>6-7=-1=>00>4-6 =>00>3-4 =>00>1-3 =>0
      买1-7-6-4-3-1

      最终结果为0

    • 715364
      卖000 > 1-7 => 00<5-1=4 => 44>3-1 => 44<6+1 =>77> 4+1 =>7
      买1-7-1-1>0-5 => -1-1 < 4-3=>11 > 4-6 =>11<7-4=>3

      最大收益为7

  • 买股票的最大收益为 上次交易的最大收益 - 当前股票价格 转成状态方程为 max(dp(i-1,0),dp(i-1,1)-prices[i])

  • 买ath.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);

public class MaxProfit122ByDynamic {

    public static void main(String[] args) {
        int[] prices = {7, 6, 4, 3, 1};
        System.out.println(maxProfit(prices));
    }

    public static int maxProfit(int[] prices) {
        int len = prices.length;
        if (len < 2) {
            return 0;
        }
        // 0 卖股票
        // 1 买股票
        /**     1   2   3   4   5
         * 卖0  0   2-1 3-1
         * 买1  -1  -1  -1
         * 状态转移方程 收益 int[i][0] = max(int[i-1][0], prices[i] + int[i-1][1]) # 收入减去最低支出 现在支出是负数 所以需要+
         *            支出 int[i][1] = max(int[i-1][0],int[i-1][0] - prices[i]) # 上一次的收入减去当前股票的支出
         */
        int[][] dp = new int[prices.length][2];
        dp[0][0] = 0;
        dp[0][1] = -prices[0];

        for (int i = 1; i < dp.length; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], prices[i] + dp[i - 1][1]);
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
        }
        return dp[prices.length - 1][0];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值