1. 程式人生 > >遞迴和動態規劃(一)

遞迴和動態規劃(一)

換錢的方法數

題目:給定陣列arr, arr中所有的值都為正數且不重複。每個值代表一種面值貨幣,每種面值的貨幣可以使用任意張,再給定一個整數aim代表要找的錢數,求換錢有多少種方法。

解題思路:

解法一:暴力遞迴

如果arr={5,10,25,1}, aim=1000,過程如下:

1. 用0張5元的貨幣,讓[10,  25, 1]組成剩下的1000,最終方法數記為res1

2..用1張5元的貨幣,讓[10,  25, 1]組成剩下的995,最終方法數記為res2

3. 用3張5元的貨幣,讓[10,  25, 1]組成剩下的990,最終方法數記為res3

...

201. 用200張5元的貨幣,讓[10, 25, 1]組成剩下的0,最終方法數記為201

那麼res1 + res2 + ...+res201 的值就是總的方法數。

具體程式碼實現如下:

int coins1(vector<int> arr, int aim)
{
	if(arr.size() == 0 || aim < 0)
		return 0;
	return process1(arr, 0, aim);
}
int process1(vector<int> arr, int index, int aim)
{
	int res = 0;
	if(index == arr.size())
		res = aim == 0 ? 1 : 0;
	else
		for(int i=0; arr[index]*i <= aim; i++)
			res += process1(arr, index+1, aim - i*arr[index]);
	return res;
}
解法二:記憶化搜尋

針對暴力遞迴存在大量重複搜尋的情況,可以事先準備一個m,每計算完一個遞迴過程,都將結果記錄到m中,當下次進行同樣的遞迴過程之前,先m中查詢這個遞迴過程是否已經計算過,如果已經計算過,就把值拿出來直接用,如果沒有計算過,需要再進入遞迴過程。

m[i][j]表示遞迴過程p(i, j)的返回值。

m[i][j] = 0表示遞迴過程p(i, j)從來沒有計算過。

m[i][j] = -1 表示遞迴過程p(i, j)計算過,但返
回值是0.如果m[i][j]的值既不等於0,也不等於-1,記為a,則表示遞迴過程p(i, j)返回值為a

具體程式碼如下:

int coins2(vector<int> arr, int aim)
{
	if(arr.size() == 0 || aim < 0)
		return 0;
	vector<vector<int> > m(arr.size()+1, vector<int>(aim+1, 0));
	return process2(arr, 0, aim, m);
}
int process2(vector<int> arr, int index, int aim, vector<vector<int> >& m)
{
	int res = 0;
	if(index == arr.size())
		res = aim == 0 ? 1 : 0;
	else
	{
		int mapValue = 0;
		for(int i=0; i * arr[index] <= aim; i++)
		{
			mapValue = m[index+1][aim-i*arr[index]];
			if(mapValue != 0)
				res += mapValue == -1 ? 0 : mapValue;
			else
				res += process2(arr, index+1, aim-i*arr[index], m);
		}
	}
	m[index][aim] = res == 0 ? -1 : res;
	return res;
}

解法三:動態規劃

生成行數為N列數為aim+1的矩陣dp,dp[i][j]表示在使用arr[0...i]貨幣的情況下,組成錢數j的方法數。

  1. 對於dp第一列的值dp[...][0], 表示組成錢數0的方法數,只有一種,就是不使用任何貨幣
  2. 對於dp第一行的值dp[0][...],表示只使用arr[0]這一種貨幣的情況下,組成錢的方法數
  3. 其他位置(i, j)的dp值是以下幾個值的累加。
  • 完全不用arr[i]貨幣,只使用arr[0...i-1]貨幣時,方法數為dp[i-1][j]
  • 用1張arr[i]貨幣,剩下的錢用arr[0...i-1]貨幣組成時,方法數為dp[i-1][j-arr[i]]
  • 用2張arr[i]貨幣,剩下的錢用arr[0...i-1]貨幣組成時,方法數為dp[i-1][j-2*arr[i]].
  • 用k張arr[i]貨幣,剩下的用arr[0...i-1]貨幣組成時,方法數為dp[i-1][j-2*arr[i]].
  • .............
        4 .最終dp[N-1][aim]的值就是最終結果

具體程式碼如下:

int coins3(vector<int> arr, int aim)
{
	if(arr.size() == 0 || aim < 0)
		return 0;
	vector<vector<int> > dp(arr.size(), vector<int>(aim+1, 0));
	for(int i=0; i<arr.size(); i++)
		dp[i][0] = 1;
	for(int j=1; j*arr[0]<=aim; j++)
		dp[0][arr[0] * j] = 1;
	int num = 0;
	for(int i=1; i<arr.size(); i++)
	{
		for(int j=1; j<=aim; j++)
		{
			num = 0;
			for(int k=0; j - k*arr[i] >= 0; k++)
				num += dp[i-1][j - k*arr[i]];
			dp[i][j] = num;
		}
	}
	return dp[arr.size()-1][aim];
}

解法四:動態規劃(改進解法三)

解法三,第1種情況的方法數就是dp[i-1][j], 而第2種情況一直到第k種情況的方法累加值其實就是dp[i][j-arr[i]]

則dp[i][j] = dp[i-1][j] + dp[i][j-arr[i]]

具體程式碼如下:

int coins4(vector<int> arr, int aim)
{
	if(arr.size() == 0 || aim < 0)
		return 0;
	vector<vector<int> > dp(arr.size(), vector<int>(aim+1, 0));
	for(int i=0; i<arr.size(); i++)
		dp[i][0] = 1;
	for(int j=1; j*arr[0]<=aim; j++)
		dp[0][j*arr[0]] = 1;
	for(int i=1; i<arr.size(); i++)
		for(int j=1; j<=aim; j++)
		{
			dp[i][j] += dp[i-1][j];
			dp[i][j] += j-arr[i] >= 0 ? dp[i][j-arr[i]] : 0;
		}
	return dp[arr.size()-1][aim];
}

解法五:動態規劃空間壓縮
int coins5(vector<int> arr, int aim)
{
	if(arr.size() == 0 || aim < 0)
		return 0;
	vector<int> dp(aim+1, 0);
	for(int j=1; j*arr[0]<=aim; j++)
		dp[j*arr[0]] = 1;
	for(int i=1; i<arr.size(); i++)
		for(int j=1; j<=aim; j++)
			dp[j] += j - arr[i] >= 0 ? dpj-arr[i]] : 0;
	return dp[aim];
}

注:演算法思路參考程式設計師程式碼面試指南,由本人自己改由C++實現!