LeetCode——链表专题

LeetCode——链表专题

19. 删除链表的倒数第N个节点

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。

【思路】双指针

  • 头节点有可能被删除,创建虚拟头节点
  • 采用双指针,快指针向前走n步停止,慢指针从头和快指针一起走,当快指针走到链表最后一个节点时,慢指针走到倒数第n+1个节点
  • 删除慢指针当前节点的下一个节点
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        auto dummy = new ListNode(-1);  // 创建虚拟头结点
        dummy->next = head;
        auto fast = dummy, slow = dummy;
        while(n--)
            fast = fast->next;  // 快指针走n步
        while(fast->next){
            fast = fast->next;
            slow = slow->next;
        }
        // 当快指针走到最后一个节点时,慢指针在倒数第n+1的节点
        slow->next = slow->next->next;
        return dummy->next;
    }
};
/*先扫描一遍链表,求出链表长度,走到倒数第k+1的位置,即走n-k-1步,删掉下一个节点*/
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int k) {
        auto dummy = new ListNode(-1);
        dummy->next = head;  // 创建虚拟头结点
        int n = 0;  // 记录链表的长度
        for(auto p = dummy; p; p = p->next) n++;
        auto p = dummy;
        for(int i = 0; i < n - k - 1; i++) p = p->next;  // 走到倒数第k+1个点
        p->next = p->next->next;
        return dummy->next;
    }
};
// 时间复杂度均为O(n)
206. 反转链表

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

【思路】

方法一:迭代

  • 用一个额外变量记录当前节点的前驱结点,遍历一遍链表,链表不空时,当前节点的下一个点指向前驱,前驱结点后移,当前点往后移(需要记录当前点的下一个点),最后前驱结点即为反转链表的头结点。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *pre = NULL, *cur = head;
        while(cur){
            auto next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

方法二:递归

  • 1->2->3->4->5->NULL 递归后,5->4->3->2->NULL,再把2指向1,1指向空,递归后的尾指针即为新链表的表头
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(!head || !head->next) return head;
        auto tail = reverseList(head->next);  // tail:5
        head->next->next = head;  // 2->1
        head->next = NULL;  // 1->NULL
        return tail;
    }
};
148. 排序链表

O ( n l o g n ) O(n log n) O(nlogn) 时间复杂度和常数级空间复杂度下,对链表进行排序。

示例 :

输入: 4->2->1->3
输出: 1->2->3->4

输入: -1->5->3->4->0
输出: -1->0->3->4->5

【思路】只能用自底向上的非递归迭代式归并排序

  • 求链表长度

    • 第一次,将整个区间分成连续的若干段,每段长度是2: [ a 0 , a 1 ] , [ a 2 , a 3 ] , [ a 2 , a 3 ] , … , [ a n − 1 , a n − 1 ] [a_0,a_1],[a_2,a_3],[a_2,a_3],…,[a_{n−1},a_{n−1}] [a0,a1],[a2,a3],[a2,a3],,[an1,an1], 然后将每一段内排好序,小数在前,大数在后;

    • 第二次,将整个区间分成连续的若干段,每段长度是4: [ a 0 , … , a 3 ] , [ a 4 , … , a 7 ] , … , [ a n − 4 , … , a n − 1 ] [a_0,…,a_3],[a_4,…,a_7],…,[a_{n−4},…,a_{n−1}] [a0,,a3],[a4,,a7],,[an4,,an1],然后将每一段内排好序,这次排序可以利用之前的结果,相当于将左右两个有序的半区间合并,可以通过一次线性扫描来完成;

    • 依此类推,直到每段小区间的长度大于等于 n n n 为止;

    • 另外,当 n n n 不是2的整次幂时,每次迭代只有最后一个区间会比较特殊,长度会小一些,遍历到指针为空时需要提前结束。

时间复杂度分析:整个链表总共遍历 l o g n logn logn 次,每次遍历的复杂度是 O ( n ) O(n) O(n),所以总时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)

空间复杂度分析:整个算法没有递归,迭代时只会使用常数个额外变量,所以额外空间复杂度是 O ( 1 ) O(1) O(1)
LeetCode——链表专题_第1张图片

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        int n = 0;
        for(auto p = head; p; p = p->next) n++;  // 求链表长度
        auto dummy = new ListNode(-1);  // 创建虚拟头结点
        dummy->next = head;
        for(int i = 1; i < n; i *= 2){  // 从1开始枚举划分链表长度
            auto cur = dummy;  // 从第一个结点开始每次从前往后扫描每一个长度为2i的段
            for(int j = 1; j + i <= n; j += 2*i){  // 每次枚举所有相应两个长度为i的区间
                auto p = cur->next, q = p;  // 找到当前两个区间的头结点
                for(int k = 0; k < i; k++) q = q->next;  // q为下一段区间的头结点
                // 二路归并
                int l = 0, r = 0;
                while(l < i && r < i && p && q){  // 最后一段可能不全,要保证q不为空
                    if(p->val <= q->val) cur = cur->next = p, p = p->next, l++;
                    else cur = cur->next = q, q = q->next, r++;
                }
                // 将剩余链表接上
                while(l < i && p) cur = cur->next = p, p = p->next, l++;
                while(r < i && q) cur = cur->next = q, q = q->next, r++;
                cur->next = q;  // 下一段开始的位置
            }
        } 
        return dummy->next;
    }
};

你可能感兴趣的:(LeetCode)