語言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;
}