`
gstarwd
  • 浏览: 1538868 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

数据结构面试大全(一) - [算法]

阅读更多

版权声明 :转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://lzhshen.blogbus.com/logs/20333778.html

1. 判断链表是否存在环型链表
问题:判断一个链表是否存在环,例如下面这个链表 就存在一个环:
例如 N1->N2->N3->N4->N5->N2 就是一个有环的链表,环的开始结点是 N5
这里有一个比较简单的解法。设置两个指针 p1 p2 。每次循环 p1 向前走一步, p2 向前走两步。直到 p2 碰到 NULL 指针或者两个指针相等结束循环。如果两个指针相等则说明存在环。
struct link
{
   int data;
    link* next;
};
 
bool IsLoop(link* head)
{
    link* p1=head, *p2 = head;
     if (head ==NULL || head->next ==NULL) 
     {
          return false;
     }
    do{
        p1= p1->next;
        p2 = p2->next->next;
    } while(p2 && p2->next && p1!=p2);     
     if(p1 == p2)
          return true;
     else
          return false;
}
2, 链表反转
单向链表的反转是一个经常 被问到的一个面试题,也是一个非常基础的问题。比如一个链表是这样的: 1->2->3->4->5 通过反转后成为 5->4->3->2->1 。最容易想到的方法遍历一遍链表,利用一个辅助指针,存储遍历过程中当前指针指向的 下一个元素,然后将当前节点元素的指针反转后,利用已经存储的指针往后面继续遍历。源代码如下:
struct linka {
     int data;
     linka* next;
};
 
void reverse(linka*& head)
{
     if(head ==NULL)
          return;
     linka*pre, *cur, *ne;
     pre=head;
     cur=head->next;
     while(cur)
     {
          ne = cur->next;
          cur->next = pre;
          pre = cur;
          cur = ne;
     }
     head->next = NULL;
     head = pre;
}
还有一种利用递归的方法。 这种方法的基本思想是在反转当前节点之前先调用递归函数反转后续节点。源代码如下。不过这个方法有一个缺点,就是在反转后的最后一个结点会形成一个环,所 以必须将函数的返回的节点的 next 域置为 NULL 。因为要改变 head 指针,所以我用了引用。算法的源代码如下:
linka* reverse(linka* p,linka*& head)
{
     if(p == NULL || p->next == NULL)
     {
          head=p;
          return p;
     }
     else
     {
          linka* tmp = reverse(p->next,head);
          tmp->next = p;
          return p;
     }
}
3, 判断两个数组中是否存在相同的数字
给定两个排好序的数组,怎 样高效得判断这两个数组中存在相同的数字?
这个问题首先想到的是一个 O(nlogn) 的 算法。就是任意挑选一个数组,遍历这个数组的所有元素,遍历过程中,在另一个数组中对第一个数组中的每个元素进行 binary search 。用 C++ 实现代码如下:
bool findcommon(int a[],int size1,int b[],int size2)
{
     int i;
     for(i=0;i<size1;i++)
     {
          int start=0,end=size2-1,mid;
          while(start<=end)
          {
               mid=(start+end)/2;
               if(a[i]==b[mid])
                    return true;
               else if (a[i]<b[mid])
                    end=mid-1;
               else
                    start=mid+1;
          }
     }
     return false;
}
后来发现有一个 O(n) 算法。因为两个数组都是排好序的。所以只要一次遍历就行了。首先设两个下标,分别初始化为两个数组的起始地址,依次向前推进 。推进的规则是比较两个 数组中的数字,小的那个数组的下标向前推进一步,直到任何一个数组的下标到达数组末尾时,如果这时还没碰到相同的数 字,说明数组中没有相同的数字。
bool findcommon2(int a[], int size1, int b[], int size2)
{
     int i=0,j=0;
     while(i<size1 && j<size2)
     {
          if(a[i]==b[j])
               return true;
          if(a[i]>b[j])
               j++;
          if(a[i]<b[j])
               i++;
     }
     return false;
}
4, 最大子序列
问题:
给定一整数序列 A1 A2 ... An (可能有负数),求 A1~An 的一个子序列 Ai~Aj ,使得 Ai Aj 的和最大
例如:
整数序列 -2, 11, -4, 13, -5, 2, -5, -3, 12, -9 的最 大子序列的和为 21
对于这个问题,最简单也是最容易想到 的那就是穷举所有子序列的方法。利用三重循环,依次求出所有子序列的和然后取最大的那个。当然算法复杂度会达到 O(n^3) 。显然这种方法不是最优的,下面给出一个算法复杂度为 O(n) 的线性算法实现,算法的来源 于 Programming Pearls 一书。
在给出线性算法之前,先来 看一个对穷举算法进行优化的算法,它的算法复杂度为 O(n^2) 。其实这个算法只是对对穷举算法稍微做了一些修改:其实子序列的和我们并不需要每次 都重新计算一遍。假设 Sum(i, j) A[i] ... A[j] 的和,那么 Sum(i, j+1) = Sum(i, j) + A[j+1] 。利用这一个递推,我们就可以得到下面这个算法:
int max_sub(int a[],int size)
{
     int i,j,v,max=a[0];
     for(i=0;i<size;i++)
     {
          v=0;
          for(j=i;j<size;j++)
          {
               v=v+a[j];//Sum(i, j+1) = Sum(i, j) + A[j+1]
               if(v>max)
                    max=v;
          }
     }
     return max;
}
那怎样才能达到线性复杂度 呢?这里运用动态规划的思想。先看一下源代码实现:
int max_sub2(int a[], int size)
{
     int i,max=0,temp_sum=0;
     for(i=0;i<size;i++)
     {
          temp_sum+=a[i];
          if(temp_sum>max)
               max=temp_sum;
          else if(temp_sum<0)
               temp_sum=0;
     }
     return max;
}
在这一遍扫描数组当中,从 左到右记录当前子序列的和 temp_sum ,若这个和不断增加,那么最大子序列的和 max 也不断增加 ( 不断更新 max) 。如果往前扫描中遇到负数, 那么当前子序列的和将会减小。此时 temp_sum 将会小于 max ,当然 max 也就不更新。如果 temp_sum 降到 0 时,说明前面已经扫描的那一段就可 以抛弃了,这时将 temp_sum 置为 0 。然后, temp_sum 将从后面开始将这个子段进行分析,若有比当前 max 大的子段,继 续更新 max 。这样一趟扫描结果也就出来了。
5, 找出单向链表的中间结点
这道题和解 判断链表是否存在环 ,我用的是非常类似的方法,只不过结束循环的条件和函数返回值不一样罢了。设置两个指针 p1 p2 。每次循环 p1 向前走一步, p2 向前走两步。当 p2 到达链表的末尾时, p1 指向的时链表的 中间。
link* mid(link* head)
{
       link* p1,*p2;
       p1=p2=head;
       if(head==NULL || head->next==NULL)
              return head;
       do {
              p1=p1->next;
              p2=p2->next->next;
       } while(p2 && p2->next);
       return p1;
}
6, 按单词反转字符串
并不是简单的字符串反转, 而是按给定字符串里的单词将字符串倒转过来,就是说字符串里面的单词还是保持原来的顺序,这里的每个单词用空格分开。例如:
Here is www.fishksy.com.cn
经过反转后变为:
www.fishksy.com.cn is Here
如果只是简单的将所有字符串翻转的话,可以遍历字符串,将第一个字符和最后一个交换, 第二个和倒数第二个交换,依次循环。其实按照单词反转的话可以在第一遍遍历的基础上,再遍历一遍字符串,对每一个单词再反转一次。这样每个单词又恢复了原 来的顺序。
char* reverse_word(const char* str)
{
     int len = strlen(str);
     char* restr = new char[len+1];
     strcpy(restr,str);
     int i,j;
     for(i=0,j=len-1;i<j;i++,j--)
     {
          char temp=restr[i];
          restr[i]=restr[j];
          restr[j]=temp;
     }
     int k=0;
     while(k<len)
     {
          i=j=k;
          while(restr[j]!=' ' && restr[j]!='\0' )
               j++;
          k=j+1;
          j--;
          for(;i<j;i++,j--)
          {
               char temp=restr[i];
               restr[i]=restr[j];
               restr[j]=temp;
          }
     }
     return restr;
}
如果考虑空间和时间的优化 的话,当然可以将上面代码里两个字符串交换部分改为异或实现。
例如将
          char temp=restr[i];
          restr[i]=restr[j];
          restr[j]=temp;
改为
          restr[i]^=restr[j];
          restr[j]^=restr[i];
          restr[i]^=restr[j];
 
7, 字符串反转
我没有记错的话是一道 MSN 的笔试题,网上无意中看到的,拿来做了一下。题目是这样的,给定一个字符串,一个这个字符串的子串,将第一个字符串反转,但保留子串 的顺序不变。例如:
输入: 第一个字符串 : "This is fishsky 's Chinese site: http://www.fishsky.com.cn/cn"
子串 : "fishsky"
输出: "nc/nc.moc.fishsky.www//:ptth :etis esenihC s'fishsky si sihT"
一般的方法是先扫描一边第一个字符串,然后用 stack 把它反转,同时记录下子串出现的位置。然后再扫描一遍把记录下来的子串再用 stack 反转。我用的方法是用一遍扫描数组的方法。扫描中如果发现子串,就将子串倒过来压入堆栈。
最后再将堆栈里的字符弹出,这样子串又恢复了原来的顺序。源代码如下:
#include <iostream>
#include <cassert>
#include <stack>
using namespace std;
//reverse the string 's1' except the substring 'token'.
const char* reverse(const char* s1, const char* token)
{
       assert(s1 && token);
       stack<char> stack1;
       const char* ptoken = token, *head = s1, *rear = s1;
       while (*head != '\0')
       {
              while(*head!= '\0' && *ptoken == *head)
              {
                 ptoken++;
                 head++;
              }
              if(*ptoken == '\0')//contain the token
              {
                 const char* p;
                 for(p=head-1;p>=rear;p--)
                        stack1.push(*p);
 
                 ptoken = token;
                 rear = head;
              }
              else
              {
                 stack1.push(*rear);
                 head=++rear;
                 ptoken = token;
              }
       }
       char * return_v = new char[strlen(s1)+1];
       int i=0;
       while(!stack1.empty())
       {
              return_v[i++] = stack1.top();
              stack1.pop();
       }
       return_v[i]='\0';
       return return_v;
}
int main(int argc, char* argv[])
{
       
       cout<<"This is 
fishsky
 's Chinese site: http://www.
fishsky
.com.cn/cn\n";
       cout<<reverse("This is 
fishsky
's Chinese site: http://www.
 fishsky
.com.cn/cn","
 fishsky
 ");
       return 0;
}
 
8, 删除数组中重复的数字
问题:一个动态长度可变 的数字序列,以数字 0 为结束标志,要求将重复的数字用一个数字代替,例如:
将数组 1,1,1,2,2,2,2,2,7,7,1,5,5,5,0 转变成 1,2,7,1,5,0
问题比较简单,要注意的 是这个数组是动态的。所以避免麻烦我还是用了 STL vector
#include <iostream>
#include <vector>
using namespace std;
//remove the duplicated numbers in an intger array, the array was end with 0;
//e.g. 1,1,1,2,2,5,4,4,4,4,1,0 --->1,2,5,4,1,0
void static remove_duplicated(int a[], vector<int>& _st)
{
       _st.push_back(a[0]);
       for(int i=1;_st[_st.size()-1]!=0;i++)
       {
              if(a[i-1]!=a[i])
                 _st.push_back(a[i]);
       }
}
当然如果可以改变原来的 数组的话,可以不用 STL ,仅需要指针操作就可以了。下面这个程序将修改原来数组的内容。
void static remove_duplicated2(int a[])
{
       if(a[0]==0 || a==NULL)
              return;
       int insert=1,current=1;
       while(a[current]!=0)
       {
              if(a[current]!=a[current-1])
              {
                 a[insert]=a[current];
                 insert++;
                 current++;
              }
              else
                 current++;
       }
       a[insert]=0;
}
 
9, 如何判断一棵二叉树是否是平衡二叉树
问题:判断一个二叉排序 树是否是平衡二叉树
解决方案:
根据平衡二叉树的定义,如果 任意节点的左右子树的深度相差不超过 1 ,那这棵树就是平衡二叉树。
首先编写 一个计算二叉树深度的函数,利用递归实现。
template<typename T>
static int Depth(BSTreeNode<T>* pbs)
{
       if (pbs==NULL)
              return 0;
       else
       {
              int ld = Depth(pbs->left);
              int rd = Depth(pbs->right);
              return 1 + (ld >rd ? ld : rd);
       }
}
下面是利用递归判断左右 子树的深度是否相差 1 来判断是否是平衡二叉树的函数:
template<typename T>
static bool isBalance(BSTreeNode<T>* pbs)
{
       if (pbs==NULL) 
              return true;
       int dis = Depth(pbs->left) - Depth(pbs->right);
       if (dis>1 || dis<-1 )
              return false;
       else
              return isBalance(pbs->left) && isBalance(pbs->right);
}
分享到:
评论

相关推荐

    算法大全-面试题-链表-栈-二叉树-数据结构

    "算法大全-面试题-链表-栈-二叉树-数据结构"这个压缩包文件提供了丰富的知识资源,旨在帮助学习者深入理解和应用这些核心概念。 链表是一种线性数据结构,与数组不同,它的元素在内存中不是连续存储的。链表由一...

    算法大全-面试题-数据结构

    在面试中,数据结构和算法的考察占据着重要地位,因为它们是软件开发和计算机科学中不可或缺的基础。 单链表是一种常见的线性数据结构,它由一系列节点组成,每个节点包含数据部分和指向下一个节点的指针。在面试中...

    程序员代码面试指南:IT名企算法与数据结构题目最优解-代码

    《程序员代码面试指南:IT名企算法与数据结构题目最优解-代码》是一部专为准备IT企业面试的程序员量身定制的指南。本书的核心内容围绕算法和数据结构展开,通过Java语言实现,旨在帮助读者掌握解决常见面试问题的...

    数据结构面试大全 面试 算法

    本资料集“数据结构面试大全”显然是为准备面试者提供了一个全面的资源库,包含了各种知名企业的真实面试题目,以及数据结构的基础知识。 1. **数组**:是最基本的数据结构,提供了随机访问元素的能力。面试中可能...

    面试---10. 数据结构与算法.pdf

    根据提供的文档内容,我们可以归纳出一系列关于数据结构与算法的重要知识点。下面将详细解析文档中提及的每一个问题或概念,并尽量扩展相关内容。 ### 一、数据结构基础 #### 1. 字符串处理 - **旋转词判断**: ...

    C/C++面试大全--算法和知识点详析

    在C/C++编程领域,面试通常会涵盖多个主题,包括但不限于语言特性、数据结构、算法、设计模式以及操作系统等基础知识。以下是一些基于提供的标题和描述中的知识点的详细分析: 1. **多态性与虚函数表**: - 多态性...

    算法数据结构常见面试题

    本资料包“算法数据结构常见面试题”是为准备面试的程序员精心准备的,涵盖了这一领域的核心知识点,旨在帮助你提升面试成功率。 数据结构是计算机科学中用于组织和存储数据的一种方式,它关系到数据的逻辑结构、...

    程序员代码面试指南 IT名企算法与数据结构题目最优解.zip

    这本书主要聚焦于算法和数据结构,旨在帮助读者掌握在面试中常见的问题,并提供最优解。"左神"作为标签,暗示了这本书的作者或者内容在编程界具有较高的权威性和影响力,"左神"通常是对在编程领域有深厚造诣的人的一...

    《名企数据结构面试题之--栈与队列(上)》课件和代码

    本课程“名企数据结构面试题之--栈与队列(上)”专注于讲解这两种基础但至关重要的数据结构。 栈(Stack)是一种后进先出(Last In, First Out, LIFO)的数据结构。它的主要操作包括压入(push)元素到栈顶和弹出...

    《程序员面试代码指南-IT名企算法与数据结构题目最优解》、个人面试算法练习

    《程序员面试代码指南——IT名企算法与数据结构题目最优解》、个人面试算法练习

    数据结构和算法面试题---微软、百度

    1. 数据结构与算法面试准备:文章提到的是微软和百度的面试题,这提示我们对于准备面试,特别是那些知名的科技公司,需要对数据结构和算法有深入的了解和准备。这包括但不限于对二元查找树、双向链表等基本数据结构...

    微软等公司数据结构-算法面试第1-100题汇总

    数据结构和算法是计算机科学的基础,对于面试来说,掌握这些知识是至关重要的,尤其是在像微软这样的顶级科技公司。以下是对给定题目的一些详细解析: 1. **二元查找树转双向链表**: - 二元查找树(BST)是一种自...

    数据结构算法与应用--C++语言描述(源代码)

    通过对这些源代码的阅读和分析,可以提升对数据结构和算法的理解,提高编程能力,对于准备面试或者实际项目开发都有很大帮助。在学习过程中,读者应结合理论与实践,逐步掌握这些核心概念,并能够灵活运用到具体问题...

    数据结构算法与应用-C++语言描述_Sahni著

    C++作为一种强大的编程语言,因其面向对象的特性,常被用于实现复杂的数据结构和算法。《数据结构算法与应用-C++语言描述》这本书,由Sahni著,旨在帮助读者深入理解这些核心概念,并通过C++实践来提升技能。 本书...

    数据结构与算法-----PPT版本

    这个“数据结构与算法-----PPT版本”很可能包含了徐旭松教授或专家精心制作的一系列教学材料,旨在帮助学习者深入理解这些核心概念。 数据结构是关于如何在计算机中组织和存储数据的方式,它直接影响到程序的效率和...

    面试前必备(微软数据结构+算法)

    在准备面试,特别是针对微软这样的顶级科技公司的面试时,数据结构和算法是不可或缺的重要部分。这份资源"面试前必备(微软数据结构+算法)"包含了针对这类面试的100道题目,旨在帮助求职者全面了解和掌握相关知识。...

    程序员代码面试指南-算法与数据结构代码

    《程序员代码面试指南-算法与数据结构代码》是一本针对准备技术面试,特别是涉及算法和数据结构问题的程序员的重要资源。书中的源代码涵盖了多种编程语言,如Java、C++或Python,旨在帮助读者理解并解决常见的面试...

    程序员代码面试指南--IT名企算法与数据结构题目最优解下载

    程序员代码面试指南--IT名企算法与数据结构题目最优解下载 左程云 程序员代码面试指南--IT名企算法与数据结构题目最优解下载 左程云 程序员代码面试指南--IT名企算法与数据结构题目最优解下载 左程云

Global site tag (gtag.js) - Google Analytics