剑指offer之链表

Posted dzy521

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了剑指offer之链表相关的知识,希望对你有一定的参考价值。

//剑指offer 之 链表

//面试题6 从尾到头打印链表
/*****************************************************************************************
问题描述:
输入一个链表的头节点,从尾到头反过来打印出每个节点的值
链表节点定义如下:
struct ListNode{
   int m_nValue;
   ListNode* m_pNext;
};
******************************************************************************************/

//解答如下:
//首先实现栈 由于是用C语言 所以需要自己构建栈
int a[100];  //用数组实现栈
int p = 0;       //栈顶
//入栈
void push(int a[], int value)
{
  if(p == 99)
  {
    /*扩大栈*/
  }
  a[p++] = value;
}

//出栈
int pop(int a[]);
{
  if(p <= 25)
  {
     /*压缩栈*/
  }

  int res = a[--p];
  return res;
}

//判断是否空栈
int isEmpty(int a[])
{
  return p == 0;
}

//从尾到头打印链表
//栈实现
void PrintListReversingly_Iteratively(ListNode* pHead)
{
  if(pHead == NULL) return;
  ListNode *tmp = pHead;

  while(tmp != NULL)
  {
    push(a,tmp->value);
    tmp = tmp->m_pNext;
  }

  int value;
  while(!isEmpty(a))
  {
     value = pop(a);
     printf("%d-",value);
  }
}

//递归实现
void PrintListReversingly_Recursively(ListNode* pHead)
{
   if(pHead == NULL) return;
   if(pHead->m_pNext != NULL)
   {
    PrintListReversingly_Recursively(pHead->m_pNext);
   }
   printf("%d-",pHead->m_nValue);
   return;
}





//面试题18:删除链表的节点
/**********************************************************
18-1:
在O(1)时间内删除链表节点
给定单向链表的头指针和一个节点指针
链表节点定义如问题6所述, 函数定义如下
***********************************************************/

void DeleteNode(ListNode** pListNode, ListNode* pToBeDeleted)
{
  /*
  1)节点位于中间:将下一个节点内容复制到该节点,删除下一个节点 时间复杂度为O(1)
  2)节点位于尾部:从头遍历至该节点;
  3)链表只有一个节点:删除并将头指针置为NULL
  */
  if( pListNode == NULL || pToBeDeleted == NULL) return;
  //处理情况1 
  if(pToBeDeleted->m_pNext != NULL)
  {
    ListNode *pNext = pToBeDeleted->m_pNext;
    pToBeDeleted->m_nValue = pNext->m_nValue;
    pToBeDeleted->m_pNext = pNext->m_pNext;
    free(pNext);
    pNext = NULL;
  }
  //处理情况3
  else if(*pListNode == pToBeDeleted)
  {
    free(pToBeDeleted);
    *pListNode = NULL;
    pToBeDeleted = NULL;
  }
  //处理情况2
  else 
  {
    ListNode *pNext = *pListNode;
    while(pNext->m_pNext != pToBeDeleted)
    {
      pNext = pNext->m_pNext;
    }
    pNext->m_pNext = NULL;
    free(pToBeDeleted);
    pToBeDeleted = NULL;
  }
  /*
  复杂度分析:假设链表上有n个节点
  对于前n-1个节点,时间复杂度为O(1)
  对于最后一个节点,时间复杂度为O(n)
  因此,总的为((n-1)*O(1) + O(n)) /n = O(1)
  */
}


/*********************************************
18-2:
在一个排序链表中,如何删除重复节点?
**********************************************/
void DeleteDuplication(ListNode** pHead)
{
  if(pHead == NULL || *pHead == NULL) return;

  ListNode* pPreNode = NULL; //围绕前面节点 此节点 此节点的后续节点 操作
  ListNode* pNode = *pHead;

  while(pNode != NULL)
  {
    ListNode *pNext = pNode->m_pNext;
    int deleteFlag = 0;
    //判断是否需要删除操作
    if(pNode->m_nValue == pNext->m_nValue) deleteFlag = 1;
    //不需要删除,则指针向后移
    if(!deleteFlag)
    {
      pPreNode = pNode;
      pNode = pNode->m_pNext;
    }
    else
    {
      int value = pNode->m_nValue;
      ListNode *pToBeDel = pNode;
      //不断执行删除操作 从删除当前节点开始
      //前面节点用于连接后面的节点
      while(pToBeDel != NULL && pToBeDel->m_nValue == value)
      {
        pNext = pToBeDel->m_pNext;
        free(pToBeDel);
        pToBeDel = pNext;
      }
      //将需要删除的节点已删除完毕    判断之前删除的节点中是否包含头节点
      if(pPreNode == NULL) *pHead = pNext;
      else
           pPreNode->m_pNext = pNext;

      pNode = pNext;
    }
  }
}




/***********************************************************************************************
面试题22:链表中倒数第k个节点
输入一个链表,输出该连表中倒k个节点

双指针 注意判断边界以及k小于链表个数
************************************************************************************************/
ListNode* FindKthToTail(ListNode* pListNode, unsigned int k)
{
  if(pListNode == NULL || k <= 0) return NULL;

  ListNode *pAhead = pListNode;
  ListNode *pBhead = NULL;

  for(unsigned int i=0;i<k-1;i++)
  {
     if(pAhead->m_pNext != NULL)
     {
      pAhead = pAhead->m_pNext;
     }
     else
     {
      return NULL;
     }
  }

 pBhead = pListNode; 
 while(pAhead != NULL)
 {
   pBhead = pBhead->m_pNext;
   pAhead = pAhead->m_pNext;
 }

 return pBhead;
}


/*
拓展:求链表中间节点
方法:快慢指针
*/


/***********************************************************************************************
面试题23: 链表中环的入口节点
如果一个链表中有环,如何找出出环的入口节点
解题思路:
1)先判断有无环存在:
  设定快慢指针,若慢指针会追上快指针,则存在环
2)得出环中节点数目n:
  在1)基础上,返回两指针相遇点,该点在环内,然后让指针从
  该点出发走一圈,记录数目
3)根据环中节点数目n:
  还是两指针,一个在另一个前面n步,同时走,则相遇处为入口
************************************************************************************************/
ListNode* MeettingNode(ListNode *pHead)
{
  if(pHead == NULL || pHead->m_pNext == NULL) return NULL;

  ListNode *pFast = pHead;
  ListNode *pSlow = pHead;

  while(pFast != NULL && pSlow!= NULL)
  {
     if(pFast == pSlow) return pFast;

     pSlow = pSlow->m_pNext;
     pFast = pFast->m_pNext;  
     if(pFast != NULL)//当链表中无环时起到了判断作用
       pFast = pFast->m_pNext;
  }
  return NULL;
}

ListNode* EntryNodeOfLoop(ListNode* pHead)
{
    ListNode *meetingNode = MeettingNode(pHead);
    if(meetingNode == NULL) return NULL;

    //得环中节点数目n
    int num_of_nodes = 1;
    ListNode *tmp1 = meetingNode;
    while(tmp1->m_pNext != meetingNode)
    {
      num_of_nodes++;
      tmp1 = tmp1->m_pNext;
    }

    //移动第一个指针 n步
    tmp1 = pHead;
    for(int i=0;i<num_of_nodes;i++)
    {
      tmp1 = tmp1->m_pNext;
    }

    //两指针一起走
    ListNode *tmp2 = pHead;
    while(tmp1 != tmp2)
    {
      tmp1 = tmp1->m_pNext;
      tmp2 = tmp2->m_pNext;
    }

    return tmp1;
}




/*****************************************************************************************
面试题24: 反转链表
定义一个函数,输入一个链表的头节点,
反转该链表并输出反转后链表的头节点
*******************************************************************************************/
ListNode *ReverseList(ListNode* pHead)
{
  if(pHead == NULL || pHead->m_pNext == NULL) return pHead;

  ListNode *pPreNode = NULL;
  ListNode *pNode = pHead;
  ListNode *pNext = NULL;
  ListNode *pReverseHead = NULL;
  while(pNode != NULL)
  {
      pNext = pNode->m_pNext;
      //判断是否到尾部
      if(pNext == NULL) pReverseHead = pNode;

      pNode->m_pNext = pPreNode;
      pPreNode = pNode;
      pNode = pNext;
  }

  return pReverseHead;
}





/*********************************************************************************************
面试题25:合并两个排序链表
输入两个递增排序的链表,合并这两个链表并使新链表中的
节点仍然是排序的
*********************************************************************************************/

ListNode *Merge(ListNode *pHead1, ListNode *pHead2)
{
   if(pHead1 == NULL) return pHead2;
   else if(pHead2 == NULL) return pHead1;

   ListNode* pMergedHead = NULL;

   if(pHead1->m_nValue < pHead2->m_nValue)
   {
       pMergedHead = pHead1;
       pMergedHead -> m_pNext = Merge(pHead1->m_pNext,pHead2);
   }
   else 
   {
       pMergedHead = pHead2;
       pMergedHead -> m_pNext = Merge(pHead1,pHead2->m_pNext);
   }
   return pMergedHead;
}


/*
面试题35 复杂链表的复制
实现函数ComplexListNode * Clone(ComplexListNode* pHead);
复制一个复杂链表,每个节点除了有一个m_pNext指针指向下一个节点,还有一个
m_pSibling指针指向链表中的任意节点orNULL
*/
struct ComplexListNode{
  int m_nValue;
  ComplexListNode* m_pNext;
  ComplexListNode* m_pSibling;
};

 

以上是关于剑指offer之链表的主要内容,如果未能解决你的问题,请参考以下文章

剑指offer五十五之链表中环的入口结点

剑指offer(五十六)之链表中环的入口结点

Leetcode刷题笔记之链表篇剑指 Offer 18. 删除链表的节点

Leetcode刷题笔记之链表篇剑指 Offer 18. 删除链表的节点

Leetcode刷题笔记之链表篇剑指 Offer 22. 链表中倒数第k个节点

剑指offer--链表小结