LeetCode系列之链表专题

1. 链表题目概述

https://leetcode-cn.com/tag/linked-list/

链表有多重类型:单链表、双链表、循环链表等。使用链表,需要注意头结点的用法。

2. 典型题目

2.1 删除链表中的节点

https://leetcode-cn.com/problems/delete-node-in-a-linked-list/

题目比较无趣,各种条件限制之后,取巧的办法几乎成了唯一。

值得学习的是,对于不能回溯到前一个节点的场景,和next节点的值进行交换,然后删除next,是个不错的方法。

2.2 环形链表

https://leetcode-cn.com/problems/linked-list-cycle/

快慢指针法

bool hasCycle(ListNode *head) {
    ListNode* tortoise = head;
    ListNode* bunny = head;

    while (tortoise && bunny && bunny->next) {
        tortoise = tortoise->next;
        bunny = bunny->next->next;
        if (tortoise == bunny) {
            return true;
        }
    }

    return false;
}

时间复杂度O(N),空间复杂度O(1)。

https://leetcode-cn.com/problems/linked-list-cycle-ii/

ListNode *detectCycle(ListNode *head) {
    ListNode* tortoise = head;
    ListNode* bunny = head;
    bool hasCycle = false;

    // 找到相遇点
    while (tortoise && bunny && bunny->next) {
        tortoise = tortoise->next;
        bunny = bunny->next->next;
        if (tortoise == bunny) {
            hasCycle = true;
            break;
        }
    }

    if (!hasCycle) return nullptr;

    // 从相遇点和头结点同时出发,相遇点即是环的入口点
    while (head && tortoise) {
        if (head == tortoise) {
            return tortoise;
        } else {
            head = head->next;
            tortoise = tortoise->next;
        }
    }

    return nullptr;
}

第二阶段的证明过程

时间复杂度O(N),空间复杂度O(1)。

2.3 排序链表

https://leetcode-cn.com/problems/sort-list/

ListNode* sortList(ListNode* head) {
    if (head == nullptr || head->next == nullptr) return head;

    // 快慢指针分割链表
    ListNode* fast = head->next;
    ListNode* slow = head;
    while (fast && fast->next) {
        fast = fast->next->next;
        slow = slow->next;
    }
    ListNode* h1 = head;
    ListNode* h2 = slow->next;
    slow->next = nullptr;

    // 分别排序
    h1 = sortList(h1);
    h2 = sortList(h2);

    // 合并排序结果
    return mergeLists(h1, h2);
}

ListNode* mergeLists(ListNode* l1, ListNode* l2) {
    ListNode dummy = ListNode(0);
    ListNode* node = &dummy;
    while (l1 && l2) {
        if (l1->val < l2->val) {
            node->next = l1;
            l1 = l1->next;
        } else {
            node->next = l2;
            l2 = l2->next;
        }
        node = node->next;
    }
    node->next = (l1 == nullptr ? l2 : l1);
    return dummy.next;
}

时间复杂度:O(N x logN),空间复杂度O(1)。

2.4 两数相加

https://leetcode-cn.com/problems/add-two-numbers/

2.5 回文链表

https://leetcode-cn.com/problems/palindrome-linked-list/

先放一个错误答案在这儿

bool isPalindrome(ListNode* head) {
    vector<int> ivec;
    ListNode* node = head;
    while (node) {
        ivec.push_back(node->val);
        node = node->next;
    }

    bool isPalindrome = true;
    int start = 0, end = ivec.size() - 1;
    while (start <= end) {
        if (ivec[start] != ivec[end]) {
            isPalindrome = false;
            break;
        }
        start++;
        end--;
    }
    return isPalindrome;
}

时间复杂度O(N),空间复杂度O(N)。

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

https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/

ListNode* deleteDuplicates(ListNode* head) {
    if (head == nullptr) return nullptr;

    int latestVal = head->val;
    ListNode* node = head->next;
    ListNode* prev = head;
    while (node) {
        if (node->val == latestVal) {
            node = deleteCurrentNode(node);
            prev->next = node;
        } else {
            latestVal = node->val;
            prev = node;
            node = node->next;
        }
    }
    return head;
}

ListNode* deleteCurrentNode(ListNode* node) {
    ListNode* next = node->next;
    delete node;
    return next;
}

时间复杂度O(N),空间复杂度O(1)。

2.7 合并两个有序链表

https://leetcode-cn.com/problems/merge-two-sorted-lists/

ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
    ListNode dummy = ListNode();
    ListNode* current = &dummy;
    while (l1 != nullptr && l2 != nullptr) {
        if (l1->val < l2->val) {
            current->next = l1;
            l1 = l1->next;
        } else {
            current->next = l2;
            l2 = l2->next;
        }
        current = current->next;
    }
    current->next = (l1 == nullptr ? l2 : l1);
    return dummy.next;
}

时间复杂度O(N),空间复杂度O(1)。

2.8 相交链表

https://leetcode-cn.com/problems/intersection-of-two-linked-lists/

注意写的时候,判断 pA == pB,而不是 pA->val == pB->val。看官方题解里,skipA和skipB与此相关。

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    ListNode* pA = headA;
    ListNode* pB = headB;
    bool isFirstA = true, isFirstB = true;
    while (pA && pB) {
        if (pA == pB) {
            return pA;
        }
        pA = pA->next;
        pB = pB->next;
        if (pA == nullptr && isFirstA) {
            pA = headB;
            isFirstA = false;
        }
        if (pB == nullptr && isFirstB) {
            pB = headA;
            isFirstB = false;
        }
    }
    return nullptr;
}

时间复杂度O(m+n),空间复杂度O(1)。

2.9 二进制链表转整数

https://leetcode-cn.com/problems/convert-binary-number-in-a-linked-list-to-integer/

int getDecimalValue(ListNode* head) {
    ListNode* node = head;
    int res = 0;
    while (node) {
        res = (res << 1) + node->val;
        node = node->next;
    }
    return res;
}

时间复杂度O(N),空间复杂度O(1)。

2.10 反转链表(常考题目)

https://leetcode-cn.com/problems/reverse-linked-list/

迭代版本

ListNode* reverseList(ListNode* head) {
    ListNode* prev = nullptr;
    ListNode* current = head;

    while (current != nullptr) {
        ListNode* next = current->next;
        current->next = prev;
        prev = current;
        current = next;
    }

    return prev;
}

时间复杂度O(N),空间复杂度O(1)。

递归版本

ListNode* reverseList(ListNode* head) {
    if (head == nullptr || head->next == nullptr) return head;

    ListNode* p = reverseList(head->next);
    head->next->next = head;
    head->next = nullptr;

    return p;
}

时间复杂度O(N),空间复杂度O(N)。

https://leetcode-cn.com/problems/reverse-linked-list-ii/

题目有额外要求,“一遍扫描”解决问题。看了自己很久以前的一次提交,虽然AC了,但是不符合这个要求。

 // TODO:不太常见的题目,稍后回来补充完整

 

面试实战中遇到了一个变形题目:链表元素三个一组,按组打包之后进行反转;不满三个的元素,单独打包成一组。如[1, 2, 3, 4, 5, 6, 7] => [7, 4, 5 ,6, 1, 2, 3]。

ListNode* reverse3Elements(ListNode* head) {
    if (head && head->next && head->next->next) {
        std::swap(head->val, head->next->next->val);
    } else if (head && head->next) {
        // 只有两个元素的时候,交换这两个元素
        std::swap(head->val, head->next->val);
    }
}

ListNode* reverseListEvery3Elements(ListNode* head) {
    ListNode* current = head;
    while (current) {
        reverse3Elements(current);
        if (current && current->next && current->next->next) {
            current = current->next->next->next;
        } else {
            break;
        }
    }
    return reverseList(head);
}

这里容易失误的地方是,[1, 2, 3, 4, 5, 6, 7, 8] => [7, 8, 4, 5 ,6, 1, 2, 3],所以说,在只有两个元素的时候,也要反转。

3. 总结

做算法,背单词

tortoise 乌龟

hare 野兔

bunny 可爱的小兔子

rabbit 泛指兔子

posted @ 2020-07-07 23:17  不写诗的诗人小安  阅读(126)  评论(0编辑  收藏  举报