链表

103 阅读5分钟

链表类题目的常用题型,及解题思路

链表反转

JZ24 反转链表

经典思路:3个指针pre,cur,nex【按最初的链表顺序】,最后return pre。

key-point在于:从当前结点到下一结点时, 不是nex = nex->next,而是nex = cur->next,如果用前者的话就需要考虑循环中nex为null的情况,增加了问题的复杂性。

关键代码为:

ListNode *pre = 0, *cur = pHead, *nex = 0;
while(cur)
{
    nex = cur->next;
    cur->next = pre;
    pre = cur;
    cur = nex;
}
return pre;

注意返回的是pre而不是nex【nex此时已为0】

在看过一些题解之后,get了另一种思路,即用vector保存链表的所有指针,然后利用c++中的reverse()函数完成反转,之后再构建链表。此时,只是断开了链表,链表的每个结点并没有丢失。

if(pHead == 0) return pHead;
vector<ListNode*> ptr;
ListNode* p = pHead;
while(p)
{
    ptr.push_back(p);
    p = p->next;
}
reverse(ptr.begin(),ptr.end());
//连接新链表
ListNode* head = ptr[0];
ListNode* cur = head;
for(vector<ListNode*>::iterator it = ptr.begin()+1; it != ptr.end(); it++)
{
    cur->next = *it;
    cur = cur->next;
}
cur->next = 0;
return head;

合并两个有序链表

思路同归并排序

JZ25 合并两个排序的链表

首先是自己写的代码:

ListNode *cur1 = pHead1, *cur2 = pHead2;
ListNode *head = new ListNode(-1); //头结点,值为-1
ListNode *cur = head;
if(!pHead1) return pHead2;
if(!pHead2) return pHead1;
while(cur1 && cur2)
{
    if(cur1->val < cur2->val)
    {
         ListNode* tmp = new ListNode(cur1->val);
         tmp->next = 0;
         cur->next = tmp;
         cur = tmp;
         cur1 = cur1->next;
     }
     else
     {
         ListNode* tmp = new ListNode(cur2->val);
         tmp->next = 0;
         cur->next = tmp;
         cur = tmp;
         cur2 = cur2->next;
     }
}
while(cur1)
{
     ListNode* tmp = new ListNode(cur1->val);
     tmp->next = 0;
     cur->next = tmp;
     cur = tmp;
     cur1 = cur1->next;
}
while(cur2)
{
    ListNode* tmp = new ListNode(cur2->val);
    tmp->next = 0;
    cur->next = tmp;
    cur = tmp;
    cur2 = cur2->next;
}
return head->next;

ps:主要思路是自己的,涉及一些小的语法也在网上百度了一下。

key point

  1. 头结点。题目中所给和要求返回的均是不带头结点的单链表,从理论上来说,不带头结点的单链表在循环中不占优势(准确的说是我不知道该怎样处理第一个结点和最后一个结点),所有一开始就麻了爪。解决方案是创建一个带头结点的单链表,在完成所有操作之后对头结点进行处理。

  2. new语法的使用。new typename(value)

代码优化

  1. 不再创建新结点,而是将已经存在的结点用指针相连出第三个有序链表:
ListNode *cur1 = pHead1, *cur2 = pHead2;
ListNode *head = new ListNode(-1); //头结点,值为-1
ListNode *cur = head;
if(!pHead1) return pHead2;
if(!pHead2) return pHead1;
while(cur1 && cur2)
{
    if(cur1->val < cur2->val)
    {
        cur->next = cur1;
        cur1 = cur1->next;
        cur = cur->next;
    }
    else
    {
         cur->next = cur2;
         cur2 = cur2->next;
         cur = cur->next;
     }
}
while(cur1)
{
    cur->next = cur1;
    cur = cur->next;
    cur1 = cur1->next;
}
while(cur2)
{
    cur->next = cur2;
    cur = cur->next;
    cur2 = cur2->next;
}
return head->next;
  1. 第一个while循环中,cur = cur->next在两个分支中均有执行,可以提出来
ListNode *cur1 = pHead1, *cur2 = pHead2;
ListNode *head = new ListNode(-1); //头结点,值为-1
ListNode *cur = head;
if(!pHead1) return pHead2;
if(!pHead2) return pHead1;
while(cur1 && cur2)
{
    if(cur1->val < cur2->val)
    {
        cur->next = cur1;
        cur1 = cur1->next;
     }
     else
     {
         cur->next = cur2;
         cur2 = cur2->next;
     }
     cur = cur->next;
}
while(cur1)
{
    cur->next = cur1;
    cur = cur->next;
    cur1 = cur1->next;
}
while(cur2)
{
    cur->next = cur2;
    cur = cur->next;
    cur2 = cur2->next;
}
return head->next;

两个链表的公共结点

JZ52 两个链表的第一个公共结点

思路1

两个指针同时开始移动且速度相同,cur1走完自己的链表1后,走链表2;cur2走完自己的链表2后,走链表1,这样两个指针所走的路程都一样,当两个指针相等时,就找到了公共结点。

ListNode *cur1 = pHead1, *cur2 = pHead2;
while(cur1 != cur2)
{
    cur1 = (cur1 == NULL) ? pHead2 : cur1->next;
    cur2 = (cur2 == NULL) ? pHead1 : cur2->next;
}
return cur1;

思路2

两个链表长度相等时,同时遍历可找到公共结点;若长度不等,则先遍历长的那个链表,直到二者长度相等

ListNode *cur1 = pHead1, *cur2 = pHead2;
//length
int len1 = 0, len2 = 0;
while(cur1)
{
    len1++;
    cur1 = cur1->next;
}
while(cur2)
{
    len2++;
    cur2 = cur2->next;
}
        
cur1 = pHead1, cur2 = pHead2;
while(len1 < len2)
{
     cur2 = cur2->next;
     len2--;
}
while(len1 > len2)
{
     cur1 = cur1->next;
     len1--;
}
//len1 == len2
while(cur1 && cur2)
{
     if(cur1->val == cur2->val) break;
     cur1 = cur1->next;
     cur2 = cur2->next;
}
return cur1;

链表中环的入口结点

JZ23 链表中环的入口结点

快慢指针法 快指针一次两步,慢指针一次一步,快慢指针相遇时有环。

LinkNode *fast = pHead, *slow = pHead;
while(fast)
{
    fast = fast->next;
    if(fast)
    {
        fast = fast->next;
        slow = slow->next;
    }
    if(fast == slow)
    {
        fast = pHead;
        while(fast != slow)
        {
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
     }
}
return NULL; 

集合set

利用find()函数

set<ListNode*>st;
LisNode* cur = pHead;
while(cur)
{
    if(st.find(cur) != st.end()) return cur;
    st.insert(cur);
    cur = cur->next;
}
return NULL;

利用count()函数

set<ListNode*>st;
LisNode* cur = pHead;
while(cur)
{
    if(st.count(cur)) return cur;
    st.insert(cur);
    cur = cur->next;
}
return NULL;

链表去重

JZ76 删除链表中重复的结点

看了一下题解,发现很多方法只对连续多个结点相同的情况有效,像1->2->3->4->2这种情况,可能无法识别。所以还是采用我自己的“笨”方法,用数组下标和值的关系来记录某个结点的出现次数(当然根据题目,0val10000\leq val \leq 1000,所需数组长度还不算大)。此外,不对原链表进行操作,而是新建一个链表。

ListNode* deleteDuplication(ListNode* pHead) {
        if(!pHead || pHead->next == NULL) return pHead;
        ListNode* dummy = new ListNode(-1);
        ListNode* tail = dummy;
        ListNode *cur = pHead;
        int num[1001] = {0};
        while(cur)
        {
            num[cur->val]++;
            cur = cur->next;
        }
        cur = pHead;
        while(cur)
        {
            if(num[cur->val] == 1)//非重复结点,加入新链表
            {
                ListNode* tmp = new ListNode(cur->val);
                tail->next = tmp;
                tail = tail->next;
                cur = cur->next;
            }
            else
            {
                cur = cur->next;
            }
        }
        return dummy->next;
    }