程序员的算法趣题:Q28 社团活动的最优分配方案(Java版)

题目说明

对学生而言,社团活动可能比学习还更重要。
假设你即将成为某新建学校的校长,学校里有150名想要运动的学生,
请你考虑要为他们准备哪些社团活动。
你调查各项运动所需的场地面积后得到了如表 7 所示的表格。
在确定活动场地时,也要考虑各个社团的人数。

Q28 表7.png

请选择一些社团活动,社团总人数不能超过 150 人,还要使场地面积最大。
求这个最大的面积的值。(背包问题)

【背包问题】问题的名称来源于如何选择最合适的物品放置于给定背包中。
问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,
在限定的总重量内,我们如何选择,才能使物品的总价格最高。

思路1

1.穷举:利用层层递归穷举所有选择
2.每层递归基于当下的情况做各种选择尝试,并统计出面积最大值返回给上一层
3.最外层的递归得到的就是所有选择中的面积最大值

代码1

public static void main(String[] args) {
    List<Club> clubs = new ArrayList<Club>(
            Arrays.asList(new Club("棒球", 11000, 40),
            new Club("足球", 8000, 30),
            new Club("排球", 400, 24),
            new Club("篮球", 800, 20),
            new Club("网球", 900, 14),
            new Club("田径", 1800, 16),
            new Club("手球", 1000, 15),
            new Club("橄榄球", 7000, 40),
            new Club("乒乓球", 100, 10),
            new Club("羽毛球", 300, 12)));

    int maxArea = choose(clubs, 150);

    System.out.println("maxArea = " + maxArea);
}
/**
 * 计算出当下情况(当前剩余可选社团、当前剩余人数情况下)所有选择中的面积最大值
 * @param clubs             剩余可选的社团
 * @param remainPeoNum      剩余的人数
 */
private static int choose(List<Club> clubs, int remainPeoNum) {
    // 入参检查
    if(remainPeoNum < 0 || clubs == null) return 0;

    int maxArea = 0;
    for (int i = 0; i < clubs.size(); i ++) {                       // 从剩余可选社团中选择一个
        Club club = clubs.get(i);
        if(club.getPeopleNum() > remainPeoNum){                     // 超出人数限制,跳过
            continue;
        }
        clubs.remove(i);
        remainPeoNum -= club.getPeopleNum();                        // 剩余人数减少
        int curArea = club.getArea() + choose(clubs, remainPeoNum); // 本次选择的面积最大值
        maxArea = curArea > maxArea ? curArea : maxArea;            // 保存较大的那个

        // 还原(恢复本次选择之前的状态,以便尝试下一种选择)
        remainPeoNum += club.getPeopleNum();
        clubs.add(i, club);
    }

    return maxArea;     // 本轮所有选择中的面积最大值
}
// 社团
public class Club{
    private String name;    // 社团名称(仅用于提升可读性)
    private int area;       // 占地面积
    private int peopleNum;  // 人数

    public Club(String name, int area, int peopleNum){
        this.name = name;
        this.area = area;
        this.peopleNum = peopleNum;
    }

    // 省略getter/setter方法

}

思路2

1.动态规划:依次判断每个社团是否选择。
2.为每一个社团创建一个一维数组,下标表示人数[0,150],元素的值表示当前人数情况下的最大占地面积
3.如果选择某个社团,则计算当前数组对应下标最大的占地面积

Q28 图17.png

代码2

public static void main(String[] args) {
    // {面积,人数}
    int[][] club = {{11000, 40}, {8000, 30}, {400, 24}, {800, 20}, {900, 14},
                    {1800, 16}, {1000, 15}, {7000,40}, {100, 10}, {300, 12}};

    int N = 150; // 人数限制

    // 社团选择范围[0,club.length]
    // 人数范围[0,N]
    int[][] area = new int[club.length+1 /*社团*/ ][N+1 /*人数*/ ];

    // 逐步增加可选社团(每一个新的社团出现,都可能改变指定人数限制下的最大面积)
    for(int i = 0; i < club.length; i ++){  // 0个社团~10个社团
        for (int j = 0; j < N+1; j++) {     // 人数
            // 是否选择club[i]社团
            if(j < club[i][1]){ // 未选:人数都不足club[i]社团的人数,肯定是没有选择该社团的情况。直接复制即可
                area[i+1][j] = area[i][j];
            }else{              // 已选:在area[i][j - club[i][1]/*人数*/]的基础上,增加club[i][0]的面积,比较后保存更大的面积
                int oldArea = area[i][j];                                // 之前的种种选择在人数为j时的最大面积
                int newArea = area[i][j - club[i][1]] + club[i][0];      // 如果本次选择club[i],则面积应该在人数减去club[i]的情况下的最大面积加上club[i]的面积
                area[i+1][j] = oldArea > newArea ? oldArea : newArea;    // 保存较大的值
            }
        }
    }

    // 随着可选社团数量的增加,数组中每个人数对应的最大面积在不断变化。最终area最后一个一维数组的下标和值就是所有社团参与后每个人数限定内的最大面积。
    for(int i = 0; i < area.length; i ++){
        System.out.println(Arrays.toString(area[i]));
    }

    System.out.println("maxArea = " + area[area.length-1][N]); // area[可选社团数量][人数上限] == 最大面积
}

结果

......此处省略很多内容......
maxArea = 28800

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值