計算n x m的棋盤格子, 沿着各自邊緣線從左上角走到右下角,總共有多少種走法. 要求不能走回頭路,即:只能往右和往下走,不能往左和往上走。

語言C++ 

題目 : [編程題]201301 JAVA 題目2-3級

題目描述 : 

請編寫一個函數(允許增加子函數),計算n x m的棋盤格子(n爲橫向的格子數,m爲豎向的格子數)沿着各自邊緣線從左上角走到右下角,總共有多少種走法,要求不能走回頭路,即:只能往右和往下走,不能往左和往上走。

 

輸入描述:

輸入兩個正整數

輸出描述:

返回結果

示例:

        輸入

2
2

        輸出

6

OJ鏈接:https://www.nowcoder.com/questionTerminal/e2a22f0305eb4f2f9846e7d644dba09b
來源:牛客網

先吐槽一下, 這題目名字是個什麼鬼.. 還有就是, 需要循環輸入測試用例

已經好幾次做題時怎麼調試都死活通不過, 就是死在這個循環輸入測試用例上面, 可氣的是, 題目里根本不說啊, 有的題不用寫循環就能通過, 有的必須寫, 真是頭都大了ヽ(#`Д´)ノ┌┛〃

分析 :

兩種思路, 動態規劃或者遞歸, 要注意的是, 路徑走的是格子邊緣西安, 並不是格子

1. 動態規劃

這道題的動態規劃解法較爲簡單, 利用的是子問題之間不獨立,舉個栗子, 在本題中, 即 3 x 4棋盤的走法數與 2 x 4 有

關. 本題可以根據前兩個階段有幾種走法來導出本階段有幾種走法. 幹想是不好想的, 既然題中有棋盤, 那我們就找一個, 由少

到多的找出規律, 寫出狀態轉移方程

通過棋盤由小到多, 我們隱約發現, 從左上角到右下角的走法與左邊的格子, 和上邊的格子有關, 如果將格子看成一個二維數組的話, 就a[ i ][ j ] = a[ i-1][ j ] + a[ i ][ j-1]的關係,  爲了確認是不是, 我們多畫幾個看一看

                   

我們已經能確認狀態轉移爲a[ i ][ j ] = a[ i-1][ j ] + a[ i ][ j-1], 但起始第一行和第一列卻只有前一個狀態, 可以發現, 第一行或

第一列往後都是加一, 我們爲了更好的滿足狀態轉移方程, 可以給現有的棋盤加上起始行和起始列, 如下

可以看到這樣一張表, 我們最該想到二維數組了, 現在就簡化成了利用初始狀態和狀態方程填表的問題, 表的右下角的元素值

就是我們要的結果代碼實現如下:

#include<iostream>
#include<vector>
using namespace std;
class Solution {
public:
    long long fun(int n, int m) {
        vector<vector<long long> > v;
        v.resize(n + 1, vector<long long>(m + 1));
        for (int i = 1; i < m + 1; ++i) {
            v[0][i] = 1;
        }
        for (int i = 1; i < n + 1; ++i) {
            v[i][0] = 1;
        }
        for (int i = 1; i < n + 1; ++i) {
            for (int j = 1; j < m + 1; ++j) {
                v[i][j] = v[i - 1][j] + v[i][j - 1];//狀態轉移方程
            }
        }
        return v[n][m];
    }
};
int main() {
    int n, m;
    Solution f;
    while (cin >> n >> m) {
        cout << f.fun(n, m) << endl;
    }
    system("pause");
    return 0;
}

這種解法還可以簡化一下, 用一維數組來做, 思路也是一樣的不過每次填的數字會覆蓋掉原來的值, 如下 :

#include<iostream>
#include<string>
#include<vector>
using namespace std;
class Solution {
public:
	long long fun(int n, int m) {
		vector<long long> v;
		v.resize(m + 1, 1);
		for (int i = 1; i < n + 1; ++i) {
			for (int j = 1; j < m + 1; ++j) {
				v[j] += v[j - 1];
			}
		}
		return v[m];
	}
};
int main() {
	int n, m;
	Solution f;
	while (cin >> n >> m) {
		cout << f.fun(n, m) << endl;
	}
	system("pause");
	return 0;
}

2. 遞歸

遞歸也一樣, 需要先知道其狀態轉移方程,  也就是f( i, j ) = f( i - 1, j ) + f( i, j - 1 ), 同樣, 和遞歸一樣也需要注意邊界問題, 當棋

盤的行或者列(即這裏的i, j)有一個爲0時, 即左上角和右下角重合時, 只有一種走法, 這就是遞歸結束條件


PS: 遞推關係必須是從次小的問題開始到較大的問題之間的轉化,從這個角度來說,動態規劃往往可以用遞歸程序來實

現,不過因爲遞推可以充分利用前面保存的子問題的解來減少重複計算,所以對於大規模問題來說,有遞歸不可比擬的優

勢,這也是動態規劃算法的核心之處。

比如輸入20, 20, 動規可以秒出, 遞歸嘛, 算了好長時間都沒算出來.....

來祭出代碼

#include<iostream>
#include<string>
#include<vector>
using namespace std;
class Solution {
public:
	long long fun(int n, int m) {
		if (n == 0 || m == 0) {
			return 1;
		}
		return fun(n - 1, m) + fun(n, m - 1);
	}
};
int main() {
	int n, m;
	Solution f;
	while (cin >> n >> m) {
		cout << f.fun(n, m) << endl;
	}
	system("pause");
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章