Leetcode刷题笔记Day2:链表

81 阅读5分钟

反转链表

  • 要点:虚拟头结点
  • 三种方式:头插法、迭代法和递归法
  • 力扣题目链接
  • 头插法:
ListNode* reverseList(ListNode* head) {
    if(head==nullptr) return nullptr;
    ListNode *vhead=new ListNode(); //虚拟头结点
    ListNode *cur=head;             //工作结点
    ListNode *tmp1, *tmp2;
    do{                     //头插法
        tmp1=cur->next;     //待插入结点链表下一个,保证不断链
        tmp2=vhead->next;   //虚拟头结点链表下一个,保证不断链
        cur->next=tmp2;
        vhead->next=cur;
        cur=tmp1;
    }while(tmp1!=nullptr);
    return vhead->next;
    //或把上面那句改成,但释放不释放空间其实不要紧
    //cur=vhead->next;
    //delete vhead;
    //return cur;
}
  • 迭代法(理解递归法的基础):
ListNode* reverseList(ListNode* head) {
    ListNode* temp;         //保存cur的下一个节点
    ListNode* cur=head;
    ListNode* pre=NULL;
    while(cur){
        temp = cur->next;   //保存一下cur的下一个节点
        cur->next = pre;    //翻转操作
        //更新pre和cur指针
        pre=cur;
        cur=temp;
    }
    return pre;
}
  • 递归法:
ListNode* reverseList(ListNode* head) {
    if(!head || !head->next) return head;
    //newHead就是head->next
    ListNode* newHead=reverseList(head->next);
    head->next->next=head;
    head->next=nullptr;
    return newHead;
}
  • 算法复杂度:时间复杂度为O(n),空间复杂度为O(1)(递归为O(n))

两两交换链表中的节点

  • 要点:理解反转链表这道题处理差不多,有递归和非递归写法
  • 力扣题目链接
  • 非递归解法:
ListNode* swapPairs(ListNode* head) {
    ListNode *vhead=new ListNode();
    vhead->next=head;
    ListNode *cur=vhead;
    while(cur->next!=nullptr&&cur->next->next!=nullptr){
        ListNode *tmp1=cur->next->next->next;   //不断链
        ListNode *tmp=cur->next;
        cur->next=tmp->next;
        cur->next->next=tmp;
        cur->next->next->next=tmp1;
        cur=cur->next->next;                    //后移两格
    }
    return vhead->next;
}
  • 递归解法:
ListNode* swapPairs(ListNode* head) {
    if(head == nullptr || head->next == nullptr) return head;
    ListNode* newHead = head->next;
    head->next = swapPairs(newHead->next);
    newHead->next = head;
    return newHead;
}
  • 算法复杂度:时间复杂度为O(n),空间复杂度为O(1)(递归为O(n))

删除倒数第N个节点

ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode *vhead=new ListNode();
    vhead->next=head;
    ListNode *fast=vhead, *slow=vhead;
    for(int i=0; i<n; i++) fast=fast->next;
    while(fast->next!=nullptr){
        slow=slow->next;
        fast=fast->next;
    }
    slow->next=slow->next->next; //删除结点
    return vhead->next;
}
  • 算法复杂度:时间复杂度为O(n),空间复杂度为O(1)

链表相交

  • 两种方法:右对齐法和交替遍历法
  • 交替遍历法正确性证明:

情况一:两个链表相交

链表 headAheadB 的长度分别是 mn。假设链表 headA 的不相交部分有 a 个节点,链表 headB 的不相交部分有 b 个节点,两个链表相交的部分有 c 个节点,则有 a+c=m, b+c=na+c=m,\ b+c=n

  • 如果a=ba=b,则两个指针会同时到达两个链表相交的节点,此时返回相交的节点;
  • 如果aba\neq b,则指针 pA 会遍历完链表 headA,指针 pB 会遍历完链表 headB,两个指针不会同时到达链表的尾节点,然后指针 pA 移到链表 headB 的头节点,指针 pB 移到链表 headA 的头节点,然后两个指针继续移动,在指针 pA 移动了a+c+ba+c+b次、指针 pB 移动了b+c+ab+c+a次之后,两个指针会同时到达两个链表相交的节点,该节点也是两个指针第一次同时指向的节点,此时返回相交的节点。

情况二:两个链表不相交

链表 headAheadB 的长度分别是 mn。考虑当m=nm=nmnm\neq n的情况:

  • 如果m=nm=n,则两个指针会同时到达两个链表的尾节点,然后同时变成空值,返回null
  • 如果mnm\neq n,则由于两个链表没有公共节点,两个指针也不会同时到达两个链表的尾节点,因此两个指针都会遍历完两个链表,在指针 pA 移动了m+nm+n次、指针 pB 移动了n+mn+m次之后,两个指针会同时变成空值,返回null
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    int size_a=getSize(headA);
    int size_b=getSize(headB);
    ListNode *a=headA, *b=headB;
    if(size_a>size_b)
        for(int i=0;i<size_a-size_b;i++) a=a->next;
    else if(size_a<size_b)
        for(int i=0;i<size_b-size_a;i++) b=b->next;
​
    while(a!=b && a!=nullptr){
        a=a->next;
        b=b->next;
    }
    return a;
}
int getSize(ListNode *head){
    int size=0;
    while(head!=nullptr){
        size++;
        head=head->next;
    }
    return size;
}
  • 交替遍历法:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    if(headA==nullptr || headB==nullptr) return nullptr;
    ListNode *pa=headA, *pb=headB;
    while(pa!=pb){
        pa = pa==nullptr ? headB : pa->next;
        pb = pb==nullptr ? headA : pb->next;
    }
    return pa;
}
  • 算法复杂度:时间复杂度为O(m+n),空间复杂度为O(1)

环形链表II

  • 要点:判断是否有环,并且找出入口在哪儿

  • 双指针法正确性证明要点:参考题解

    • 判断无环:fast指针走过链表末端,说明链表无环,此时直接返回null
    • 有环的情况下,快慢指针一定相遇
    • 找入口(第二次相遇):head指针到入口处的步数=slow指针继续走到入口的步数
ListNode *detectCycle(ListNode *head) {
    unordered_set<ListNode*> visited;
    while(head!=nullptr){
        if(visited.count(head)) return head;
        visited.insert(head);
        head=head->next;
    }
    return nullptr;
}
  • 双指针法:
ListNode *detectCycle(ListNode *head) {
    ListNode *fast=head, *slow=head;
    while(true){
        if(fast==nullptr || fast->next==nullptr) return nullptr;
        fast=fast->next->next;
        slow=slow->next;
        if(fast==slow) break;
    }           //第一次相遇判断有环
    fast=head;  //构造第二次相遇找到入口
    while(slow!=fast){
        slow=slow->next;
        fast=fast->next;
    }
    return fast;
}
  • 算法复杂度:时间复杂度为O(n),快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n;空间复杂度为O(1)(直接法为O(n))

参考资料

[1] 代码随想录

[2] Leetcode题解