面试题精选(一)

题目地址:http://blog.csdn.net/garfielder007/article/details/48931183

数组

求子数组的最大和

题目:
输入一个整形数组,数组里有正数也有负数。
数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。
求所有子数组的和的最大值。要求时间复杂度为O(n)。
例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2,

因此输出为该子数组的和18。

ANSWER: 
 A traditional greedy approach.
 Keep current sum, slide from left to right, when sum < 0, reset sum to 0.

int maxSubarray(int a[], int size) {
   if (size<=0) error(“error array size”);
   int sum = 0;
   int max = - (1 << 31);
   int cur = 0;
   while (cur < size) {
     sum += a[cur++];
     if (sum > max) {
       max = sum;
     } else if (sum < 0) {
       sum = 0;
     }
   }
   return max;
 }

查找最小的k个元素[算法] 

题目:输入n个整数,输出其中最小的k个。例如输入1,2,3,4,5,6,7和8这8个数字,则最小的4个数字为1,2,3和4。


分析:这道题最简单的思路莫过于把输入的n个整数排序,这样排在最前面的k个数就是最小的k个数。只是这种思路的时间复杂度为O(nlogn)。我们试着寻找更快的解决思路。
我们可以先创建一个大小为k的数据容器来存储最小的k个数字。接下来我们每次从输入的n个整数中读入一个数。如果容器中已有的数字少于k个,则直接把这次读入的整数放入容器之中;如果容器中已有k个数字了,也就是容器已满,此时我们不能再插入新的数字而只能替换已有的数字。我们找出这已有的k个数中最大值,然和拿这次待插入的整数和这个最大值进行比较。如果待插入的值比当前已有的最大值小,则用这个数替换替换当前已有的最大值;如果带插入的值比当前已有的最大值还要大,那么这个数不可能是最小的k个整数之一,因为我们容器内已经有k个数字比它小了,于是我们可以抛弃这个整数。

因此当容器满了之后,我们要做三件事情:一是在k个整数中找到最大数,二是有可能在这个容器中删除最大数,三是可能要插入一个新的数字,并保证k个整数依然是排序的。如果我们用一个二叉树来实现这个数据容器,那么我们能在O(logk)时间内实现这三步操作。因此对于n个输入数字而言,总的时间效率就是O(nlogk)。

我们可以选择用不同的二叉树来实现这个数据容器。由于我们每次都需要找到k个整数中的最大数字,我们很容易想到用最大堆。在最大堆中,根结点的值总是大于它的子树中任意结点的值。于是我们每次可以在O(1)得到已有的k个数字中的最大值,但需要O(logk)时间完成删除以及插入操作。

我们自己从头实现一个最大堆需要一定的代码。我们还可以采用红黑树来实现我们的容器。红黑树通过把结点分为红、黑两种颜色并根据一些规则确保树是平衡的,从而保证在红黑树中查找、删除和插入操作都只需要O(logk)。在STL中set和multiset都是基于红黑树实现的。如果面试官不反对我们用STL中的数据容器,我们就直接拿过来用吧。下面是基于STL中的multiset的参考代码:

typedef multiset<int, greater<int> >  IntHeap;

///////////////////////////////////////////////////////////////////////
// find k least numbers in a vector
///////////////////////////////////////////////////////////////////////
void FindKLeastNumbers
(
      const vector<int>& data,               // a vector of data
      IntHeap& leastNumbers,                 // k least numbers, output
      unsigned int k                              
)
{
      leastNumbers.clear();

      if(k == 0 || data.size() < k)
            return;

      vector<int>::const_iterator iter = data.begin();
      for(; iter != data.end(); ++ iter)
      {
            // if less than k numbers was inserted into leastNumbers
            if((leastNumbers.size()) <= k)
                  leastNumbers.insert(*iter);

            // leastNumbers contains k numbers and it's full now
            else
            {
                  // first number in leastNumbers is the greatest one
                  IntHeap::iterator iterFirst = leastNumbers.begin();

                  // if is less than the previous greatest number 
                  if(*iter < *(leastNumbers.begin()))
                  {
                        // replace the previous greatest number
                        leastNumbers.erase(iterFirst);
                        leastNumbers.insert(*iter);
                  }
            }
      }
}


字符串

翻转句子中单词的顺序。
 题目:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。
 句子中单词以空格符隔开。为简单起见,标点符号和普通字母一样处理。
 例如输入“I am a student.”,则输出“student. a am I”。

思路:先整个字符串反转,然后各个单词反转。

#include <stdio.h>
#include <string.h> 
/** * reverse a string */ 
void reverse(char * str, int str_len, int start, int end) { 
    if(str == NULL || start < 0 || end > str_len || start >= end) { 
        return; 
    } 
    while(start < end) { 
        char tmp = str[end]; 
        str[end] = str[start]; 
        str[start] = tmp; 
        start ++; 
        end --; 
    } 
    return; 
} 

void reverse_words(char * str, int str_len) {
     /* reverse whole string first */ 
     reverse(str, str_len, 0, str_len - 1); 
     
     /* reverse each single word */ 
     int start = 0, end = 0; 
     char * tmps = str;      
     while(*tmps != '\0') { 
         if(*tmps == ' ') { 
             reverse(str, str_len, start, end - 1); 
             start = end + 1; 
         } 
         end ++; 
         tmps ++; 
     } 
     return; 
 } 
 
 int main(int argc, char * argv[]) { 
     char str[] = "I am a student."; 
     reverse_words(str, sizeof(str)-1); 
     printf("%s\n", str); 
     return 0; 
 } 



链表

判断两个链表是否相交并找出交点

问题描述:

一个比较经典的问题,判断两个链表是否相交,如果相交找出他们的交点。


思路:


1、碰到这个问题,第一印象是采用hash来判断,将两个链表的节点进行hash,然后判断出节点,这种想法当然是可以的。

2、当然采用暴力的方法也是可以的,遍历两个链表,在遍历的过程中进行比较,看节点是否相同。

3、第三种思路是比较奇特的,在编程之美上看到的。先遍历第一个链表到他的尾部,然后将尾部的next指针指向第二个链表(尾部指针的next本来指向的是null)。这样两个链表就合成了一个链表,判断原来的两个链表是否相交也就转变成了判断新的链表是否有环的问题了:即判断单链表是否有环?
这样进行转换后就可以从链表头部进行判断了,其实并不用。通过简单的了解我们就很容易知道,如果新链表是有环的,那么原来第二个链表的头部一定在环上。因此我们就可以从第二个链表的头部进行遍历的,从而减少了时间复杂度(减少的时间复杂度是第一个链表的长度)。
下图是一个简单的演示:


这种方法可以判断两个链表是否相交,但不太容易找出他们的交点。

4、仔细研究两个链表,如果他们相交的话,那么他们最后的一个节点一定是相同的,否则是不相交的。因此判断两个链表是否相交就很简单了,分别遍历到两个链表的尾部,然后判断他们是否相同,如果相同,则相交;否则不相交。示意图如下:


判断出两个链表相交后就是判断他们的交点了。假设第一个链表长度为len1,第二个问len2,然后找出长度较长的,让长度较长的链表指针向后移动|len1 - len2| (len1-len2的绝对值),然后在开始遍历两个链表,判断节点是否相同即可。

下面给出一个简单的实现:

typedef struct node_t
{
    int data;//data
    struct node_t *next; //next
} node;

 
node* find_node(node *head1, node *head2)
{
    if(NULL == head1 || NULL == head2)
    {
        return NULL;//如果有为空的链表,肯定是不相交的
    }

    node *p1, *p2;
    p1 = head1;
    p2 = head2;
    int len1 = 0;
    int len2 =0;
    int diff = 0;
    while(NULL != p1->next)
    {
        p1 = p1->next;
        len1++;
    }
    
    while(NULL != p2->next)
    {
        p2 = p2->next;
        len2++;
    }

    if(p1 != p2) //如果最后一个节点不相同,返回NULL
    {
        return NULL;
    }

    diff = abs(len1 - len2);

    if(len1 > len2)
    {
        p1 = head1;
        p2 = head2;
    } else {
        p1 = head2;
        p2 = head1;
    }

    for(int i=0; i<diff; i++) {
        p1 = p1->next;
    }

    while(p1 != p2) {
        p1 = p1->next;
        p2 = p2->next;
    }
    return p1;
}

题目:链表反转

http://blog.csdn.net/kc58236582/article/details/77503376


题目:输入一个单向链表,输出该链表中倒数第k个结点。链表的倒数第0个结点为链表的尾指针。
分析:
单链表只能向后遍历,不能向前遍历,尾指针好找,倒数第K个不能从尾指针向前找。
倒的不好找,正的好找,我们只需要知道链表的总长度,就可以知道正数第几个节点(len(list) - k -1)。

#include<iostream>

using namespace std;

struct ListNode{
	ListNode(int _v = 0):value(_v), next(NULL) {}
	int value;
	ListNode* next;
	void add(ListNode* _l)
	{
		if(next== NULL)
			next = _l;
		else
			next->add(_l);
	}
};


ListNode* findNode(ListNode* root, int k/*倒数第k个*/)
{
	int len = 0;
	ListNode* p = root;
	while(p != NULL)
	{
		len ++;
		p = p->next;
	}

	if(k > len) return NULL;
	int i = 1;
	p = root;
	while( i < len-k+1)
	{
		p = p->next;
		i++;
	}
	return p;
}

int main()
{
	ListNode *root = new ListNode(0);
	ListNode l1(1);
	ListNode l2(2);
	ListNode l3(3);
	ListNode l4(4);
	ListNode l5(5);
	ListNode l6(6);
	root->add(&l6);
	root->add(&l5);
	root->add(&l4);
	root->add(&l3);
	root->add(&l2);
	root->add(&l1);

	cout << "List 节点为:" ;
	ListNode* p = root;
	while(p != NULL)
	{
		cout << p->value << ",";
		p = p->next;
	}
	cout << endl;
	p = findNode(root, 4);
	cout << "List 倒数第 4 个节点是:" << p->value << endl;
	return 0;
}




二叉树

在二元树中找出和为某一值的所有路径

题目:输入一个整数和一棵二元树。
从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径。
打印出和与输入整数相等的所有路径。
例如输入整数22 和如下二元树
10
/ \
5 12
/ \
4 7
则打印出两条路径:10, 12 和10, 5, 7。

分析:看到该题目的第一反应是:递归+回溯。首先当然要保存搜索的路径,并记录当前路径上所有元素的和。如果累积和与当前节点值的和大于输入的整数data,则将不进行加法运算,直接回退;如果与当前节点值的和刚好等于sum,且当前节点为叶子节点,则打印该路径,然后回退;如果与当前节点值得和小于data,则继续向下计算……,这样的做法似乎很合理,其实呢?很显然,如果这样做,我们就为问题添加了一个条件:即二元树上的元素值均为正数!!!题目显然没有给出这个条件。自然算法就错了……
    正确的做法应该是这样的:首先当然仍要保存搜索的路径,并记录当前路径上所有元素的和sum。如果当前节点为叶子节点,并且当前节点值与sum的和等于data,则满足条件,打印后递归返回到父节点,注意在打印后、递归返回之前要先减去当前节点元素的值;如果当前节点不是叶子节点,则不必判断当前节点值与sum的和是否等于data,继续访问子节点……
    另一种类似的方法不用录当前路径上所有元素的和sum,而是使用期望的和依次减去访问到的节点的值……最后是判断到达叶子节点时期望和是否减为0,其实时类似的……看代码实现:

#include <stdafx.h>
#include<stdlib.h>
#define MAX 20

struct BiTreeNode
{
	int data;
	struct BiTreeNode *left;
	struct BiTreeNode *right;
};

/*递归创建二叉排序树,以'-1'结束*/
BiTreeNode * CreateBSTree(int *data,int pos,int len)
{
	BiTreeNode * tr;
	if(pos>=len)
	{
		return NULL;
	}
	else
	{
		tr = (BiTreeNode *)malloc(sizeof(BiTreeNode));
		tr->data=data[pos];
		tr->left = CreateBSTree(data,2*pos+1,len);
		tr->right = CreateBSTree(data,2*pos+2,len);  
		
		return tr;
	}
}

//中序遍历二叉树
void InOrderTraverse(BiTreeNode * root)
{
	if(root!=NULL)
	{
		InOrderTraverse(root->left);	
		printf("%d  ",root->data);
		InOrderTraverse(root->right);
	}
}

//打印路径
void printPath(int path[],int top)
{
	printf("第一组:");
	for(int i=0;i<top;i++)
		printf("%d ",path[i]);
	printf("\n");
}

//查找和为sum的路径,path数组存放路径的值,top代表每个可行的路径中的元素个数
void findPath(BiTreeNode *root, int sum,int top,int path[])
{
	path[top++] = root->data;
	sum -= root->data;
	
	if (root->left == NULL && root->right==NULL) 
	{
		if (sum == 0) 
		{
			printPath(path, top);
		}
	} 
	else 
	{
		if (root->left != NULL) 
			findPath(root->left, sum, top,path);
		if (root->right!=NULL) 
			findPath(root->right, sum, top,path);
	}
	top --;
	sum += root->data;
}

int main()
{
	int data[]={1,2,3,1,2,3,2,1,3,2,1,2,1,2,1};
	int len=sizeof(data)/sizeof(int);

	BiTreeNode * root=CreateBSTree(data,0,len);	
	InOrderTraverse(root);
	printf("\n");
	
	int sum=6;
	int top=0;
	int path[MAX]={0};
	findPath(root,sum,top,path);
	
	return 0;
}


其他:

题目:求1+2+…+n,
要求不能使用乘除法、for、while、if、else、switch、case 等关键字以及条件判断语句
 用C语言做到题
编写程序,用递归的方法求1+2+3+…+n,可设递归函数为fun ()。
(1)递归结束条件为n=0;
(2)递推公式为n+fun (n-1)。
 用C语言编程序

#include<stdio.h>

int fun(int n) {

    if(n==0){
        return 0;
    } else{
        return n+fun(n-1);
    }
}

void main() {
    int n;
    long sum=0;
    scanf("%d",&n);
    sum=fun(n);
    printf("%ld\n",sum);
}




猜你喜欢

转载自blog.csdn.net/kc58236582/article/details/78359057