题意整理

  • 有n个同学,每个同学都有一个聪明值和一个勤奋值。
  • 从这个n个同学中选出若干同学,使得总的聪明值累加和和勤奋值累加和最大,但是聪明值累加和、勤奋值累加和必须大于0。

方法一(单调栈)

1.解题思路

  • 牛妹想选择一些同学作为同事,如果最终有N个同学,那么总的组合数就是图片说明 (每个同学,要么选,要么不选)。同学数限定小于100,直接暴力递归进行搜索,最多有图片说明 次,肯定会超时。所以在每次选择时,要找出淘汰策略。
  • 我们用一个list记录所有可能的聪明值累加和与勤奋值累加和,假设当前累加和组合数为n,每新增一位同学,就多出n种组合,如果能提前淘汰不可能是最大总和的组合,就会减少很多无用搜索。当我们得到这图片说明 种组合后,首先按聪明值累加和进行排序,如果聪明值累加和相等,按勤奋值累加和排序(可以淘汰聪明值累加和相等,勤奋值累加和递增的情况)。然后维护一个单调栈,遍历list中的组合,由于是按聪明值从小到大排序,所以当前组合聪明值累加和总是比栈顶的聪明值累加和大,如果勤奋值也不小于栈顶的,那栈顶组合肯定不会是最大总和,可以排除。遍历完之后,再将list替换为单调栈。
  • 当所有可能最大的组合找出来之后,再过滤掉单个累加和小于等于0的情况,在list中找最大总和即可。

动图展示:
图片说明

2.代码实现

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param number int整型 参加校招的同学人数
     * @param si int整型一维数组 这个同学的聪明值
     * @param fi int整型一维数组 这个同学的勤奋值
     * @return int整型
     */
    public int smartSum (int number, int[] si, int[] fi) {
        //创建一个list用于存储聪明值和勤奋值累加和
        List<int[]> list=new ArrayList<>();
        list.add(new int[]{0,0});

        for(int i = 0;i < number;i ++){
            //相当于对list进行层序遍历,让每一个累加和与当前同学的聪明值、勤奋值组合,扩展出更多的可能
            int n=list.size();
            for(int j=0;j<n;j++){
                list.add(new int[]{list.get(j)[0]+si[i],list.get(j)[1]+fi[i]});
            }
            //按聪明值累加和进行排序,如果相等,按勤奋值累加和排序
            Collections.sort(list,(o1,o2)->o1[0]==o2[0]?o1[1]-o2[1]:o1[0]-o2[0]);

            //维护一个单调栈,使得聪明值累加和递增的情况下,勤奋值累加和递减
            List<int[]> stack=new ArrayList<>();
            for(int[] sum:list){
                //如果聪明值累加和小于当前,勤奋值累加和也不大于当前,直接将栈顶元素移除
                while(!stack.isEmpty()&&stack.get(stack.size()-1)[1]<=sum[1]){
                    stack.remove(stack.size()-1);
                }
                stack.add(sum);
            }

            //将list替换为清除无效组合后的集合
            list=stack;
        }
        int res=0;
        //找到最大的总和
        for(int[] sum:list){
            if(sum[0]>0&&sum[1]>0){
                res=Math.max(res,sum[0]+sum[1]);
            }
        }
        return res;
    }
}

3.复杂度分析

  • 时间复杂度:组合数会呈指数式增长,形如图片说明 的样子扩张,最坏情况下,没有淘汰任何组合,总组合数为图片说明(n是应聘的同学数),所以时间复杂度是图片说明
  • 空间复杂度:最坏情况下,需要额外大小为图片说明的list,所以空间复杂度为图片说明

方法二(动态规划)

1.解题思路

问题可以转化为一个01背包问题,以聪明值累加和为容量,以勤奋值累加和为价值,在不超过最大容量的前提下,求最大的价值。首先要确定背包的容量,先求聪明值累加和的最小值和最大值,由于dp数组以0开始,所以容量偏移图片说明 个单位。

  • 状态定义:图片说明 表示聪明值累加和为图片说明 时,勤奋值累加和的最大值。
  • 状态初始化:将所有的状态初始化为int型数据最小值,并将聪明值累加和为0时的勤奋值累加和赋为0。
  • 状态转移:由于是01背包问题,需要从后往前推算出状态,如果从前往后,会将之前的状态多算。然后,每多一个图片说明 ,就多一个图片说明 ,即图片说明图片说明 时,图片说明 相较于j要大,是从大的聪明值累加和转化到小的聪明值累加和,根据01背包,应该倒过来从前往后推,左边界是dp数组左边界0,右边界为了图片说明 不超过capacity,所以是图片说明 ,状态转移方程也是图片说明

2.代码实现

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param number int整型 参加校招的同学人数
     * @param si int整型一维数组 这个同学的聪明值
     * @param fi int整型一维数组 这个同学的勤奋值
     * @return int整型
     */
    public int smartSum (int number, int[] si, int[] fi) {
        //计算聪明值累加和的最小值和最大值
        int min=0,max=0;
        for(int i=0;i<number;i++){
            if(si[i]<0){
                min+=si[i];
            }
            else{
                max+=si[i];
            }
        }

        int capacity=max-min;
        //dp[i]表示聪明值累加和为i+min时,勤奋值累加和的最大值
        int[] dp=new int[capacity+1];
        Arrays.fill(dp,Integer.MIN_VALUE);
        //初始化聪明值累加和为0的情况
        dp[-min]=0;
        for(int i=0;i<number;i++){
            if(si[i]>0){
                //01背包问题,聪明值累加和一定,选最大勤奋值累加和
                for(int j=capacity;j>=si[i];j--){
                    if(dp[j-si[i]]!=Integer.MIN_VALUE){
                        dp[j]=Math.max(dp[j],dp[j-si[i]]+fi[i]);
                    }
                }
            }
            else{
                //如果是负数,需要从之前确定的聪明值累加和倒推
                for(int j=0;j<=capacity+si[i];j++){
                    if(dp[j-si[i]]!=Integer.MIN_VALUE){
                        dp[j]=Math.max(dp[j],dp[j-si[i]]+fi[i]);
                    }
                }
            }
        }

        int res=0;
        //如果聪明值累加和大于0,同时勤奋值累加和大于0,取两者之和最大值
        for(int i=-min+1;i<=capacity;i++){
            if(dp[i]>0){
                //i+min表示聪明值累加和
                res=Math.max(res,dp[i]+i+min);
            }
        }
        return res;

    }
}

3.复杂度分析

  • 时间复杂度:第一层循环n次(n是应聘的同学数),第二层循环最多有capacity+1次(capacity是聪明值累加和最大值与最小值之差),所以时间复杂度是图片说明
  • 空间复杂度:需要额外大小为capacity+1的dp数组,所以空间复杂度为图片说明