您的位置:首页 > 理论基础 > 数据结构算法

80道常见数据结构面试题及其解法(2)

2016-06-19 15:05 405 查看
第13题: 输入一个单向链表,输出该链表中倒数第k
个结点。链表的倒数第0个结点为链表的尾指针。

两个指针,第一个先走K步,第二个再和第一个一起走,一直到第一个走到尾结点。那第二个指针的位置就是所求。


14.题目:输入一个已经按升序排序过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。

要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字M,输出任意一对即可。

例如输入数组1、2、4、7、11、15和数字15。由于4+11=15,因此输出4和11。

思路:

(1)让指针指向数组的头部和尾部,相加,如果小于M,则增大头指针,如果大于则减小尾指针

(2)退出的条件,相等或者头部=尾部

算法:

void function(int a[],int n,int M){

int i=0,j=n-1;

  while(i!=j){

if(a[i]+a[j]==M){

      printf("%d,%d",a[i],a[j]);

break;

    }

    a[i]+a[j]>M?j--:i++;

  }

15题目:输入一个表示整数的字符串,把该字符串转换成整数并输出。

2

16题:

写一个函数,它的原形是int continumax(char *outputstr,char *intputstr)

功能:

在字符串中找出连续最长的数字串,并把这个串的长度返回,

并把这个最长数字串付给其中一个函数参数outputstr 所指内存。

例如:"abcd12345ed125ss123456789"的首地址传给intputstr 后,函数将返回9,

outputstr 所指的值为123456789

[cpp] view
plain copy

#include <iostream>

using namespace std;

int continueMax(char * &outputStr, char *inputStr)

{

int max = 0;

while(*inputStr != '\0')

{

while((*inputStr < '0' ) || (*inputStr > '9'))

{

inputStr++;

}

char *tempStr = inputStr;

while ((*inputStr >= '0') && (*inputStr <= '9'))

{

inputStr++;

}

if (inputStr - tempStr > max)

{

max = inputStr - tempStr;

outputStr = tempStr;

}

}

return max;

}

int main(void)

{

char *input = "abcd12345ed125ss123456789";

char *output = NULL;

int length = continueMax(output, input);

cout << length << endl;

for(int i = 0; i < length; i++)

{

cout << output[i];

}

system("pause");

return 0;

}

17 题目:

一个台阶总共有n 级,如果一次可以跳1 级,也可以跳2 级,求总共有多少总跳法,并分析算法的时间复杂度。

注:

这道题最近经常出现,包括MicroStrategy 等比较重视算法的公司都曾先后选用过个这道题作为面试题或者笔试题。

思路一:

首先我们考虑最简单的情况:如果只有1 级台阶,那显然只有一种跳法,如果有2 级台阶,那就有两种跳的方法了:一种是分两次跳,每次跳1 级;另外一种就是一次跳2 级。

现在我们再来讨论一般情况:我们把n 级台阶时的跳法看成是n 的函数,记为f(n)。当n>2 时,第一次跳的时候就有两种不同的选择:一是第一次只跳1 级,此时跳法数目等于后面剩下的n-1 级台阶的跳法数目,即为f(n-1);另外一种选择是第一次跳2 级,此时跳法数目等于后面剩下的n-2 级台阶的跳法数目,即为f(n-2)。

因此n 级台阶时的不同跳法的总数f(n) = f(n-1) + f(n-2)。

我们把上面的分析用一个公式总结如下:

/ 1 (n=1)

f(n) = 2 (n=2)

\ f(n-1) + (f-2) (n>2)

分析到这里,相信很多人都能看出这就是我们熟悉的Fibonacci 序列。(O(n))

代码如下:

[cpp] view
plain copy

/*----------------------------

Copyright by yuucyf. 2011.08.16

-----------------------------*/

#include "stdafx.h"

#include <iostream>

using namespace std;

int JumpStep(int n)

{

if (n <= 0) return 0;

if (n == 1 || n == 2) return n;

return (JumpStep(n-1) + JumpStep(n-2));

}

int _tmain(int argc, _TCHAR* argv[])

{

int nStep = 0;

cout << "请输入台阶数:";

cin >> nStep;

cout << "台阶数为" << nStep << ",那么总共有" << JumpStep(nStep) << "种跳法." << endl;

return 0;

}

18 题目:输入一个整数,求该整数的二进制表达中有多少个1。例如输入10,由于其二进制表示为1010,有两个1,因此输出2。

分析:这是一道很基本的考查位运算的面试题。包括微软在内的很多公司都曾采用过这道题。

一个很基本的想法是,我们先判断整数的最右边一位是不是1。接着把整数右移一位,原来处于右边第二位的数字现在被移到第一位了,再判断是不是1。这样每次移动一位,直到这个整数变成0为止。现在的问题变成怎样判断一个整数的最右边一位是不是1了。很简单,如果它和整数1作与运算。由于1除了最右边一位以外,其他所有位都为0。因此如果与运算的结果为1,表示整数的最右边一位是1,否则是0。

得到的代码如下:

///////////////////////////////////////////////////////////////////////

// Get how many 1s in an integer's binary expression

///////////////////////////////////////////////////////////////////////

int NumberOf1_Solution1(int i)

{

int count = 0;

while(i)

{

88dc
if(i & 1)

count ++;

i = i >> 1;

}

return count;

}

19.栈的push、pop序列

题目:输入两个整数序列。其中一个序列表示栈的push顺序,

判断另一个序列有没有可能是对应的pop顺序。

为了简单起见,我们假设push序列的任意两个整数都是不相等的。

比如输入的push序列是1、2、3、4、5,那么4、5、3、2、1就有可能是一个pop系列。

因为可以有如下的push和pop序列:

push 1,push 2,push 3,push 4,pop,push 5,pop,pop,pop,pop,

这样得到的pop序列就是4、5、3、2、1。

但序列4、3、5、1、2就不可能是push序列1、2、3、4、5的pop序列。

20 strcpy函数的实现

大家一般认为名不见经传strcpy函数实现不是很难,流行的strcpy函数写法是:

[cpp] view
plain copy

char *my_strcpy(char *dst,const char *src)

{

assert(dst != NULL);

assert(src != NULL);

char *ret = dst;

while((* dst++ = * src++) != '\0')

;

return ret;

}

如果注意到:

1,检查指针有效性;

2,返回目的指针des;

3,源字符串的末尾 '\0' 需要拷贝。

写出上面实现函数就不在话下。

然而这样的实现没有考虑拷贝时内存重叠的情况,下面的测试用例就能使调用my_strcp函数的程序崩溃:

[cpp] view
plain copy

char str[10]="abc";

my_strcpy(str+1,str);

然而调用系统的strcpy函数程序正常运行,打印str结果为“aabc”!可见系统strcpy函数的实现不是这样的。

strcpy的正确实现应为:

[cpp] view
plain copy

char *my_strcpy(char *dst,const char *src)

{

assert(dst != NULL);

assert(src != NULL);

char *ret = dst;

memcpy(dst,src,strlen(src)+1);

return ret;

}

memcpy函数实现时考虑到了内存重叠的情况,可以完成指定大小的内存拷贝,它的实现方式建议查看文章“卓越的教练是如何训练高手的?”,会获益良多,这里仅粘帖函数memcpy函数的实现:

[cpp] view
plain copy

void * my_memcpy(void *dst,const void *src,unsigned int count)

{

assert(dst);

assert(src);

void * ret = dst;

if (dst <= src || (char *)dst >= ((char *)src + count))//源地址和目的地址不重叠,低字节向高字节拷贝

{

while(count--)

{

*(char *)dst = *(char *)src;

dst = (char *)dst + 1;

src = (char *)src + 1;

}

}

else //源地址和目的地址重叠,高字节向低字节拷贝

{

dst = (char *)dst + count - 1;

src = (char *)src + count - 1;

while(count--)

{

*(char *)dst = *(char *)src;

dst = (char *)dst - 1;

src = (char *)src - 1;

}

}

return ret;

}

两者结合才是strcpy函数的真正实现吧。

实现自己的strstr函数:返回主串中子字符串的位置后的所有字符。

如:主串“12345678”,字串“45”,函数返回“45678”

[cpp] view
plain copy

#include <stdio.h>

const char *my_strstr(const char *str, const char *sub_str)

{

for(int i = 0; str[i] != '\0'; i++)

{

int tem = i; //tem保留主串中的起始判断下标位置

int j = 0;

while(str[i++] == sub_str[j++])

{

if(sub_str[j] == '\0')

{

return &str[tem];

}

}

i = tem;

}

return NULL;

}

int main()

{

char *s = "1233345hello";

char *sub = "345";

printf("%s\n", my_strstr(s, sub));

return 0;

}

将一句话里面的单词进行倒置,标点符号不倒换

#include <stdio.h>

#include <string.h>

void RevStr(char *str)

{

printf("原字符串:\n%s\n", str);

int i = 0, j = 0, temp, begin, end;

j = strlen(str) - 1;

while(j > i)

{

temp = str[i];

str[i] = str[j];

str[j] = temp;

i++;

j--;

}

printf("全部翻转后:\n%s\n", str);

//第二步进行部分翻转,不是空格就翻转

i = 0;

while(str[i])

{

if(str[i] != ' ')

{

begin = i;

while(str[i] && str[i] != ' ')

{

i++;

}

i = i-1; //回到空格前一个字符

end = i;

}

while(end > begin)

{

temp = str[begin];

str[begin] = str[end];

str[end] = temp;

end--;

begin++;

}

i++;

}

printf("部分翻转后:\n%s\n", str);

}

int main()

{

char p[] = "I come from Bengbu.";

RevStr(p);

return 0;

}


面试题11:数值的整数次方

题目:实现函数double Power(double base,int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大树问题。


这道题目有以下几点需要注意:

0的0次方是无意义的,非法输入
0的负数次方相当于0作为除数,也是无意义的,非法输入
base如果非0,如果指数exponent小于0,可以先求base的|exponent|次方,然后再求倒数
判断double类型的base是否等于0不能使用==号。因为计算机表述小树(包括float和double型小数)都有误差,不能直接使用等号(==)判断两个小数是否相等。如果两个数的差的绝对值很小,那么可以认为两个double类型的数相等。

根据以上4个注意点,我们可以写出求指数的程序,代码如下:



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

bool isInvalidInput=false;

double PowerWithUnsingedExponent(double base,unsigned int absExp)
{
double result=1.0;
for(int i=0;i<absExp;i++)
result*=base;
return result;
}

//由于精度原因,double类型的变量不能用等号判断两个数是否相等,因此需要写equsl函数
bool equal(double a,double b)
{
if((a-b>-0.000001)&&(a-b<0.000001))
return true;
else
return false;
}

double Power(double base,int exponent)
{
//如果底数为0且指数小于0,则表明是非法输入。
if(equal(base,0.0) && exponent<=0)
{
isInvalidInput=true;
return 0;
}

unsigned int absExp;
//判断指数正负,去指数的绝对值
if(exponent<0)
absExp=(unsigned int)(-exponent);
else
absExp=(unsigned int)exponent;

double result=PowerWithUnsingedExponent(base,absExp);

//如果指数小于0则取倒数
if(exponent<0)
result=1/result;

return result;
}

void main()
{
double a=Power(2.0,13);
cout<<a<<endl;

system("pause");
}


更优的解法:

假设我们求2^32,指数是32,那么我们需要进行32次循环的乘法。但是我们在求出2^16以后,只需要在它的基础上再平方一次就可以求出结果。同理可以继续分解2^16。也就是a^n=a^(n/2)*a^(n/2),(n为偶数);或者a^n=a^((n-1)/2)*a^((n-1)/2)*a,(n为奇数)。这样就将问题的规模大大缩小,从原来的时间复杂度O(n)降到现在的时间复杂度O(logn)。可以用递归实现这个思路,代码如下:



double PowerWithUnsingedExponent(double base,unsigned int absExp)
{
if(absExp==0)
return 1;
else if(absExp==1)
return base;

double result=PowerWithUnsingedExponent(base,absExp/2);
result*=result;//指数减少一倍以后用底数来乘
if(absExp%2==1)//如果指数为奇数,还得再乘一次底数
result*=base;
return result;
}


上述程序使用了递归的方法,这样会增加程序的空间复杂度,下面我们使用循环实现递归的思路,代码如下:
以下方法错误:



double PowerWithUnsingedExponent(double base,unsigned int absExp)
{
if(absExp==0)
return 1;
else if(absExp==1)
return base;

double result=1.0*base;
for(int i=2;i<=absExp;i=i*2)
result*=result;
if(absExp%2==1)//如果指数为奇数,还得再乘一次底数
result*=base;

return result;
}


二叉树的深度优先遍历与广度优先遍历
[ C++ 实现 ]

Posted on 2013-02-03 12:52 fancydeepin 阅读(3341) 评论(0) 编辑 收藏 所属分类: 数据结构


深度优先搜索算法(Depth First Search),是搜索算法的一种。是沿着树的深度遍历树的节点,尽可能深的搜索树的分支。

当节点v的所有边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。

如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。



如右图所示的二叉树:

A 是第一个访问的,然后顺序是 B、D,然后是 E。接着再是 C、F、G。

那么,怎么样才能来保证这个访问的顺序呢?

分析一下,在遍历了根结点后,就开始遍历左子树,最后才是右子树。

因此可以借助堆栈的数据结构,由于堆栈是后进先出的顺序,由此可以先将右子树压栈,然后再对左子树压栈,

这样一来,左子树结点就存在了栈顶上,因此某结点的左子树能在它的右子树遍历之前被遍历。

深度优先遍历代码片段

//深度优先遍历

void depthFirstSearch(Tree root){

stack<Node *> nodeStack; //使用C++的STL标准模板库

nodeStack.push(root);

Node *node;

while(!nodeStack.empty()){

node = nodeStack.top();

printf(format, node->data); //遍历根结点

nodeStack.pop();

if(node->rchild){

nodeStack.push(node->rchild); //先将右子树压栈

}

if(node->lchild){

nodeStack.push(node->lchild); //再将左子树压栈

}

}

}

广度优先搜索算法(Breadth First Search),又叫宽度优先搜索,或横向优先搜索。


是从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法中止。

如右图所示的二叉树,A 是第一个访问的,然后顺序是 B、C,然后再是 D、E、F、G。

那么,怎样才能来保证这个访问的顺序呢?

借助队列数据结构,由于队列是先进先出的顺序,因此可以先将左子树入队,然后再将右子树入队。

这样一来,左子树结点就存在队头,可以先被访问到。

广度优先遍历代码片段

//广度优先遍历

void breadthFirstSearch(Tree root){

queue<Node *> nodeQueue; //使用C++的STL标准模板库

nodeQueue.push(root);

Node *node;

while(!nodeQueue.empty()){

node = nodeQueue.front();

nodeQueue.pop();

printf(format, node->data);

if(node->lchild){

nodeQueue.push(node->lchild); //先将左子树入队

}

if(node->rchild){

nodeQueue.push(node->rchild); //再将右子树入队

}

}

}

完整代码:

/**

* <!--

* File : binarytree.h

* Author : fancy

* Email : fancydeepin@yeah.net

* Date : 2013-02-03

* --!>

*/

#include <stdio.h>

#include <stdlib.h>

#include <malloc.h>

#include <Stack>

#include <Queue>

using namespace std;

#define Element char

#define format "%c"

typedef struct Node {

Element data;

struct Node *lchild;

struct Node *rchild;

} *Tree;

int index = 0; //全局索引变量

//二叉树构造器,按先序遍历顺序构造二叉树

//无左子树或右子树用'#'表示

void treeNodeConstructor(Tree &root, Element data[]){

Element e = data[index++];

if(e == '#'){

root = NULL;

}else{

root = (Node *)malloc(sizeof(Node));

root->data = e;

treeNodeConstructor(root->lchild, data); //递归构建左子树

treeNodeConstructor(root->rchild, data); //递归构建右子树

}

}

//深度优先遍历

void depthFirstSearch(Tree root){

stack<Node *> nodeStack; //使用C++的STL标准模板库

nodeStack.push(root);

Node *node;

while(!nodeStack.empty()){

node = nodeStack.top();

printf(format, node->data); //遍历根结点

nodeStack.pop();

if(node->rchild){

nodeStack.push(node->rchild); //先将右子树压栈

}

if(node->lchild){

nodeStack.push(node->lchild); //再将左子树压栈

}

}

}

//广度优先遍历

void breadthFirstSearch(Tree root){

queue<Node *> nodeQueue; //使用C++的STL标准模板库

nodeQueue.push(root);

Node *node;

while(!nodeQueue.empty()){

node = nodeQueue.front();

nodeQueue.pop();

printf(format, node->data);

if(node->lchild){

nodeQueue.push(node->lchild); //先将左子树入队

}

if(node->rchild){

nodeQueue.push(node->rchild); //再将右子树入队

}

}

}

/**

* <!--

* File : BinaryTreeSearch.h

* Author : fancy

* Email : fancydeepin@yeah.net

* Date : 2013-02-03

* --!>

*/

#include "binarytree.h"

int main() {

//上图所示的二叉树先序遍历序列,其中用'#'表示结点无左子树或无右子树

Element data[15] = {'A', 'B', 'D', '#', '#', 'E', '#', '#', 'C', 'F','#', '#', 'G', '#', '#'};

Tree tree;

treeNodeConstructor(tree, data);

printf("深度优先遍历二叉树结果: ");

depthFirstSearch(tree);

printf("\n\n广度优先遍历二叉树结果: ");

breadthFirstSearch(tree);

return 0;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  数据结构 面试题