• 一道笔试题:捞鱼问题


    题目:20个桶,每个桶中有10条鱼,用网从每个桶中抓鱼,每次可以抓住的条数随机,每个桶只能抓一次,问一共抓到180条的排列有多少种 (也可求概率)。

    来自与:http://www.ahathinking.com/archives/112.html,跟着这位前辈学的。


    分析一下

         这道题其实可以这样理解,假设我已经抓到了180条鱼,而这180条鱼来自20个桶中,反过来就是我们将这180条鱼放到20个桶中且要保证每个桶的鱼在(0-10)之间,有多少种方法。假设我们在前i个桶中抓取了k(0<=k<=10*i)条鱼,那么抓取180条鱼的排列种数就等于在剩下的(20-i)个桶中抓取(180-k)条鱼的方法加上前i个桶中抓取k条鱼的方法。


    再想一下

         由于总共有200条鱼,我们抓走了180条鱼,那么还剩下多少条呢?显然是20条。再用上面的思维,我们就可以这样想,将20条鱼放回这20个桶中满足每个桶中的鱼(0-10),有多少种放法。这里当然是因此了一个排列问题而不是组合问题,因为我第一个桶放1条鱼、第2个桶不放与第一个桶不放、第二个桶放1条鱼,是两种的不同的放法。

         这样分析下来,感觉就是一个跳台阶问题:一个梯子有20步,每次可以跳0-10步,20步跳完多少中跳法(可选择不跳)。有关台阶问题,请看本博客:http://blog.csdn.net/lsjseu/article/details/11583513


    编程思想

        思想其实跟完全背包问题类似,我们首先考虑第20个桶放多少鱼,那么我们就可以减去最后一个桶放的鱼数,再去考虑前面19个桶怎么放剩下的鱼。这样形成了一个递归,我们就可以很快写出如下代码。

    //bucket,表示当前考虑到第几个桶,当然首先考虑第20个桶
    //fishN,表示当前有多少鱼
    //返回值表示有多少中方法
    int Func(int bucketN,int fishN)
    {
    	if(bucketN<0 || fishN<0)return 0;//错误处理
    
    	if(bucketN == 1){
    		if(fishN>=0 && fishN<=10)//第1个桶里面放0-10的鱼,方法只有一种
    			return 1;
    		else{
    			return 0;  //其他用0种方法
    		}
    	}
    
    	int sum = 0;
    
    	for(int i=0; i<=10; ++i){
    		sum += Func(bucketN-1,fishN-i);//考虑前面bucket-1的组合,同时减掉当前放的鱼
    	}
    	return sum;
    }

    递归优化

         上面的代码,其实有很多重复计算。下面我们简单的分析一下:

    F(20,20) = F(19,19)+ F(19,18).......; //考虑第20个桶放一条鱼和两条鱼的情况

    F(19,19) = F(18,17)+.....;//第19个桶放2条鱼  ①

    F(19,18) = F(18,17)+.....;//第19个桶放1条鱼  ②

         我们发现①和②都调用了F(18,17),但是它们确实各算各的,这样就存在着很多类似的重复计算。该递归树,基本全是重复的点,这样时间复杂度被拖得很高。那么,我们只要设计一个数组来把计算好的值保存下来,这样避免了很多重复的计算。代码如下:

    //bucket,表示当前考虑到第几个桶,当然首先考虑第20个桶
    //fishN,表示当前有多少鱼
    //返回值表示有多少中方法
    int dp[21][200] = {0};
    int FuncOptimize(int bucketN,int fishN)
    {
    	if(bucketN<0 || fishN<0)return 0;
    
    	if(bucketN == 1){
    		if(fishN>=0 && fishN<=10)
    			return 1;
    		else{
    			return 0;
    		}
    	}
    	if(dp[bucketN][fishN] == 0){ //这个子过程没有被计算我们采取调用递归计算
    		for(int i=0; i<=10; ++i){
    			dp[bucketN][fishN] += FuncOptimize(bucketN-1,fishN-i);
    		}
    	}
    	
    	return dp[bucketN][fishN];
    }


    下面我们用我们熟悉的斐波拉契序列做一个实验,我们同样的优化方式来做,看下程序跑得快了多少?

    #include<iostream>
    #include <windows.h>
    using namespace std;
    long long fibonacci[100];
    long long Fibonacci(int n)
    {
    	if(n == 0)return 1;
    	if(n == 1)return 1;
    
    	return Fibonacci(n-1) + Fibonacci(n-2);
    }
    
    long long FibonacciOptimize(int n)
    {
    	if(n == 0)return 1;
    	if(n == 1)return 1;
    
    	if(fibonacci[n] == 0){
    		fibonacci[n] = FibonacciOptimize(n-1) + FibonacciOptimize(n-2);
    	}
    
    	return fibonacci[n];
    }
    
    
    int main()
    {
    	DWORD seconds = GetTickCount();
    	cout<<Fibonacci(40)<<endl;
    	cout<<"优化之前:"<<(GetTickCount() - seconds)<<"ms"<<endl;
        seconds = GetTickCount();
    	cout<<FibonacciOptimize(40)<<endl;
    	cout<<"优化之后:"<<(GetTickCount() - seconds)<<"ms"<<endl;
    	system("pause");
    	return 0;
    }



    这个结果很是满意啊。


    动态规划实现

         们知道这种自顶向的递归,都可以转换为动态规划自底向上的思想。就是我们递归的值是有下面的值一步步垒起来的,那么我只需要一开始就把下面的值算好,保持在那里,然后再算上面的时候,直接拿过来用,这就很快了。据我了解,背包问题的编程思路跟这道题是一摸一样,只不过背包走到第i种物品的时候,第i种物品的重量已经固定,而我们这道题是第i个桶取值是一个范围,那我们只要把这里范围都扫一遍就好了

    #include<iostream>
    #include <windows.h>
    using namespace std;
    
    int dp[21][200] = {0};
    int FuncDp(int bucketN,int fishN)
    {
    	int i,j,k;
    
    	for(i=0; i<=10; ++i)
    		dp[1][i] = 1;
    
    	for(i=2; i<=bucketN; ++i){
    		for(j=0; j<=fishN; ++j){
    			for(k=0; k<=10&&j-k>=0; ++k){//可以取0-10
    				dp[i][j] += dp[i-1][j-k];
    			}
    		}
    	}
    
    	return dp[bucketN][fishN];
    }
    
    int main()
    {
    	cout<<FuncDp(20,180)<<endl;
        memset(dp,0,sizeof(dp));
    	cout<<FuncDp(20,20)<<endl;
    	system("pause");
    	return 0;
    }



        其实,代码还可以更简练,仔细想想,就是初始化状态的方法;其实初始化合法状态完全可以这样想,问题始终都是分解成子问题的,根据递归的实现方法,只有分解到0个桶装0条鱼才是合法的,那么我们就初始化这一个状态为合法即可,然后从第一个桶开始向上计算,代码如下:

     

    #include <iostream>
    
    using namespace std;
    
    int dp[21][200];
    int i, j, k;
     
    void main()
    {
        int bucketN, fishN;
        scanf("%d %d", &bucketN, &fishN);
     
        dp[0][0] = 1;  /* 初始化合法状态 */
     
        for(int i = 1; i <= bucketN; ++i)  /* 从第一个桶开始 */
        {
            for(int j = 0; j <= fishN; ++j)
            {
                for(int k = 0; k <= 10 && j-k >= 0; ++k)
                {
                    dp[i][j] += dp[i-1][j-k];
                }
            }
        }
        printf("%d
    ",dp[bucketN][fishN]);
    }


    想不通我的空间优化怎么不成功,类似背包问题的优化,555555555555555555。有大神可以帮忙解释一下啊?

    #include<iostream>
    #include <windows.h>
    using namespace std;
    
    int dp[200] = {0};
    int FuncDp(int bucketN,int fishN)
    {
    	int i,j,k;
    
    	for(i=0; i<=10; ++i)
    		dp[i] = 1;
    
    	for(i=2; i<=bucketN; ++i){
    		for(j=fishN; j>=0; --j){//反着遍历,防止覆盖
    			for(k=0; k<=10&&j-k>=0; ++k){//可以取0-10
    				dp[j] += dp[j-k];
    			}
    		}
    	}
    
    	return dp[fishN];
    }
    
    int main()
    {
    	cout<<FuncDp(20,180)<<endl;
    	memset(dp,0,sizeof(dp));
    	cout<<FuncDp(20,20)<<endl;
    	system("pause");
    	return 0;
    }


  • 相关阅读:
    MATLAB数据处理快速学习教程
    dev -c++ 快捷键
    SQL 通配符
    如何利用SQL语句求日期的时间差值,并汇总网上的一些信息
    matlab使用常犯的错误
    matlab中将矩阵按照行打乱顺序的一个例子
    我所认识的PCA算法的princomp函数与经历 (基于matlab)
    java 读取本地的json文件
    Oracle数据库——SQL基本查询
    Oracle数据库——表的创建与管理
  • 原文地址:https://www.cnblogs.com/pangblog/p/3322842.html
Copyright © 2020-2023  润新知