LeetCode 题解随笔:链表篇

目录

一、链表基础操作

203.移除链表元素

707.设计链表

23. 合并K个升序链表[*]

二、双指针法

206.反转链表

24. 两两交换链表中的节点

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

面试题 02.07. 链表相交

142.环形链表II

三、递归解决反转链表问题

 206. 反转链表

反转前n个节点

92. 反转链表 II[*] 

25. K 个一组翻转链表[*]


一、链表基础操作

203.移除链表元素

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* removeElements(ListNode* head, int val) {
        //设置虚拟头节点
        ListNode* newHead = new ListNode();
        newHead->next = head;
        ListNode* p = newHead;
        while (p->next != NULL)
        {
            if (p->next->val == val) {
                //删除链表记得释放空间
                ListNode* temp = p->next;
                p->next = p->next->next;
                delete temp;
            }
            else {
                p = p->next;
            }
        }
        head = newHead->next;
        //释放虚拟头节点
        delete newHead;
        return head;
    }
};

本题对于打好链表基础非常重要,尤其是最开始链表的定义。其包括数值、next指针(仍为链表类型)和构造函数。

本体采用了虚拟头节点方法保持了删除链表第一个元素时的操作与删除其它元素的操作一致。须注意一下几点:

1.删除元素记得释放空间;

2.返回head指针要让head=newHead->next;

3.记得释放虚拟头节点的空间。

707.设计链表

class MyLinkedList {
public:
    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) {}
    };

    MyLinkedList() {
        //虚拟头节点
        this->m_Head = new ListNode();
        this->m_Size = 0;
    }

    int get(int index) {
        if (index < 0 || index >= this->m_Size) {
            return -1;
        }
        ListNode* p = this->m_Head;
        for (int i = 0; i <= index; i++) {
            p = p->next;
        }
        int res = p->val;
        return res;
    }

    void addAtHead(int val) {
        ListNode* elem = new ListNode(val);
        elem->next = this->m_Head->next;
        this->m_Head->next = elem;
        this->m_Size++;
    }

    void addAtTail(int val) {
        ListNode* elem = new ListNode(val);
        ListNode* p = this->m_Head;
        while (p->next != NULL) {
            p = p->next;
        }
        p->next = elem;
        this->m_Size++;
    }

    void addAtIndex(int index, int val) {
        if (index > this->m_Size) {
            return;
        }
        else if (index <= 0) {
            addAtHead(val);
            this->m_Size++;
        }
        else {
            ListNode* elem = new ListNode(val);
            ListNode* p = this->m_Head;
            for (int i = 0; i < index; i++) {
                p = p->next;
            }
            elem->next = p->next;
            p->next = elem;
            this->m_Size++;
        }
    }

    void deleteAtIndex(int index) {
        if (index >= 0 && index < this->m_Size) {
            ListNode* p = this->m_Head;
            while (index--) {
                p = p->next;
            }
            ListNode* temp = p->next;
            p->next = temp->next;
            delete temp;
            this->m_Size--;
        }
        else {
            return;
        }
    }

    ~MyLinkedList() {
        if (this->m_Head != NULL) {
            delete this->m_Head;
            this->m_Head = NULL;
        }
    }

private:
    ListNode* m_Head;
    int m_Size;
};

都是链表的基础操作,添加和删除不要忘记修改size属性。

23. 合并K个升序链表[*]​​​​​​​

// 小顶堆
    struct MyCompare {
        bool operator() (const ListNode* p1, const ListNode* p2) {
            return p1->val > p2->val;
        }
    };
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode* dummy_head = new ListNode(0);
        ListNode* p = dummy_head;
        priority_queue<ListNode*, vector<ListNode*>, MyCompare> pq_list;
        for (ListNode* list : lists) {
            if (list)    pq_list.push(list);
        }
        while (!pq_list.empty()) {
            p->next = new ListNode(pq_list.top()->val);
            p = p->next;
            if(pq_list.top()->next)     pq_list.push(pq_list.top()->next);
            pq_list.pop();
        }
        return dummy_head->next;
    }

本体需要熟练掌握自定义数据类型的大顶堆和小顶堆的构造方式。 


二、双指针法

206.反转链表

class MyLinkedList {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = NULL;
        ListNode* cur = head;
        ListNode* temp = NULL;
        while (cur != NULL) {
            //保存下一个节点
            temp = cur->next;
            //进行翻转
            cur->next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

 用两个指针完成反转,过程参考上图(来源:代码随想录)。初始cur指向头节点,pre指向NULL。利用temp保存下一个节点的信息后,完成当前两个节点的反转。

24. 两两交换链表中的节点

class MyLinkedList {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* newHead = new ListNode();
        newHead->next = head;
        ListNode* p = newHead;
        while (p->next != NULL && p->next->next != NULL) {
            ListNode* second = p->next;
            ListNode* first = second->next;
            p->next = first;
            second->next = first->next;
            first->next = second;
            p = second;
        }
        return newHead->next;
    }
};

理解虚拟头指针的作用,一旦定义了虚拟头指针,返回值最好为newHead->next而不是原来的head。交换时按照从左到右的顺序依次进行即可。

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

class MyLinkedList {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        int count = 0;
        ListNode* newHead = new ListNode();
        newHead->next = head;
        ListNode* fast = newHead;
        ListNode* slow = newHead;
        //快慢指针相差n
        for (; count < n; count++){
            fast = fast->next;
        }
        //快指针指向链表最后一个元素,慢指针此时指向待删除元素的前一个元素
        while (fast->next != NULL) {
            fast = fast->next;
            slow = slow->next;
        }
        ListNode* temp = slow->next;
        slow->next = slow->next->next;
        delete temp;
        return newHead->next;
    }
};

这是双指针法的一个非常重要的应用。让快指针比慢指针领先n个位置,再同时移动两个指针,快指针移动至链表最后一个元素时,慢指针就在待删除元素的前一个元素。从而可以利用一次遍历实现删除。

面试题 02.07. 链表相交

class MyLinkedList {
public:
    ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
        //求链表A、B的长度
        int sizeA = 0;
        int sizeB = 0;
        ListNode* pA = headA;
        while (pA != NULL) {
            pA = pA->next;
            sizeA++;
        }
        ListNode* pB = headB;
        while (pB != NULL) {
            pB = pB->next;
            sizeB++;
        }
        //让pA指向最长链表
        pA = headA;
        pB = headB;
        if (sizeB > sizeA) {
            swap(pA, pB);
            swap(sizeA, sizeB);
        }
        //对齐并开始寻找重合点
        int deltaL = sizeA - sizeB;
        while (deltaL--)
        {
            pA = pA->next;
        }
        while (pA != NULL) {
            if (pA == pB) {
                return pA;
            }
            pA = pA->next;
            pB = pB->next;
        }
        return NULL;
    }
};

先求两个链表的长度,末端对齐后,从对齐处开始查找重合点。重合意味着地址相同。

头节点指向的就是第一个元素,head->next指向的是第二个元素,这要和虚拟节点区分清楚。

142.环形链表II

class MyLinkedList {
public:
    ListNode* detectCycle(ListNode* head) {
        ListNode* newHead = new ListNode();
        newHead->next = head;
        ListNode* fast = newHead;
        ListNode* slow = newHead;
        while (fast->next != NULL && fast->next->next != NULL) {
            fast = fast->next->next;
            slow = slow->next;
            if (slow == fast) {
                ListNode* index1 = newHead;
                ListNode* index2 = fast;
                while (index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index1;
            }
        }
        return NULL;
    }
};

本题技巧性较强,快指针每次走两步,慢指针每次走一步,若存在环,入环后快指针总能逼近慢指针。由于快指针每次走两步,需要以fast->next != NULL && fast->next->next != NULL作为循环条件!

入口位置的确定可以通过下图来说明(图片来源:代码随想录)

142环形链表2

 相遇时两个指针走过的路程存在如下关系:2(x+y)=x+n*(y+z)+y

化简可得环形入口节点位置x=(n-1)y+nz。

这个式子说明,若定义一个index1在头节点,index2在相遇节点,头节点移动到x位置时,index2运行了n次z路程和(n-1)次y路程,也正好到达环形的入口位置


三、递归解决反转链表问题

 206. 反转链表

ListNode* reverseList(ListNode* head) {
        // 无节点或只有一个结点
        if (!head || !head->next)  return head;
        return reverse(head);
    }
    ListNode* reverse(ListNode* node) {
        // 递归返回条件
        if (node->next == NULL) {
            return node;
        }
        ListNode* last_node = reverse(node->next);
        node->next->next = node;
        node->next = NULL;
        return last_node;
    }

使用递归时,不要在脑海中模拟递归的过程,而要借助递归的特性简化问题。如本题执行完第一次ListNode* last_node = reverse(node->next)后,本问题就变成了(来源:labuladong):

 在这个链表的基础上,进行递归的后序操作就比较好理解。把head设为head->next->next,再把head->next置空,返回last即为头结点。

反转前n个节点

ListNode* successor = new ListNode(0);
    ListNode* reverseList(ListNode* head, int n) {
        // 无节点或只有一个结点
        if (!head || !head->next)  return head;
        return reverseN(head, n);
    }
    ListNode* reverseN(ListNode* node, int n) {
        // 递归返回条件
        if (n == 1) {
            successor = node->next;
            return node;
        }
        ListNode* last_node = reverseN(node->next, n - 1);
        node->next->next = node;
        node->next = successor;
        return last_node;
    }

92. 反转链表 II[*] 

ListNode* successor = new ListNode(0);
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        if (left == right)   return head;
        // 相当于反转前n个节点
        if (left == 1) {
            return reverseN(head, right);
        }
        head->next = reverseBetween(head->next, left - 1, right - 1);
        return head;
    }
    ListNode* reverseN(ListNode* node, int n) {
        // 递归返回条件
        if (n == 1) {
            successor = node->next;
            return node;
        }
        ListNode* last_node = reverseN(node->next, n - 1);
        node->next->next = node;
        node->next = successor;
        return last_node;
    }

本题思路借鉴了前一道题目:反转链表的前n个节点。若left为1,该问题等价于反转前right个节点;若left不唯一,继续递归,递归参数里left变为left-1,right变为right-1,从而直到实现可以为转化为反转链表前n个元素的问题。

25. K 个一组翻转链表[*]

ListNode* reverseKGroup(ListNode* head, int k) {
        ListNode* a = head;
        ListNode* b = head;
        for (int i = 0; i < k; i++) {
            // 长度不足k,不需要翻转了
            if (b == NULL)    return head;
            b = b->next;
        }
        // 翻转a、b区间内的链表,每次都返回翻转后的头结点
        ListNode* newhead = reverse(a, b);
        // 当前区间反转后的最后一个节点,应当指向下一段的头结点
        a->next = reverseKGroup(b, k);
        // 最后一次return时,返回的是第一个区间的头结点
        return newhead;
    }
    // 反转区间[a,b)内的元素
    ListNode* reverse(ListNode* a, ListNode* b) {
        ListNode* pre; 
        ListNode* cur;
        ListNode* nxt;
        pre = NULL;
        cur = a;
        nxt = a;
        while (nxt != b) {
            nxt = cur->next;
            cur->next = pre;
            pre = cur;
            cur = nxt;
        }
        // 返回头结点
        return pre;
    }

本题可以转化为子问题,k个一组地进行翻转,因此可以采用递归法。

注意翻转区间[a,b)内链表的函数返回值为该段链表翻转后的头结点【采用的方法为迭代法,过程如下图所示,来源:labuladong】,因此主函数可以直接返回newhead;而主函数在递归调用时,也返回每段的头结点,因此可以让上一段的尾结点指向下一段的头结点:a->next = reverseKGroup(b, k)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值