剑指offer(链表的算法题)

本文包含链表的以下内容:

  1、查找单链表中的倒数第k个结点(剑指offer,题15)

  2、合并两个有序的单链表,合并之后的链表依然有序【出现频率高】(剑指offer,题17)

  3、单链表的反转【出现频率最高】(剑指offer,题16)

  4、从尾到头打印单链表(剑指offer,题5)

  5、单链表中,取出环的起始点(剑指offer,题56)

  6、判断两个单链表相交的第一个交点(剑指offer,题37)

      7、复杂链表的复制(剑指offer,题26)

      8、删除链表中重复的结点(剑指offer,题56)

 

 1. 两个指针思想,先让第一个指针和第二个指针都指向头结点,然后再让第一个指正走(k-1)步,到达第k个节点。然后两个指针同时往后移动,当第一个结点到达末尾的时候,第二个结点所在位置就是倒数第k个节点了

class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
       
        if(pListHead==NULL||k==0)
            return NULL;
        
        ListNode* head1=pListHead;
        ListNode* head2=pListHead;
        
        for(int i=0;i<k-1;i++)   
        {  
            if(head2->next!=NULL)         //防止K的大小比整个链表个数还多
               head2=head2->next;
            else
                return NULL;
        }
        while(head2->next!=NULL)
        {
            head1=head1->next;
            head2=head2->next;
        }
        return head1;
             
    }
};

2.归并排序的思想(非递归版本)

class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {  
        if(!pHead1)                     //检查判断不能少
            return pHead2;
        if(!pHead2)
            return pHead1;
        
        ListNode* pNode=NULL;
        ListNode* pHead=NULL;
        if(pHead1->val<pHead2->val)      //确定头指针
        {
            pHead=pHead1;
            pHead1=pHead1->next;
        }
        else
        {
            pHead=pHead2;
            pHead2=pHead2->next;
        }
        pNode=pHead;
        while((pHead1!=NULL)&&(pHead2!=NULL))     //开始使用归并的思想
        {
            if( pHead1->val<pHead2->val)
            {
                pNode->next=pHead1;
                pHead1=pHead1->next;
                pNode=pNode->next;
            }
            else
            {
                pNode->next=pHead2;
                pHead2=pHead2->next;
                pNode=pNode->next;
            }
        }
            if(pHead1==NULL)              //最后的多余的部分直接指向就可以,因为链表是连接的。
            {
                pNode->next=pHead2;
            }
            if(pHead2==NULL)
            {
                pNode->next=pHead1;
            }
        
        return pHead;
    }
};

3.采用3个指针的思想(保存,反转,前移)

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
      ListNode* pNode=pHead;
      ListNode* pPreNode=NULL;
      while(pNode!=NULL)
      {  
          ListNode* pNext=pNode->next;   //用一个临时指针来保存下一个结点,防止断开后丢失。
          pNode->next=pPreNode;         //保存完之后,就可以连接翻转,指向前一个结点
          
          pPreNode=pNode;               //完成一次翻转后,把结点往前移
          pNode=pNext;
      }
        return pPreNode;
    }
};

 

4.堆栈的思想

 

class Solution {
public:
    vector <int> dev;
 //   vector<int> printListFromTailToHead(ListNode* head) {     //递归解法
 //       if(head!=NULL)
 //       {
 //           if(head->next!=NULL)
 //             dev = printListFromTailToHead(head->next);
 //           dev.push_back(head->val);
 //      }
 //       return dev;
 //   }
    
    vector<int> printListFromTailToHead(ListNode* head) {       //采用堆栈的思想,后进先出
      std::stack<ListNode *>nodes;
      ListNode* pNode=head;
        while(pNode!=NULL)
        {
            nodes.push(pNode);
            pNode=pNode->next;
        }
        while(!nodes.empty())
        {
            pNode=nodes.top();
            dev.push_back(pNode->val);
            nodes.pop();
        }
        return dev;
    }
    
    //也可以直接先存到dev,在调用翻转函数即可.总共3种方法
};

5.先判断是否有环,如果有环返回相遇点,再统计环内结点的数目n。最后先让快指针走n步,然后快慢指针一起走,相遇的地点就是环的入口处。

class Solution {
public:
    ListNode* MeetingNode(ListNode* pHead)               //慢指针走一步,快指针走两步,若有环,必在环内相遇,返回该相遇点
    {  
        if(pHead==NULL||pHead->next==NULL)
            return NULL;
        ListNode* slow=pHead;
        ListNode* fast=pHead;
        while(slow->next!=NULL && fast->next->next!=NULL)
        {
            slow=slow->next;
            fast=fast->next->next;
            if(slow==fast)
                return fast;
        }
        return NULL;
        
    }
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
       ListNode* meetingNode=MeetingNode(pHead);
       if(meetingNode==NULL)
           return NULL;
       
        int LoopNodesNum=1;                    //从相遇节点出发,一边计数一边移动,当再次回到该节点时,就可以得到环中节点数目
        ListNode* node=meetingNode;
        while(node->next!=meetingNode)
        {
            node=node->next;
            ++LoopNodesNum;
        }
        
        ListNode* node1=pHead;                //先让第一个节点走LoopNodesNum步,随后在一起走,直到相遇的地方就是环的入口点
        for(int i=0;i<LoopNodesNum;i++)
            node1=node1->next;
        
        ListNode* node2=pHead;
        while(node1!=node2)
        {
            node1=node1->next;
            node2=node2->next;
        }
        
        return node1;
       
    }
};

6.用到两个指针的思想。因为每个结点只有一个next指针,所以从第一个公共结点开始,之后他们所有结点都是重合的。因此拓扑形状看起来像一个Y,而不是X;

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {                               
public:
    int GetLength(ListNode* pHead)              //遍历统计链表的各自长度
    {
        ListNode* node=pHead;
        int num=0;
        while(node!=NULL)
        {
            num++;
            node=node->next;
        }
        return num;
    }
    ListNode* preWalk(ListNode* pHead,int k)      //让长的链表先走len1-len2
    {
        ListNode* node=pHead;
        while(k--)
        {
            node=node->next;
        }
        return node;
    }
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        int  len1=GetLength(pHead1);
        int  len2=GetLength(pHead2);
        if(len1>len2)
        {
            pHead1=preWalk(pHead1,len1-len2);
        }
        else
        {  
            pHead2=preWalk(pHead2,len2-len1);
        }
        
       while(pHead1!=NULL&&pHead2!=NULL&&pHead1!=pHead2)
       {
           pHead1=pHead1->next;
           pHead2=pHead2->next;
       }
         return pHead1;
    }
};

7.复杂链表的复制,采用三个步骤

/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
public:
    void CloneNodes(RandomListNode* pHead)               //复制原始链表的任一节点N并创建新节点N',再把N'链接到N的后边
    {  
        RandomListNode* pNode=pHead;
        while(pNode!=NULL)
        {
           RandomListNode* pClone=new RandomListNode(0);  //先进行克隆结点的初始化
           pClone->label=pNode->label;
           pClone->random=NULL;
            
           pClone->next=pNode->next;                      //将克隆结点分别插入到相应位置
           pNode->next=pClone; 
           
           pNode=pClone->next;
        }
        
    }
    
    void ConnectSiblingNodes(RandomListNode* pHead)      //如果原始链表上的节点N的random指向S,则对应的复制节点N'的random指向S的下一个节点S'
    {

        RandomListNode* pNode=pHead;
        while(pNode!=NULL)
        {
            RandomListNode* pClone=pNode->next;
            if(pNode->random!=NULL)
                pClone->random=pNode->random->next;
            
            pNode=pClone->next;
            
        }
    }
    RandomListNode* SplitNodes(RandomListNode* pHead)   //把得到的链表拆成两个链表,奇数位置上的结点组成原始链表,偶数位置上的结点组成复制出来的链表
    {

        RandomListNode* pNode=pHead;
        RandomListNode* pClone=NULL;
        RandomListNode* pCloneHead=NULL;
        
        if(pNode!=NULL)                                 //初始化先将头部分离
        {
            pClone=pCloneHead=pNode->next;
            pNode->next=pClone->next;
            pNode=pNode->next;
        }
        while(pNode!=NULL)                             //循环依次往后分离
        {
            pClone->next=pNode->next;
            pClone=pClone->next;
            pNode->next=pClone->next;
            pNode=pNode->next;
        }
        return pCloneHead;
    }
    RandomListNode* Clone(RandomListNode* pHead)
    {
       CloneNodes(pHead);
       ConnectSiblingNodes(pHead);
       return  SplitNodes(pHead);
    }
};

8.一个指向前一个节点pre,另一个指向当前节点p,再用一个指针next指向后一个结点,如果遇到相等的节点,next向后移动,pre不动,直到遇到p和next不相等,pre就可以指向next。注意:因为头结点也可能重复被删除,所以新建一个指针newHead指向头结点

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    { 
        if(pHead==NULL||pHead->next==NULL)
            return pHead;                    //注意这里不能返回NULL
        
        ListNode* newHead=new ListNode(-1);  //因为头结点也可能重复被删除,所以新建一个指针指向头结点
        newHead->next=pHead;
        ListNode* pre=newHead;               //需要两个指针,一个指向前一个节点preNode,另一个指向当前节点node,
        ListNode* p=pHead;
        ListNode* next=NULL;
        while(p!=NULL && p->next!=NULL)
        {
            next=p->next;
            if(p->val==next->val)           //如果当前节点的值和下一个节点的值相等
            {
                while(next!=NULL && p->val==next->val)  //向后重复查找.移动
                    next=next->next;
                pre->next=next;            //直到不相等为止,进行连接
                p=next;
            }
            else                            //如果当前节点的值和下一个节点的值不相等,直接向后移一个
            {
                pre=p;
                p=next;
            }
        }
        return newHead->next;
    }
};

 

参考链接:https://blog.csdn.net/u012129558/article/details/52575543

 

 

 

 

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值