Leetcode链表题目

以下为Datawhale Leetcode开源学习思路总结,以下代码均为Leetcode代码,但不一定是最优解,仅供参考学习。

链表基础

707. 设计链表

本题主要练习链表的增删查,在设计这个类时,应考虑代码是否可以复用,addAtHeadaddAtTail可以用addAtIndex表示,只要实现了addAtIndex,那么addAtHeadaddAtTail就只是其中的一个子类,那么明确以后我们开始设计,代码如下:

struct Node {
    Node(int value, Node* next = nullptr) : value(value), next(next) {}
    int value;
	Node* next;
};

class MyLinkedList {
public:
    MyLinkedList() = default;
    MyLinkedList(const MyLinkedList&) = delete;
    MyLinkedList(MyLinkedList&&) = delete;

    int get(int index) {
        Node* cur = head;
        if (index >= size || index < 0) return -1;
        while (index--)
            cur = cur->next;

        return cur->value;
    }
    
    void addAtHead(int val) {
        addAtIndex(0, val);
    }
    
    void addAtTail(int val) {
        addAtIndex(size, val);
    }
    
    void addAtIndex(int index, int val) {
        if (index > size || index < 0) return;
        Node** pcur = &head;
        while (index--)
            pcur = &((*pcur)->next);

        Node* new_node = new Node(val, *pcur);
        *pcur = new_node;
        size++;
    }
    
    void deleteAtIndex(int index) {
        if (index >= size || index < 0) return;
        Node** pcur = &head;
        while (index--)
            pcur = &((*pcur)->next);

        Node* tmp = *pcur;
        *pcur = (*pcur)->next;
        delete tmp;
        size--;
    }
    
    ~MyLinkedList() {
        while (head) {
            Node* tmp = head;
            head = head->next;
            delete tmp;
        } 
    }

private:
    Node* head = nullptr;
    int size = 0;
};

我记着我当时做的时候,遇到比较蠢的问题是在遍历链表操作时直接对原来的head指针进行操作了,而非使用一个临时的指针去遍历,还是太粗心了。

206. 反转链表

这道题需要三个指针,首先是leftright指针用于修改指向,还需要一个tmp指针保存right的下一个节点的位置。

在这里插入图片描述

在这里插入图片描述

主要逻辑代码:

while(right) {
    tmp = right->next;
    right->next = left;
    left = right;
    right = tmp;
}

然后进行遍历最终当链表翻转过来以后,把末尾至null,也就是head->next = nullptr,如图:

在这里插入图片描述

此时left就是最终的首节点,返回即可。具体代码如下:

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

        ListNode *left = head;
        ListNode *right = left->next;
        ListNode *tmp = nullptr;
        while(m) {
            tmp = right->next;
            right->next = left;
            left = right;
            right = tmp;
        }
        head->next = nullptr;

        return left;
    }
};

203. 移除链表元素

我们知道要进行链表的删除,那必然需要知道删除节点(cur指向)和删除节点前一位节点(pre指向),还需要记录删除节点的下一位(tmp指向),我们可以构造一个头节点,这样构造之后,对链表的操作就会简单很多,删除节点的操作大致如下:

pre->next = cur->next;  // 删除操作
tmp = cur->next;        // 保存cur下一位
delete cur;             // 释放删除掉的节点的空间
cur = tmp;              // cur恢复为下一位

具体细节就不一一赘述了,代码如下:

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        if(head == nullptr) return head;
        ListNode *begin = new ListNode(0);
        begin->next = head;
        ListNode *l = begin;
        ListNode *r = head;
        int flag = 0;
        ListNode *tmp = nullptr;
        while(r) {
            if(r->val == val) {
                flag = 1;
                l->next = r->next;
                tmp = l->next;
                delete r;
                r = tmp;
            }
            if(flag == 0) {
                l = r;
                r = r->next;
            }
            flag = 0;
        }
        head = begin->next;
        delete begin;
        begin = nullptr;
        return head;
    }
};

328. 奇偶链表

这道题其实非常巧妙,两个指针错位连接,将一个链表分为两个链表,奇数位串起来,偶数位串起来,根据链表总节点数为奇数还是偶数,最后在拼接链表时会稍有不同,可以分为两种情况去处理,具体代码如下:

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if(head == nullptr || head->next == nullptr || head->next->next == nullptr) return head;

        //至少三个元素
        ListNode *l = head;
        ListNode *r = l->next;
        ListNode *tmp = r;

        ListNode *loc_l = l;
        ListNode *loc_r = r;

        while(r) {
            loc_l = l->next;
            loc_r = r->next;

            l->next = r->next;

            l = loc_l;
            r = loc_r;
        }

        l = head;
        while(l->next) {
            l = l->next;
        }
        l->next = tmp;
        // l->next = tmp;
        return head;
    }
};

234. 回文链表

本题我的思路是,找中间位置,然后将链表一分为二,左半部分入栈,然后出栈的同时,链表右边部分进行遍历,与出栈元素进行比较,如果均相同,说明是回文数。这个思路解题比较简单。代码如下:

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(head == nullptr || head->next == nullptr) return true;
        ListNode *tmp = head;
        int cnt = 0;
        int flag = 0; // 0 表示偶数 1表示奇数
        while(tmp) {
            cnt++;
            tmp = tmp->next;
        }
        if(cnt % 2 == 0) flag = 0; //判断奇数还是偶数
        else flag = 1;

        cnt /= 2;
        stack<int> s;
        tmp = head;
        for(int i = 0; i < cnt; ++i) {
            s.push(tmp->val);
            tmp = tmp->next;
        }

        if(flag == 1) tmp = tmp->next;

        for(int i = 0; i < cnt; ++i) {
            if(s.top() == tmp->val) {
                s.pop();
                tmp = tmp->next;
                continue;
            }
            return false;
        }
        return true;
    }
};

138. 复制带随机指针的链表

这道题我之前讲过,视频链接

主要思路是:首先建立mapkey为源节点地址,value为源节点index,的nullmapkey表示为"null"value只需要给最后一个真实节点的index1即可,把它作为nullvalue。我们再建立一个vector用于存放下标索引到真实索引的指向,遍历vector,从而在构建新链表时初始化random的指向。具体可参考视频链接,这里直接贴出本思路的代码实现:

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head == nullptr) return nullptr;
        Node *res_node = new Node(0);
        Node *firstptr = head;
        Node *go = res_node;

        unordered_map<Node *, int> mp;
        vector<int> node_vec;
        int id = 0;

        while(firstptr) {
            Node *tmp = new Node(firstptr->val);
            mp[firstptr] = id++;
            go->next = tmp;
            go = go->next;
            firstptr = firstptr->next;
        }
        mp[nullptr] = id++;
        firstptr = head;
        while(firstptr) {
            node_vec.push_back(mp[firstptr->random]);
            firstptr = firstptr->next;
        }
        go = res_node->next;
        int i = 0;
        while(go) {
            firstptr = res_node->next;
            int index = node_vec[i];
            while(index--) {
                firstptr = firstptr->next;
            }
            go->random = firstptr;
            ++i;
            go = go->next;
        }
        return res_node->next;
    }
};

链表排序

148. 排序链表

本题首先最简单的就是直接将值写入数组,对数组排序后恢复链表。另一种方法则是归并,我们需要找中间值,将一个链划分为两部分,一直递归到最深处,即每个状态都分为左状态和右状态,接着就是对左右两个无法继续递归的状态比较大小然后进行合并,代码中sortList用于递归传入左右两个状态的起始指针,merge则用于对左右状态进行合并。

在这里插入图片描述

具体代码如下:

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if(head == nullptr || head->next == nullptr) return head;
        
        //至少两个节点
        ListNode *slow = head;
        ListNode *fast = head->next;

        /* 不论链的奇偶 slow走至左链末尾 fast走至右链末尾 */
        while(fast != nullptr && fast->next != nullptr) {
            slow = slow->next;          // slow每次走一步
            fast = fast->next->next;    // fast每次走两步
        }

        fast = slow->next;
        slow->next = nullptr;

        return merge(sortList(head), sortList(fast));
    }

    ListNode *merge(ListNode *l1, ListNode *l2) {
        ListNode dump(0);           // 只是辅助头节点
        ListNode *cur = &dump;

        while(l1 != nullptr && l2 != nullptr) {
            if(l1->val < l2->val) {
                cur->next = l1;
                l1 = l1->next;
            } else {
                cur->next = l2;
                l2 = l2->next;
            }
            cur = cur->next;
        }

        if(l1 != nullptr)
            cur->next = l1;
        else
            cur->next = l2;

        return dump.next;
    }
};

21. 合并两个有序链表

本题比较简单,两个链表一起遍历,最终需要注意肯定有一个链表先遍历结束,那么后结束的只需要加入到之前合并的链当中即可,代码如下:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode *head = new ListNode();
        ListNode *tmp = head;

        while(list1 && list2) {
            if(list1->val <= list2->val) {
                tmp->next = list1;
                list1 = list1->next;
            } else {
                tmp->next = list2;
                list2 = list2->next;
            }
            tmp = tmp->next;
        }
        if(list1 == nullptr)
            tmp->next = list2;
        else
            tmp->next = list1;
        return head->next;
    }
};

147. 对链表进行插入排序

要进行插入排序,必然要涉及到对链表节点的删除和添加,删除节点需要知道要删除节点的位置和前一个位置;添加节点需要知道当前添加位置的前一个位置,我们使用begin去指向一个头节点,方便后续对链表的操作,代码如下:

class Solution {
public:
    ListNode* insertionSortList(ListNode* head) {
        if(head == nullptr) return head;

        ListNode *begin = new ListNode(0);
        begin->next = head;
        ListNode *cur = begin->next;
        ListNode *l = begin;
        ListNode *tmp = nullptr;
        int max_num = head->val;
        while(cur) {
            if(cur->val < max_num) {
                l->next = cur->next;
                tmp = cur;
                cur = tmp->next;

                // 将tmp插入到合适位置
                ListNode *left = begin;
                ListNode *right = begin->next;
                while(right->val < tmp->val) {
                    left = left->next;
                    right = right->next;
                }
                // 插入
                tmp->next = left->next;
                left->next = tmp;

            } else {
                max_num = cur->val;
                cur = cur->next;
                l = l->next;
            }
        }
        return begin->next;
    }
};

链表双指针

141. 环形链表I / 142. 环形链表II

这两个题类似,我们直接按照142去讲,141呢是直接判断链表是否可以成环,142则是判断是否可以成环,如果成环,第一个成环的地址返回。那么以142为例,这题有两种思路:

  1. 第一种方法使用unordered_set,遍历链表,将每个链表节点的地址加入到set,如果set中已存在,则第一个发现已存在的地址则是我要需要返回的地址,若遍历结束还未找到,则返回nullptr,说明未找到。
  2. 第二种方法是通过快慢双指针,快指针步长为2,慢指针步长为1,所以如果有环,最终快慢指针一定相遇,但是有个问题就是相遇以后的位置并不一定是成环的第一个位置,那么如下图有一个数学推导:
    在这里插入图片描述

按照快慢双指针的方法,最终head到第一个成环地址的距离a等于快慢指针meet以后到第一个成环地址的距离,即上图中的a = c,因此我们只需要从headmeet开始遍历,当两者相遇时,返回的就是成环的第一个地址。

最后,我分别列出141142的代码:

141:

class Solution {
public:
    bool hasCycle(ListNode *head) {
        // 思路1 使用set求环起始节点
        #if 0
        unordered_set<ListNode *> s;
        ListNode *tmp = head;
        while(tmp) {
            if(s.find(tmp) != s.end()) {
                return true;
            }
            s.insert(tmp);
            tmp = tmp->next;
        }
        return false;
        #endif

        // 思路2 快慢指针
        ListNode *fast = head;
        ListNode *slow = head;
        ListNode *meet = nullptr;

        while(fast) {
            slow = slow->next;
            fast = fast->next;
            if(!fast) {
                return false;
            }
            fast = fast->next;
            if(fast == slow) {
                return true;
            }
        }
        return false;
    }
};

142:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        // 思路1 使用set求环起始节点
        #if 0
        unordered_set<ListNode *> s;
        ListNode *tmp = head;
        // int flag = 1;
        while(tmp) {
            if(s.find(tmp) != s.end()) {
                return tmp;
            }
            s.insert(tmp);
            tmp = tmp->next;
        }
        return nullptr;
        #endif

        // 思路2 快慢指针
        ListNode *fast = head;
        ListNode *slow = head;
        ListNode *meet = nullptr;

        while(fast) {
            slow = slow->next;
            fast = fast->next;
            if(!fast) {
                return nullptr;
            }
            fast = fast->next;
            if(fast == slow) {
                meet = fast;
                break;
            }
        }
        if(meet == nullptr) {
            return nullptr;
        }
        while(head && meet) {
            if(head == meet) {
                return head;
            }
            head = head->next;
            meet = meet->next;
        }
        return nullptr;
    }
};

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

这道题的思路是,两个指针leftright,控制间距为nn是题目中给出的倒数第几个数),然后以相同速度遍历链表,当right不能遍历时,left的下一位则是要删除的倒数第N个节点。代码如下:

ListNode *removeNthFromEnd(ListNode *head, int n) 
{
    if (!head)
        return nullptr;

    ListNode new_head(-1);
    new_head.next = head;

    ListNode *slow = &new_head, *fast = &new_head;

    for (int i = 0; i < n; i++)
        fast = fast->next;

    while (fast->next) {
        fast = fast->next;
        slow = slow->next;
    }

    ListNode *to_de_deleted = slow->next;
    slow->next = slow->next->next;
    delete to_be_deleted;
    return new_head.next;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值