本帖题目均选自 何海涛剑指offer一书,欢迎大家与我一起做、讨论这些题目,共同享受编程和思考的乐趣,何乐而不为呢?
1、在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
答案
杨氏矩阵的查找,可以分治,但一个比较好的解法是从第一行最后一列开始查找,决定往左还是往下移动。
#include <iostream>
using namespace std;
#define N 1024
int a[N][N]={{1,2,8,9},{2,4,9,12},{4,7,10,13},{6,8,11,15}};
int main(int argc,char *argv[])
{
int m = 4,n = 4,t = 6,r,c;
r = 0;
c = n - 1;
bool find = false;
while((r < m) && (c >= 0))
{
if(t == a[r][c])
{
find = true;
break;
}
else
if(t < a[r][c])
c--;
else
r++;
}
printf("%d:",a[r][c]);
puts(find?"存在":"不存在");
return 0;
}
2、输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并输出它的后序遍历序列。 (本题可以直接输出来,不用先还原出二叉树)
答案: 递归的简单应用,前序遍历的根在最前面,在中序遍历里找到那个数,然后前序序列和中序序列的分解成为两部分,对这两部分在递归即可。可以直接生成后续遍历的序列,而不用重构出这棵树。
#include <iostream>
using namespace std;
int pre[1024],post[1024],in[1024];
bool can(int *pre,int *in,int *post,int fpre,int fin,int fpost,int len) {
int i;
if (len <= 0) {
return true;
}
for (i = 0; i < len; ++i) {
if (pre[fpre] == in[fin + i]) {
break;
}
}
if (i >= len) {
return false;
}
if (!can(pre,in,post,fpre + 1,fin,fpost,i)) {
return false;
}
if (!can(pre,in,post,fpre + i + 1,fin + i + 1, fpost + i,len - i - 1)) {
return false;
}
post[fpost + len - 1] = pre[fpre];
return true;
}
int main() {
int i,n;
while (scanf("%d",&n) != EOF) {
for (i = 0; i < n; ++i) {
scanf("%d",pre + i);
}
for (i = 0; i < n; ++i) {
scanf("%d", in + i);
}
if (can(pre,in,post,0,0,0,n)) {
for (i = 0; i < n; ++i) {
printf("%d ",post[i]);
}
puts("");
}
else {
puts("No");
}
}
return 0;
}
3、把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 (如果数组里有重复元素,怎么办?)
答案: 2分查找的扩充,考虑 [from,to]这段区间,mid = (from + to) / 2,如果a[mid] < a[to] 则最小值在前半段里,如果a[mid] > a[to], 这段区间发生了跳变,所以最小值在后半段区间里。相等时,当然我们可以比较a[mid]和a[from],但再相等就没办法了。偷懒的做法是直接从前后半段分别找,然后返回最小的,因此最差时间复杂度是O(n)。当存在相等的数时,可能达到最差时间复杂度。
#include<iostream>
using namespace std;
int a[1000005];
int find(int *a,int from,int to) {
if (from == to - 1) {
return (a[from] < a[to])?a[from]:a[to];
}
if (from == to) {
return a[to];
}
int mid = (from + to) >> 1,x;
if (a[mid] < a[to]) {
x = find(a, from, mid - 1);
if (x > a[mid]) {
x = a[mid];
}
}
else if (a[mid] > a[to]) {
x = find(a,mid + 1, to);
}
else {
int x1 = find(a, from, mid - 1);
int x2 = find(a, mid + 1, to );
x = (x1 < x2)?x1:x2;
if (x > a[mid]) {
x = a[mid];
}
}
return x;
}
int main() {
int i,n;
while (scanf("%d",&n) != EOF) {
for (i = 0; i < n; ++i) {
scanf("%d", a + i);
}
printf("%d\n",find(a,0,n -1));
}
return 0;
}
4、大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。斐波那契数列的定义如下:
f(n) = 0 n = 0;
f(n) = 1 n = 1
f(n) = f(n - 1) + f(n - 2)
答案: 直接用定义递推求就可以。当然可以用O(n)空间来求保存出每一项,实际上保存最后两项即可。所以可以做到O(1)空间,O(n)时间。另外,斐波那契数列增长速度非常快,所以要用到long long。
#include<iostream>
using namespace std;
typedef long long ll;
int main(int argc,char *argv[])
{
ll a,b;
int i,n;
while(scanf("%d",&n) != EOF)
{
if(n < 2)
printf("%d\n",n);
else
{
a = 0;
b = 1;
printf("%4lld%4lld",a,b);
for(i = 1;;)
{
a += b;
printf("%4lld",a);
if(++i == n)
{
printf("\n");
break;
}
b += a;
printf("%4lld",b);
if(++i == n)
{
printf("\n");
break;
}
}
}
}
return 0;
}
5、 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
答案: 简单递推,最后一次上一级或者两级,所以f(n) = f(n - 1) + f(n - 2) 所以结果也是斐波那契数列,除初始条件外,和第4题一样。
#include<iostream>
using namespace std;
typedef long long ll;
int main()
{
ll a,b;
int i,n;
while (scanf("%d",&n) != EOF)
{
if (n < 2) {
printf("%d\n",n);
}
else {
a = 1;
b = 1;
for (i = 1;;)
{
a += b;
if (++i == n)
{
printf("%lld\n",a);
break;
}
b += a;
if (++i == n)
{
printf("%lld\n",b);
break;
}
}
}
}
return 0;
}
6 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
f(n) = f(n - 1) + f(n - 2) + f(n - 3) +...+f(0) 算出f(n) = 2^n
#include <iostream>
using namespace std;
typedef long long ll;
int main() {
int n;
while (scanf("%d",&n) != EOF) {
printf("%lld\n",((ll) 1) << (n - 1));
}
return 0;
}
7 我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
考虑 如果第一块横放还是竖着放,因为大矩形是2 * n的 所以 f(n) = f(n - 1) + f(n - 2)因此 还是斐波那契数。
#include <iostream>
using namespace std;
typedef long long ll;
int main() {
ll a,b;
int i,n;
while (scanf("%d",&n) != EOF) {
if (n < 2) {
printf("%d\n",n);
}
else {
a = 1;
b = 1;
for (i = 1;;) {
a += b;
if (++i == n) {
printf("%lld\n",a);
break;
}
b += a;
if (++i == n) {
printf("%lld\n",b);
break;
}
}
}
}
return 0;
}
8 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵:
#include <iostream>
using namespace std;
int a[1024][1024];
int main() {
int i,j,m,n;
while (scanf("%d%d",&m,&n) != EOF) {
for (i = 0; i < m; ++i) {
for (j = 0; j < n; ++j) {
scanf("%d",&a[i][j]);
}
}
int U = 0;
int D = m - 1;
int L = 0;
int R = n - 1;
for (;(U <= D) && (L <= R);++U,--D,++L,--R) {
for (i = L; i <= R; ++i) {
printf("%d ",a[U][i]);
}
for (i = U + 1; i <= D; ++i) {
printf("%d ",a[i][R]);
}
if (U != D) {
for (i = R - 1; i >= L; --i) {
printf("%d ",a[D][i]);
}
}
if (L != R) {
for (i = D - 1; i > U; --i) {
printf("%d ",a[i][L]);
}
}
}
puts("");
}
return 0;
}
9 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列
其实就是弄个栈模拟,当前没处理的数,如果不等于栈顶元素就是压入栈,否则弹出栈。 |
10 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
后序遍历的话根在最后,从跟分开。找到左子树右子树两段,这两段分别是比根小的和比根大的(一定是连续的),然后分别递归判断。
#include <iostream>
using namespace std;
int a[10005];
bool help(int *a,int from,int to) {
int i,j;
if (from >= to) {
return true;
}
for (i = from; a[i] < a[to];++i)
;
j = i - 1;
for (;a[i] > a[to];++i)
;
return (i == to) && help(a, from, j) && help(a, j, to - 1);
}
int main() {
int n,i;
while (scanf("%d",&n) != EOF) {
for (i = 0; i < n; ++i) {
scanf("%d",a + i);
}
puts(help(a,0,n - 1)?"Yes":"No");
}
return 0;
}