牛妹的春游

描述
给出两个正整数x,y,另给出若干个数对,请挑选若干数对使得挑出的数对的和不小于x,的和不小于y,计算挑出数对的的和的最小值

注:
每个数对只能挑选一次,x和y均小于2000

方法一

思路分析

本题可以抽象为动态规划中的经典问题--背包问题,设置x,y分别表示需要物品A的数量,物品B的数量,而两个物品是捆绑销售的,数对分别表示物品A的数量、物品B的数量表示购买个物品A以及个物品B情况下的钱数。要使得购买的物品A的数量不小于x,购买物品B的数量不小于y,所需要的钱最少。由于存在两种物品,按照0-1背包的思想,需要设置三维数组,与0-1背包问题不同的是本题并没有设置最大的容量,而是需要的容量,因此对于转移方程如果容量减到负数了,就从0转移,这样即使拿了更大的容量也不会数组越界,转移也是正确的。转移方程如下:


  • 上式k表示第k个捆绑物品,表示对于物品A容量减到负数了,就从0转移,即使拿了更大的容量也不会数组越界,转移也是正确的;
  • 同样的表示对于物品B容量减到负数了,就从0转移,即使拿了更大的容量也不会数组越界,转移也是正确的;

核心代码

class Solution {
public:
    /**
     * 
     * @param breadNum int整型 
     * @param beverageNum int整型 
     * @param packageSum int整型vector<vector<>> 每个一维数组中有三个数,依次表示这个包装里面的面包数量、饮料数量、花费
     * @return int整型
     */
    int minCost(int breadNum, int beverageNum, vector<vector<int> >& packageSum) {
        // write code here
        int n=packageSum.size();
        vector< vector < vector<int> > > dp(n+1,vector< vector<int> >(breadNum+1,vector<int>(beverageNum+1,2e9 + 7)));
        dp[0][0][0]=0;//初始化取第0个包装,0个A,0个B的最小花费为0
        for(int k=1;k<=n;k++)
        {
            int x=packageSum[k-1][0];
            int y=packageSum[k-1][1];
            int z=packageSum[k-1][2];
            for(int i=0;i<=breadNum;i++)
            {
                
                for(int j=0;j<=beverageNum;j++)
                {
                      dp[k][i][j] = min(dp[k-1][i][j], dp[k-1][max(i-x,0)][max(j-y,0)]+z);
                      //表示可选物品为1-k,物品A容量为i,物品B容量为j的最优值(钱最少)
                }
                  
            }
        }
        return dp[n][breadNum][beverageNum];
    }
};
复杂度分析
  • 时间复杂度:存在三层循环,总的时间复杂度为$O(n*breadNum*beverageNum)$,其中$n$表示所给的数对的个数
  • 空间复杂度:需要借助于一个二维数组存储最小费用,需要额外的空间为$O(n*breadNum*beverageNum)$

方法二

思路分析

    针对第一种方法,可以利用滚动数组的思想,减小数组的维数,从而降低空间复杂度,转移方程如下:

  • 上式k表示第k个捆绑物品,表示对于物品A容量减到负数了,就从0转移,即使拿了更大的容量也不会数组越界,转移也是正确的;
  • 同样的表示对于物品B容量减到负数了,就从0转移,即使拿了更大的容量也不会数组越界,转移也是正确的;

核心代码

class Solution {
public:
    /**
     * 
     * @param breadNum int整型 
     * @param beverageNum int整型 
     * @param packageSum int整型vector<vector<>> 每个一维数组中有三个数,依次表示这个包装里面的面包数量、饮料数量、花费
     * @return int整型
     */
    int minCost(int breadNum, int beverageNum, vector<vector<int> >& packageSum) {
        // write code here
        vector<vector<int>> dp(breadNum+1,vector<int>(beverageNum+1,2e9 + 7));//定义二维数组dp[i][j]表示i个A,j个B的最小花费
        dp[0][0]=0;//初始化0个A,0个B的最小花费为0
        int n=packageSum.size();
        for(int k=0;k<n;k++)
        {
            int x=packageSum[k][0];
            int y=packageSum[k][1];
            int z=packageSum[k][2];
            for(int i=breadNum;i>=0;i--)
            {
                for(int j=beverageNum;j>=0;j--)
                    dp[i][j] = min(dp[i][j], dp[max(i-x,0)][max(j-y,0)]+z);
                //当前捆绑套餐内A或者B大于需要的数量,那么当前剩余需求为0
            }
        }
        return dp[breadNum][beverageNum];
    }
};
复杂度分析
  • 时间复杂度:存在三层循环,总的时间复杂度为$O(n*breadNum*beverageNum)$,其中$n$表示所给的数对的个数
  • 空间复杂度:需要借助于一个二维数组存储最小费用,需要额外的空间为$O(breadNum*beverageNum)$