链表常见题型整理

1. 将链表进行翻转

从头到尾遍历原链表,每遍历有关结点,将其摘下放在新链表的最前端。注意链表为空和只有一个结点的情况。

ListNode * ReverseList(ListNode * pHead){
  // 如果链表为空或只有一个结点,无需反转,直接返回原链表头指针
  if(pHead == NULL || pHead->m_pNext == NULL)
    return pHead;
  ListNode * pReversedHead = NULL; // 反转后的新链表头指针,初始为NULL
  ListNode * pCurrent = pHead;
  while(pCurrent != NULL){
    ListNode * pTemp = pCurrent;
    pCurrent = pCurrent->m_pNext;
    pTemp->m_pNext = pReversedHead; // 将当前结点摘下,插入新链表的最前端
    pReversedHead = pTemp;
  }
return pReversedHead;
}

2. 查找链表的中间结点

设置两个指针,两个指针同时向前走,前面的指针每次走两步,后面的指针每次走一步,前面的指针走到最后一个结点时,后面的指针所指的结点就是中间结点,即第 (n / 2 + 1) 个结点。

ListNode * GetMiddleNode(ListNode * pHead){

  // 链表为空或只有一个结点,返回头指针
  if(pHead == NULL || pHead->m_pNext == NULL)
    return pHead;

  ListNode * pAhead = pHead;
  ListNode * pBehind = pHead;

  // 前面指针每次走两步,直到指向最后一个结点,后面指针每次走一步
  while(pAhead->m_pNext != NULL){
    pAhead = pAhead->m_pNext;
    pBehind = pBehind->m_pNext;
    if(pAhead->m_pNext != NULL)
      pAhead = pAhead->m_pNext;
  }

  // 后面的指针所指结点即为中间结点
  return pBehind;
}

3. 查找链表倒数第 k 个结点

使用两个指针,先让前面的指针走到正向的第k个结点,这样前后两个指针的距离差是k-1,之后前后两个指针一起向前走,前面的指针走到最后一个结点时,后面指针所指的结点就是倒数第k个结点。

// 函数名前面的R代表反向
ListNode * RGetKthNode(ListNode * pHead, unsigned int k){
  // 这里k的计数是从1开始的,若k为0或链表为空返回NULL
  if(k == 0 || pHead == NULL)
    return NULL;

  ListNode * pAhead = pHead;
  ListNode * pBehind = pHead;

  // 前面的指针先走到正向第k个结点
  while(k > 1 && pAhead != NULL){
    pAhead = pAhead->m_pNext;
    k--;
  }

  // 结点个数小于k,返回NULL
  if(k > 1 || pAhead == NULL)    
    return NULL;

  // 前后两个指针一起向前走,直到前面的指针指向最后一个结点
  while(pAhead->m_pNext != NULL){
    pBehind = pBehind->m_pNext;
    pAhead = pAhead->m_pNext;
  }

  // 后面的指针所指结点就是倒数第k个结点
  return pBehind;
}

4. 逆序打印链表

对于这种颠倒顺序的问题,要不使用栈,要不使用递归解决。

递归实现

void RPrintList(ListNode * pHead){
  if(pHead == NULL){
    return;
  } else {
    RPrintList(pHead->m_pNext);
    printf("%d\t", pHead->m_nKey);
  }
}

栈实现

void RPrintList(ListNode * pHead){
  std::stack<ListNode *> s;
  ListNode * pNode = pHead;
  while(pNode != NULL){
    s.push(pNode);
    pNode = pNode->m_pNext;
  }
  while(!s.empty()){
    pNode = s.top();
    printf("%d\t", pNode->m_nKey);
    s.pop();
  }
}

5. 判断一个链表是否有环

这里也使用两个指针。如果一个链表中有环,也就是说用一个指针去遍历,是永远走不到头的。因此,我们可以用两个指针去遍历,一个指针一次走两步,另一个指针一次走一步,如果有环,两个指针肯定会在环中相遇。

bool HasCircle(ListNode * pHead){
  // 快指针每次前进两步
  ListNode * pFast = pHead;

  // 慢指针每次前进一步
  ListNode * pSlow = pHead;

  while(pFast != NULL && pFast->m_pNext != NULL){
    pFast = pFast->m_pNext->m_pNext;
    pSlow = pSlow->m_pNext;

  // 相遇,存在环
  if(pSlow == pFast)
    return true;
  }
  return false;
}

6. 判断两个链表是否相交

如果两个链表相交于某一节点,那么在这个相交节点之后的所有节点都是两个链表所共有的。也就是说,如果两个链表相交,那么最后一个节点肯定是共有的。先遍历第一个链表,记住最后一个节点,然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点作比较,如果相同,则相交,否则不相交。

bool IsIntersected(ListNode * pHead1, ListNode * pHead2){
  if(pHead1 == NULL || pHead2 == NULL)
    return false;

  ListNode * pTail1 = pHead1;
  while(pTail1->m_pNext != NULL)
    pTail1 = pTail1->m_pNext;

  ListNode * pTail2 = pHead2;
  while(pTail2->m_pNext != NULL)
    pTail2 = pTail2->m_pNext;
  return pTail1 == pTail2;
}

7. 求两个链表相交的第一个节点

对第一个链表遍历,计算长度 len1,同时保存最后一个节点的地址。
对第二个链表遍历,计算长度 len2,同时检查最后一个节点是否和第一个链表的最后一个节点相同,若不相同,不相交,结束。
两个链表均从头节点开始,假设 len1 大于 len2,那么将第一个链表先遍历 len1 ~ len2 个节点,此时两个链表当前节点到第一个相交节点的距离就相等了,然后一起向后遍历,知道两个节点的地址相同。

ListNode* GetFirstCommonNode(ListNode * pHead1, ListNode * pHead2){
  if(pHead1 == NULL || pHead2 == NULL)
    return NULL;

  int len1 = 1;
  ListNode * pTail1 = pHead1;
  while(pTail1->m_pNext != NULL){
    pTail1 = pTail1->m_pNext;
    len1++;
  }

  int len2 = 1;
  ListNode * pTail2 = pHead2;
  while(pTail2->m_pNext != NULL){
    pTail2 = pTail2->m_pNext;
    len2++;
  }

  // 不相交直接返回NULL
  if(pTail1 != pTail2)
    return NULL;

  ListNode * pNode1 = pHead1;
  ListNode * pNode2 = pHead2;

  // 先对齐两个链表的当前结点,使之到尾节点的距离相等
  if(len1 > len2){
    int k = len1 - len2;
    while(k--)
      pNode1 = pNode1->m_pNext;
  } else {
    int k = len2 - len1;
    while(k--)
      pNode2 = pNode2->m_pNext;
  }
  while(pNode1 != pNode2){
    pNode1 = pNode1->m_pNext;
    pNode2 = pNode2->m_pNext;
  }
  return pNode1;
}  

8. 判断一个链表是否存在环,如果存在求进入环中的第一个节点

首先判断是否存在环,若不存在结束。在环中的一个节点处断开(当然函数结束时不能破坏原链表),这样就形成了两个相交的单链表,求进入环中的第一个节点也就转换成了求两个单链表相交的第一个节点。

ListNode* GetFirstNodeInCircle(ListNode * pHead){
  if(pHead == NULL || pHead->m_pNext == NULL)
    return NULL;

  ListNode * pFast = pHead;
  ListNode * pSlow = pHead;
  while(pFast != NULL && pFast->m_pNext != NULL){
    pSlow = pSlow->m_pNext;
    pFast = pFast->m_pNext->m_pNext;
    if(pSlow == pFast)
      break;
    }
    if(pFast == NULL || pFast->m_pNext == NULL)
      return NULL;

  // 将环中的此节点作为假设的尾节点,将它变成两个单链表相交问题
  ListNode * pAssumedTail = pSlow;
  ListNode * pHead1 = pHead;
  ListNode * pHead2 = pAssumedTail->m_pNext;

  ListNode * pNode1, * pNode2;
  int len1 = 1;
  ListNode * pNode1 = pHead1;
  while(pNode1 != pAssumedTail){
    pNode1 = pNode1->m_pNext;
    len1++;
  }

  int len2 = 1;
  ListNode * pNode2 = pHead2;
  while(pNode2 != pAssumedTail){
    pNode2 = pNode2->m_pNext;
    len2++;
  }

  pNode1 = pHead1;
  pNode2 = pHead2;

  // 先对齐两个链表的当前结点,使之到尾节点的距离相等
  if(len1 > len2){
    int k = len1 - len2;
    while(k--)
      pNode1 = pNode1->m_pNext;
  } else {
    int k = len2 - len1;
    while(k--)
      pNode2 = pNode2->m_pNext;
  }
  while(pNode1 != pNode2){
    pNode1 = pNode1->m_pNext;
    pNode2 = pNode2->m_pNext;
  }
  return pNode1;
}

9. 给出一个链表头指针 pHead 和一节点指针 pToBeDeleted,O(1) 时间复杂度删除节点 pToBeDeleted

对于删除节点,我们普通的思路就是让该节点的前一个节点指向该节点的下一个节点,这种情况需要遍历找到该节点的前一个节点,时间复杂度为 O(n)。对于链表,链表中的每个节点结构都是一样的,所以我们可以把该节点的下一个节点的数据复制到该节点,然后删除下一个节点即可。要注意最后一个节点的情况,这个时候只能用常见的方法来操作,先找到前一个节点,但总体的平均时间复杂度还是 O(1)。

void Delete(ListNode * pHead, ListNode * pToBeDeleted){
  if(pToBeDeleted == NULL)
    return;

  if(pToBeDeleted->m_pNext != NULL){

    // 将下一个节点的数据复制到本节点,然后删除下一个节点
    pToBeDeleted->m_nKey = pToBeDeleted->m_pNext->m_nKey;
    ListNode * temp = pToBeDeleted->m_pNext;
    pToBeDeleted->m_pNext = pToBeDeleted->m_pNext->m_pNext;
    delete temp;
  } else { // 要删除的是最后一个节点

    // 链表中只有一个节点的情况  
    if(pHead == pToBeDeleted){
      pHead = NULL;
      delete pToBeDeleted;
    } else {
      ListNode * pNode = pHead;

      // 找到倒数第二个节点
      while(pNode->m_pNext != pToBeDeleted)
        pNode = pNode->m_pNext;
      pNode->m_pNext = NULL;
      delete pToBeDeleted;
    }
  }
}

10.合并两个有序链表

定义val1和val2分别指向两个有序链表的第一个结点,只要两个有序链表有一个遍历到了尾结点就结束比较,如果val1< val2,则让p指向l1,并将l1的指针后移,反之,让p指向l2,并将l2指针后移.最后补足剩余多出来的元素。

ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode dummy(0);
        ListNode* p = &dummy;
        while(l1&&l2){
            int val1 = l1->val;
            int val2 = l2->val;
            if(val1<val2){
                p->next = l1;
                p = l1;
                l1 = l1->next;
            }else{
                p->next = l2;
                p = l2;
                l2 = l2->next;
            }
        }
        if(l1){
            p->next = l1;
        }else if(l2){
            p->next = l2;
        }
        return dummy.next;
    }

11.请判断一个链表是否为回文链表。

第一步:两个指针都从头出发,快指针每次两步,慢指针每次一步,这样快指针的下一个或下下个为空时,慢指针就在链表正中间那个节点了(如果链表有偶数个节点则在靠近头那侧的)。
第二步:从慢指针的下一个开始,把后面的链表都反转(Reverse Linked List),
第三步:然后我们再从头和从尾同时向中间前进,就可以判断该链表是不是回文了。

bool isPalindrome(ListNode* head) {
        if(head==NULL||head->next==NULL){
            return true;
        }
        ListNode* mid = findMid(head);
        mid->next = reverse(mid->next);
        mid = mid->next;
        while(head!=NULL&&mid!=NULL){
            if(head->val!=mid->val){
                return false;
            }
            head = head->next;
            mid = mid->next;
        }
        return true;
    }
    ListNode* findMid(ListNode* now){
        ListNode* slow = now;
        ListNode* fast = now->next;
        while(fast!=NULL&&fast->next!=NULL){
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
    ListNode* reverse(ListNode* now){
        ListNode* pre = NULL;
        while(now!=NULL){
            ListNode* temp = now->next;
            now->next = pre;
            pre = now;
            now = temp;
        }
        return pre;
    }

12.删除排序链表中的重复元素

直接从第一个结点开始遍历整个链表,若当前p所指元素与下一个元素相等,则直接后移指针p=p->next-next,让p指向下下一个元素,如果不相等,则记录下当前结点。

 ListNode* deleteDuplicates(ListNode* head) {
        if(!head){
            return head;
        }//真正写的时候,链表为空一定不能漏了
        int val = head->val;
        ListNode* p = head;
        while(p&&p->next){
            if(p->next->val!=val){
                val = p->next->val;
                p = p->next;
            }else{
                ListNode* n = p->next->next;
                p->next = n;
            }
        }
        return head;
    }
阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hy971216/article/details/80691859
个人分类: 链表、栈、队列
上一篇1060 爱丁顿数(25)(25 分)
下一篇374. 猜数字大小
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭