数据结构面试大全

摘要: 1.判断链表是否存在环型链表问题:判断一个链表是否存在环,例如下面这个链表就存在一个环:例如N1->N2->N3->N4- >N5->N2就是一个有环的链表,环的开始结点是N5这里有一个比较简单的解法。设置两个指针p1,p2。每次循环p1向前走一步,p2向前走两步。直到p2碰到NULL指针或者两个指针相等结束循环。如果两个指针相等则说明存在环。[@more@]
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
     {
          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]
                    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
     {
          if(a[i]==b[j])
               return true;
          if(a[i]>b[j])
               j++;
          if(a[i]
               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
     {
          v=0;
          for(j=i;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
     {
          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
     {
          char temp=restr[i];
          restr[i]=restr[j];
          restr[j]=temp;
     }
     int k=0;
     while(k
     {
          i=j=k;
          while(restr[j]!=' ' && restr[j]!='' )
               j++;
          k=j+1;
          j--;
          for(;i
          {
               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 
#include 
#include 
using namespace std;
//reverse the string 's1' except the substring 'token'.
const char* reverse(const char* s1, const char* token)
{
       assert(s1 && token);
       stack stack1;
       const char* ptoken = token, *head = s1, *rear = s1;
       while (*head != '')
       {
              while(*head!= '' && *ptoken == *head)
              {
                 ptoken++;
                 head++;
              }
              if(*ptoken == '')//contain the token
              {
                 const char* p;
                 for(p=head-1;p>=rear;p--)
                        stack1.push(*p);
 
                 ptoken = token;
                 rear = head;
              }
              else
                    

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/7196059/viewspace-1000617/,如需转载,请注明出处,否则将追究法律责任。

上一篇: 关于重载(ZT)
user_pic_default.png
请登录后发表评论 登录
全部评论
<%=items[i].createtime%>

<%=items[i].content%>

<%if(items[i].items.items.length) { %>
<%for(var j=0;j
<%=items[i].items.items[j].createtime%> 回复

<%=items[i].items.items[j].username%>   回复   <%=items[i].items.items[j].tousername%><%=items[i].items.items[j].content%>

<%}%> <%if(items[i].items.total > 5) { %>
还有<%=items[i].items.total-5%>条评论 ) data-count=1 data-flag=true>点击查看
<%}%>
<%}%>
<%}%>

转载于:http://blog.itpub.net/7196059/viewspace-1000617/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值