题目2:包粽子, 四个数n, m, c0, d0, 一共n 克面粉, m种馅料
参加了阿里笔试,第一题没看懂,第二题写了半天,使用的递归求解,思路没问题,但是递归显然时间复杂度会很大,因此只通过20%的用例,之后就超出时间限制了,
对于递归穷举的问题我们都可以使用动态规划来大大降低他的时间复杂度,通过构建数组这个空间来优化
由于在考试的时候,看到题比较懵,题目信息有点小大,理解了一段时间,直接按照递归的思路来分析了,因此使用递归来解得
考完试后我苦思冥想,这道题动态规划该怎么写呢?不达目的誓不休的我,今天早上起来我想了一下dp算法,写着写着,发现了这道题不仅仅需要动态规划,还需要贪心算法来解答,可惜考试中没想到,因此我现在也没有示例了,只有考试中的一个示例,跑的结果没问题,我觉得我的思路很正确,就是写的太繁琐了,来记录一下,顺便分享一下,如果我的思路能够帮助小伙伴的话,那就更好了,对于递归求解法这里我把我写的代码粘贴上,思路就不讲解了,毕竟时间复杂度比较大,这里我重点说一下动态规划+贪心算法,我是如何思考的,当然仅供参考!
递归求解的代码贴出来:
import java.util.ArrayList; import java.util.Scanner; public class Test { static int maxValue=-1; public static void main(String[] args) { Scanner s=new Scanner(System.in); String[] ones=s.nextLine().split(" "); if(ones.length!=4)return; int n=Integer.parseInt(ones[0]),m=Integer.parseInt(ones[1]),c0=Integer.parseInt(ones[2]),d0=Integer.parseInt(ones[3]); String[][] others=new String[m][4]; for(int i=0;i<m;i++){ others[i]=s.nextLine().split(" "); } //将输入的转化为数字型列表,方便回溯 ArrayList<ArrayList<Integer>> lists=new ArrayList<>(); ArrayList<Integer> temp; for(int i=0;i<m;i++){ temp=new ArrayList<>(); for(int j=0;j<4;j++){ temp.add(Integer.parseInt(others[i][j])); } lists.add(temp); } //(这里其实可以全部放到下面的) //回溯递归,每一个我做0个到做大最大可做的数量,每一个我都尝试,剩余的给其他的,达到深度搜索 getMax(lists,n,0,c0,d0); System.out.println(maxValue); } /** * * @param lists * @param restMian 剩余面粉数量 * @param money 当前积累价值 * @param c0 消耗c0面粉 * @param d0 产生d0价值 */ private static void getMax(ArrayList<ArrayList<Integer>> lists,int restMian,int money,int c0,int d0){ //判断当前lists里面还有没有,如果没有了,则我计算剩余面粉的价值加上后和现有的最大值比较 if(lists.isEmpty()){ int curMax= money+(restMian/c0)*d0; if(maxValue<curMax){ maxValue=curMax;} } //如果还有,则我们还得按照方案继续分配 ArrayList<Integer> temp; int tempMoney; for (int i = 0; i < lists.size(); i++) { temp=new ArrayList<>(); //本种类做0次到可做的最大次 tempMoney=money; for(int j=0;j<=lists.get(i).get(0)/lists.get(i).get(1);j++){ //需要判断当前来做面粉够不够,只有够了,才能操作 tempMoney=money; int te=restMian-lists.get(i).get(2)*j; if(te>0){ tempMoney+=lists.get(i).get(3)*j; temp=lists.get(i); lists.remove(lists.get(i)); getMax(lists,te,tempMoney,c0,d0); lists.add(0,temp); } } } }
动态规划思路:
1.构建dp[i][j]代表第i种产品,有j克面粉产生的价值(对于其中细节要考虑的是:当前最大生产数量:Math.min(面粉可生产的数量,馅料可生产的数量))
2.对于dp含义的解释:求解出dp中,我尝试使用dp代表最大价值来表示,通过debug发现不可以,因为:加入A生产4产生100价值,B生产4产生40价值,如果定义为最大价值的话,那么B生产4放的就应该是100,那么就不对了,我8克面粉4,4分的时候实际上是100+40才是对的,如果100+100就是错的了,因此 dp不能代表最大价值,只能代表当前价值
3.那么我们如何求最大价值呢,这时候贪心就起作用了,我想着是构建所有种类的排序,从上到下依次找当前馅料可生产的最大个数产生的价值就okl了
4.那么我们需要求出所有的组合 如://a b c-> abc acb bac bca cab cba,一个个组合求最大值,这个每个组合的局部最优求最大也就是全局最优了。
import java.util.ArrayList; import java.util.Scanner; //10 2 1 1 //6 3 2 50 //8 2 1 10 public class DynamicTest { public static void main(String[] args) { Scanner s=new Scanner(System.in); String[] ones=s.nextLine().split(" "); if(ones.length!=4)return; int n=Integer.parseInt(ones[0]),m=Integer.parseInt(ones[1]),c0=Integer.parseInt(ones[2]),d0=Integer.parseInt(ones[3]); String[][] others=new String[m][4]; for(int i=0;i<m;i++){ others[i]=s.nextLine().split(" "); } //将输入的转化为整形数组 int[][] categorys=new int[m][4]; for(int i=0;i<m;i++){ for(int j=0;j<4;j++){ categorys[i][j]=Integer.parseInt(others[i][j]); } } //dp[i][j]表示i中产品消耗j克面粉可以产生的价值 int[][] dp=new int[m+1][n+1]; int[][] res=new int[m+1][n+1]; for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ int nowMax=0; int flour2Count=j/categorys[i-1][2]; int material2Count=categorys[i-1][0]/categorys[i-1][1]; int produceCount=flour2Count<material2Count?flour2Count:material2Count;//最大可生产的数量(就是能完成最小的呗) if(flour2Count<=material2Count){//说明我们还没有计算过 int value=produceCount*categorys[i-1][3]; int restMian=j-produceCount*categorys[i-1][2];//剩余的面粉 value+= restMian/c0*d0; nowMax=value; } else{//说明我们可以到前面取值,即dp[0][material2Count]+ (flour2Count-material2Count)面粉产生的价值 int value=dp[i][produceCount*categorys[i-1][2]]; int restMian=j-produceCount*categorys[i-1][2]; value+= restMian/c0*d0; nowMax=value; } dp[i][j]=nowMax; } } //到这里dp记录每个种类消耗面粉产生的价值 //接下来,求出m种组合,从上往下使用贪心,求解最大值 ArrayList<ArrayList<Integer>> list=new ArrayList<>(); ArrayList<Integer> tempArr; for(int i=0;i<m;i++){ tempArr=new ArrayList<>(); tempArr.add(i); list.add(tempArr); } ArrayList<ArrayList<Integer>> resList=getCombine(list); //按照这个顺序,从上往下找当前最大生产有馅数 int result=0; int resValue=0; int resRestMian=n; for (int i = 0; i < resList.size(); i++) { resValue=0; resRestMian=n; for (int j = 0; j < resList.get(i).size(); j++) { if(j==resList.get(i).size()-1){//如果能到最后一个,那么面全部给他 resValue+=dp[resList.get(i).get(j)+1][resRestMian]; } else { int index=resList.get(i).get(j); int maxXian=categorys[index][0]/categorys[index][1];//馅最大可生产数 int maxMian=resRestMian/categorys[index][2];//面最大可生产数 if(maxXian<=maxMian){ resValue+=dp[resList.get(i).get(j)+1][maxXian*categorys[index][2]]; resRestMian-=maxXian*categorys[index][2]; } else { resValue+=dp[resList.get(i).get(j)+1][maxMian]; break; } } } result=result>resValue?result:resValue; } System.out.println(result); } //求出list的所有组合 //a b c-> abc acb bac bca cab cba private static ArrayList<ArrayList<Integer>> getCombine(ArrayList<ArrayList<Integer>> list){ ArrayList<ArrayList<Integer>> res=new ArrayList<>(); if(list.isEmpty()){ return res; } for (int i = 0; i < list.size(); i++) { ArrayList<Integer> first=list.get(i); ArrayList<ArrayList<Integer>> newList=CopyNoIndex(list,i); ArrayList<ArrayList<Integer>> temp=getCombine(newList); if(!temp.isEmpty()){ for (int j = 0; j < temp.size(); j++) { temp.get(j).addAll(0,first); } res.addAll(temp); } else { temp.add(first); res.addAll(temp); } } return res; } private static ArrayList<ArrayList<Integer>> CopyNoIndex(ArrayList<ArrayList<Integer>> list,int index){ ArrayList<ArrayList<Integer>> res=new ArrayList<>(); ArrayList<Integer> temp; for (int i = 0; i < list.size(); i++) { if(i==index)continue; temp=new ArrayList<>(); for (int j = 0; j < list.get(i).size(); j++) { temp.add(list.get(i).get(j)); } res.add(temp); } return res; } }
以上算法全部都是个人写出,没有参照网上任何一个人,写的可能稍微复杂了一些,需要优化的地方肯定也不少。
每天努力一点点,为了心中的那个梦,继续加油!
原文:https://www.cnblogs.com/ningxinjie/p/13334562.html