您的位置:首页 > 职场人生

面试题整理-台阶问题

2012-10-11 21:11 381 查看
原题

测试链接

http://ac.jobdu.com/problem.php?cid=1039&pid=4

首先我们考虑上面链接中提出的问题:由于一次只能走两步,那么在到达n步的上一步。其只能是要么在(n-1)个台阶上。要么是在(n-2)个台阶上。

所以我们可以得到fn = fn-1 + fn-2;

f1 = 1; f2 = 2;

这里可以看出,同样也可以利用斐波那契数的原理进行处理。(也有人直接把这个序列转换成为斐波那契数来处理,亦可以,但是对于后续的思路不是一个整体。在这里就不考虑。)

这里需要注意与HD OJ方法的不同之处。http://acm.hdu.edu.cn/showproblem.php?pid=2041

在HD OJ上面,由于起始步是在第一台阶上,走第二个台阶就只需要一步。所以是F1 = 0, F2 = 1; F3 = 2;

最简单的处理方式

当n<=50的时候,是可以利用64位int长整型。long long来进行处理。

#include <stdio.h>
#include <stdlib.h>

long long a[71];
void init(void) {
a[0] = 0;
a[1] = 1; a[2] = 2;
for (int i = 3; i < 71; ++i) {
a[i] = a[i-1] + a[i-2];
}
}

int main(void) {
int n;init();
while (scanf("%d", &n) != EOF) {
printf("%lld\n", a
);
}
return 0;
}
//注意打表及长整型。


很简单,就把上面那个题给秒掉了。

高效的处理

如果你还对前面讲到的斐波那契数有印象的话,可以轻易地得到。

[ Fn Fn-1] = [F2 F1] pow(A, n - 2);

A = [1 1

1 0];

值得注意的是,针对于原始的斐波那契数而言,其递推之后是到[F1 F0] 截止,所以其指数是n-1。而在这里是到[F2 F1] 截止。所以指数为n-2。

此外,假设T = pow(A, n-2);

与前面不同的是,Fn = F2*T[0][0] + F1*T[1][0];

从而可以写出如下代码:

#include <stdio.h>
#include <stdlib.h>

typedef struct _node
{
long long a, b;
long long c, d;
} node;

void _multiple(node *x, node *y) {
node temp;
temp.a = x->a*y->a + x->b*y->c;
temp.b = x->a*y->b + x->b*y->d;
temp.c = x->c*y->a + x->d*y->c;
temp.d = x->c*y->b + x->d*y->d;
*x = temp;
}

long long fib(int n)
{
if (0 == n) return 0;
if (1 == n) return 1;
if (2 == n) return 2;

node odd; odd.a = odd.d = 1; odd.c = odd.b = 0; //单位矩阵
node temp; temp.a = temp.b = temp.c = 1; temp.d = 0; // A矩阵

n -= 2;
while (n) {
if (n&1) _multiple(&odd, &temp);
_multiple(&temp, &temp);
n >>= 1;
}

return odd.a * 2 + odd.c;
}

int main()
{
int n;
while (scanf("%d", &n) != EOF) {
printf("%lld\n", fib(n));
}
return 0;
}


大整数的处理

这里,还是可以利用前面提到的大整数的加法模板来进行运算。

#include <iostream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
using namespace std;

string &_del_zeros_before_dot(string &a)
{
if (a.length() <= 0 || a[0] != '0') return a;
int i = 0;
while (i < a.length() && a[i] == '0') ++i;
a = a.substr(i, a.length() - i);
return a;
}

string &_string_add_string(const string &a, const string &b, string &res)
{
int sum_value = 0, add_bit = 0;
const int alen = a.length(), blen = b.length();
res = "0" + (alen > blen ? a : b);
for (int i = alen-1, j = blen-1, k = res.length() - 1;
i >= 0 || j >= 0 || add_bit > 0;
--i, --j, --k){
sum_value = (i>=0 ? a[i]-48: 0) + (j>=0 ? b[j]-48: 0) + add_bit;
add_bit = sum_value / 10;
res[k] = sum_value%10 + '0';
}
if (res[0] == '0') res = res.substr(1, res.length() - 1);
return res;
}

string fib(int n) {
if (0 == n) return "0";
if (1 == n) return "1";
if (2 == n) return "2";
string a_2 = "1", b_1 = "2", ret = "0";
for (int i = 3; i <= n; ++i) {
_string_add_string(a_2, b_1, ret); // fn = fn-2 + fn-1;
a_2 = b_1;
b_1 = ret;
}
return ret;
}

int main(void) {
int n;
while (scanf("%d", &n) != EOF) {
printf("%s\n", fib(n).c_str());
}
return 0;
}


扩展-1

首先题意变成:如果一下子可以走1,2,3步呢。也就是多了一种走法。根据惯性思维

Fn = Fn-1 + Fn-2 + Fn-3



F1 = 1

F2 = 2

F3 = F1 + F2 + 1 = 4

由于有了递推公式。可以轻易地写出如下代码:

#include <stdio.h>

long long step(int n) {
int i = 0;
long long a = 1, b = 2, c = 4, ret;
if (0 == n) return 0;
if (1 == n) return 1;
if (2 == n) return 2;
if (3 == n) return 4;
for (i = 4; i <= n; ++i) {
ret = a + b + c;
a = b;
b = c;
c = ret;
}
return ret;
}

int main(void)
{
int n;
while (scanf("%d", &n) != EOF) {
printf("%lld\n", step(n));
}
return 0;
}


同样也可以写成大数模板形式

#include <iostream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
using namespace std;

string &_del_zeros_before_dot(string &a)
{
if (a.length() <= 0 || a[0] != '0') return a;
int i = 0;
while (i < a.length() && a[i] == '0') ++i;
a = a.substr(i, a.length() - i);
return a;
}

string &_string_add_string(const string &a, const string &b, string &res)
{
int sum_value = 0, add_bit = 0;
const int alen = a.length(), blen = b.length();
res = "0" + (alen > blen ? a : b);
for (int i = alen-1, j = blen-1, k = res.length() - 1;
i >= 0 || j >= 0 || add_bit > 0;
--i, --j, --k){
sum_value = (i>=0 ? a[i]-48: 0) + (j>=0 ? b[j]-48: 0) + add_bit;
add_bit = sum_value / 10;
res[k] = sum_value%10 + '0';
}
if (res[0] == '0') res = res.substr(1, res.length() - 1);
return res;
}

string fib(int n) {
if (0 == n) return "0";
if (1 == n) return "1";
if (2 == n) return "2";
if (3 == n) return "4";
string a = "1", b = "2", c = "4", ret = "0", temp="0";
for (int i = 4; i <= n; ++i) {
_string_add_string(a, b, temp); // temp = fn-2 + fn-3;
_string_add_string(temp, c, ret); // ret = temp + fn-1;
a = b;
b = c;
c = ret;
}
return ret;
}

int main(void) {
int n;
while (scanf("%d", &n) != EOF) {
printf("%s\n", fib(n).c_str());
}
return 0;
}


那么我们考虑,如何写成O(lgN)的效率呢。

同样,我们也可以写出矩阵的表达式。

[Fn Fn-1 Fn-2] = [Fn-1 Fn-2 Fn3] * A;

可以得出A的值为

1 1 0

1 0 1

1 0 0

那么,在这种情况下,如果层次递推,我们可以得到

[Fn Fn-1 Fn-2] = [F3 F2 F1] * pow(A, n-3);

如果设T = pow(A, n-3);

那么Fn = F3*T[0,0] + F2*T[1,0] + F1*T[2,0];

因此,也可以写出如下的代码。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MX 3

typedef struct _node
{
long long a[MX][MX];
} node;

void _multiple(node *x, node *y) {
node temp;
long long (*a)[MX] = x->a;
long long (*b)[MX] = y->a;
long long (*c)[MX] = temp.a;

for (int row = 0; row < MX; ++row) {
for (int col = 0; col < MX; ++col) {
long long s = 0;
for (int i = 0; i < MX; ++i) {
s += a[row][i] * b[i][col];
}
c[row][col] = s;
}
}
*x = temp;
}

long long fib(int n)
{
if (0 == n) return 0;
if (1 == n) return 1;
if (2 == n) return 2;
if (3 == n) return 4;

node odd;
//设置odd为单位矩阵
memset(odd.a, 0, sizeof(odd.a));
for (int i = 0; i < MX; ++i) {
odd.a[i][i] = 1;
}
node temp; // A矩阵
memset(temp.a, 0, sizeof(temp.a));
for (int i = 0; i < MX; ++i) {
temp.a[i][0] = 1;
if (i + 1 < MX) temp.a[i][i+1] = 1;
}

n -= 3;
while (n) {
if (n&1) _multiple(&odd, &temp);
_multiple(&temp, &temp);
n >>= 1;
}
return 4*odd.a[0][0] + 2*odd.a[1][0] + odd.a[2][0];
}

int main()
{
int n;
while (scanf("%d", &n) != EOF) {
printf("%lld\n", fib(n));
}
return 0;
}


扩展-2

那么接下的问题是

假设A上台阶,一次可以跨1层,2层,3层..或m层,问A上n层台阶,有多少种走法?

其中,m和n都是正整数,并且 m <= n, m <= 10, n <= 50

由于m, n并不清楚。

不过对于1<= x <= m的走法,应该是很清楚的。也就是Fx = Fx-1 + Fx-2 + ...... + F1 + 1

而对于大于m的而言,则是Fy = Fy-1 + Fy-2 + Fy-3 + .... + Fy-m;

公式都出来了,就不用去想了。

#include <stdio.h>
#include <iostream>
#include <list>

using namespace std;

long long fib(unsigned int m, int n) {
list<long long> l;
long long a = 0, s = 0, ret;
for (int i = 1; i <= m && i <= n; ++i) {
a = s + 1;
l.push_back(a);
s += a;
}
if (n <= m) return l.back();

for (int i = m + 1; i <= n; ++i) {
s = 0;
for (list<long long>::iterator iter = l.begin();
iter != l.end(); ++iter) {
s += *iter;
}
ret = s;

l.pop_front();
l.push_back(ret);
}
return ret;
}

int main(void) {
int m, n;
while (scanf("%d%d", &m, &n) != EOF){
printf("%lld\n", fib(m, n));
}
return 0;
}


如果觉得上面的方法不好理解。没关系。直接采用打表法。

#include <stdio.h>
int m, n;
long long a[51];
long long fib(int m, int n) {
long long s = 0;
a[0] = 0;
for (int i = 1; i <=m && i <= n; ++i) {
a[i] = s + 1;
s += a[i];
}
if (n <= m) return a
;
for (int i = m + 1; i <= n; ++i) {
s = 0;
for (int j = 1; j <= m; ++j) {
s += a[i-j];
}
a[i] = s;
}
return a
;
}
int main(void)
{
while (scanf("%d%d", &m, &n) != EOF &&0 <= m && m <=n && n <= 50) {
printf("%lld\n", fib(m, n));
}
return 0;
}

这里也顺便给出大整数求解的方案,也就是当n超过50的时候的处理方式。

#include <iostream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <list>
using namespace std;

string &_del_zeros_before_dot(string &a)
{
if (a.length() <= 0 || a[0] != '0') return a;
int i = 0;
while (i < a.length() && a[i] == '0') ++i;
a = a.substr(i, a.length() - i);
return a;
}

string &_string_add_string(const string &a, const string &b, string &res)
{
int sum_value = 0, add_bit = 0;
const int alen = a.length(), blen = b.length();
res = "0" + (alen > blen ? a : b);
for (int i = alen-1, j = blen-1, k = res.length() - 1;
i >= 0 || j >= 0 || add_bit > 0;
--i, --j, --k){
sum_value = (i>=0 ? a[i]-48: 0) + (j>=0 ? b[j]-48: 0) + add_bit;
add_bit = sum_value / 10;
res[k] = sum_value%10 + '0';
}
if (res[0] == '0') res = res.substr(1, res.length() - 1);
return res;
}

string fib(unsigned int m, int n) {
list<string> l;
string a = "0", s = "0", ret, temp;
for (int i = 1; i <= m && i <= n; ++i) {
_string_add_string(s, "1", a);
l.push_back(a);
_string_add_string(s, a, temp);
s = temp;
}
if (n <= m) return l.back();

for (int i = m + 1; i <= n; ++i) {
s = "0";
for (list<string>::iterator iter = l.begin();
iter != l.end(); ++iter) {
_string_add_string(s, *iter, temp);
s = temp;
}
ret = s;

l.pop_front();
l.push_back(ret);
}
return ret;
}

int main(void) {
int m, n;
while (scanf("%d%d", &m, &n) != EOF) {
printf("%s\n", fib(m, n).c_str());
}
return 0;
}


可能你还希望找出一个针对于存在m的情况下的高效率的解法。那好吧。这里给个示例。原理与前面O(lgN)的原理几乎一样。

只不过你需要注意一下A矩阵的表达形式。其他的没什么不一样的。还有就是最后有个相乘的情况。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct _node {
long long **a;
}node;
long long **_init(int m) {
long long **t = NULL;
t = (long long **)malloc(sizeof(long long*)*m);
for (int i = 0; i < m; ++i){
t[i] = (long long *)malloc(sizeof(long long) * m);
for (int j = 0; j < m; ++j) {
t[i][j] = 0;
}
}
return t;
}

void _destroy(long long **t, int m) {
for (int i = 0; i < m; ++i) {
free(t[i]);
}
free(t);
}

void _multiple(node *x, node *y, int m) {
node temp; temp.a = _init(m);
long long **a = x->a;
long long **b = y->a;
long long **c = temp.a;

for (int row = 0; row < m; ++row) {
for (int col = 0; col < m; ++col) {
long long s = 0;
for (int i = 0; i < m; ++i) {
s += a[row][i] * b[i][col];
}
c[row][col] = s;
}
}
_destroy(x->a, m);
x->a = temp.a;
}

long long fib(int m, int n)
{
long long ret;
long long *front = (long long *) malloc(sizeof(long long)*(m + 1));

long long s = 0;
front[0] = 0;
for (int i = 1; i <= m && i <= n; ++i) {
front[i] = s + 1;
s += front[i];
}

if (n <= m) {
ret = front
;
free(front);
return ret;
}

node odd; odd.a = _init(m);
//设置odd为单位矩阵
for (int i = 0; i < m; ++i) {
odd.a[i][i] = 1;
}
//A矩阵.
node temp; temp.a = _init(m);
for (int i = 0; i < m; ++i) {
temp.a[i][0] = 1;
if (i + 1 < m) temp.a[i][i+1] = 1;
}

n -= m;
while (n) {
if (n&1) _multiple(&odd, &temp, m);
_multiple(&temp, &temp, m);
n >>= 1;
}

ret = 0;
for (int i = 1; i <= m; ++i) {
ret += front[i] * odd.a[m-i][0];
}

_destroy(odd.a, m);
_destroy(temp.a, m);
return ret;
}

int main()
{
int m, n;
while (scanf("%d%d", &m, &n) != EOF) {
printf("%lld\n", fib(m, n));
}
return 0;
}


扩展-3

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

测试链接:http://ac.jobdu.com/problem.php?cid=1039&pid=5

这个只是第二个扩展的特殊情况。也就是当m==n的时候的情况。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

long long fib(int m)
{
long long ret;
long long *front = (long long *) malloc(sizeof(long long)*(m + 1));

long long s = 0;
front[0] = 0;
for (int i = 1; i <= m; ++i) {
front[i] = s + 1;
s += front[i];
}
ret = front[m];
free(front);
return ret;
}

int main()
{
int m, n;
while (scanf("%d", &n) != EOF) {
printf("%lld\n", fib(n));
}
return 0;
}


这个就没有更高效的办法了。除了再加入大整数之外。~~大整数就不用再加了。都差不多的。

当然,如果再深入地看一下,会发现F(n) = 2^(n-1);

那这个代码就更好写了。

#include <stdio.h>

long long pow(long long x, long long n) {
long odd = 1;
while (n) {
if (n&1) odd *= x;
x *= x;
n >>= 1;
}
return odd;
}

int main(void) {
int n;
while (scanf("%d", &n) != EOF) {
printf("%lld\n", pow(2, n-1));
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: