《数据结构与算法之美》- 分糖果&0-1背包问题&区间覆盖三道经典题目


注:此篇文章,基于前Google工程师王争编写的著作《数据结构与算法之美》,加以自身理解,编写而成。

1 分糖果

有m个糖果分给n个孩子吃。但是糖果少,孩子多(m<n)。每个糖果的大小不等,孩子对糖果的胃口大小也不等。如何给孩子分配糖果,才能尽可能满足更多的孩子?

1.1 代码实现

private static int dealArray(int[] child, int[] candy) {
    Arrays.sort(child);
    Arrays.sort(candy);
    int lenCh = child.length;
    int lenCa = candy.length;
    int result = 0;
    for (int i = 0, j = 0; i < lenCh && j < lenCa; i ++) {
        while(j < lenCa){
            if(candy[j] >= child[i]){
                result ++;
                j++;
                break;
            }
            j ++;
        }
    }
    return result;
}

1.2 解题思路

如果两个糖果大小不同,但都能满足某个小孩,那我们肯定是优先分配更小的那个,更大的那个,留着分配胃口更大的那个小孩。另外,分配糖果给两个胃口大小不同的小孩,对期望值的贡献度是一样的,但是胃口小的更容易被满足,所以我们应该优先分配胃口小的。

2 区间覆盖

假设有n个区间,从这n个区间选出某些区间,满足区间两两不想交(端点相交的情况不算相交),最多能选出多少个区间?

2.1 代码实现

private static List<Interval> findMaxNumIntervals(List<Interval> list) {
    List<Interval> result = new ArrayList<>();
    Collections.sort(list);
    int preRight = 0;
    for (int i = 0; i < list.size(); i++) {
        Interval current = list.get(i);
        if (current.left >= preRight) {
            result.add(current);
            preRight = current.right;
        }
    }
    return result;
}

private static class Interval implements Comparable<Interval> {

    private int left;
    private int right;

    public Interval(int left, int right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int compareTo(Interval o) {
        return this.right - o.right;
    }
}

2.2 解题思路

我们按照右端点从小到大的顺序,对这n个区间进行排序,然后每次选出左端点与已覆盖区间不重合,且右端点尽量小的区间,这样就能让未覆盖区间尽可能大,就可以放置更多的区间。这是一种贪心的选择方法。

3 0-1背包问题

对于一组不同重量,不可分割的物品。选择其中的一些物品放入背包中,求在不超过背包可承受重量的前提下,背包能放入的物品重量之和的最大值。

3.1 代码实现

//结果
private static int result = Integer.MIN_VALUE;
//备忘录
private static boolean[][] arrayFlag;
//背包能承受的最大重量
private static int maxWeight;
//物品数量
private static int totalNum;
//物品
private static int[] arrayArg;
private static int getMaxWeight(int[] array, int max) {
    arrayArg = array;
    totalNum = array.length;
    arrayFlag = new boolean[totalNum][max + 1];
    maxWeight = max;
    getWeight(0, 0);
    return result;
}
private static void getWeight(int current, int total) {
    //达到临界条件时,终止递归
    if (total == maxWeight || current == totalNum) {
        if (total > result) {
            result = total;
        }
        return;
    }
    //重复状态
    if (arrayFlag[current][total]) {
        return;
    }
    arrayFlag[current][total] = true;
    //当前物品不放入背包
    getWeight(current + 1, total);
    if (total + arrayArg[current] <= maxWeight) {
        //当前物品放入背包
        getWeight(current + 1, total + arrayArg[current]);
    }
}

3.2 解题思路

在这里插入图片描述

我们采用基于备忘录去重的递归求解方法来解答本题。以物品重量数组2、2、4、6、3为例。我们把整个求解过程,分成5个阶段。每个阶段会决定是否将当前物品放入背包中。是否有两种情况,与上图中的递归树中的节点一一对应。以f(1,2)为例,其表达的含义是,遍历完第一个节点后,将其放入背包中。同时,我们采用了”备忘录”,记录已经计算好的f(i,cw)。这样当再次计算到重复的f(i,cw)时,我们直接从备忘录中取出结果来用,不需要再递归计算,可以避免子问题被重复求解。同时达到临界条件时,如当前背包总重量已经等于其能承受的最大重量时。说明有最优解,方法直接返回,避免无效递归。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值