面试经典150题 -- 链表 (总结)

总的地址 : 

面试经典 150 题 - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台

c++链表总结 : 

链表总结 -- 《数据结构》-- c/c++-CSDN博客

141 . 环形链表

详细题解参考 : 

141 . 环形链表-CSDN博客

这里给出慢双指针的代码 : 

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head == nullptr || head->next == nullptr) return false;
        ListNode* slow = head;
        ListNode* fast = head->next;
        while(slow != fast){
            if(fast == nullptr || fast->next == nullptr){
                return false;
            }
            slow = slow->next;
            fast = fast->next->next;
        } 
        return true;
    }
};

2 . 两数相加

法1

递归 , 这里是具有子问题的性质的 , 然后模拟乘法 ;

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        return addTwo(l1, l2, 0);
    }

    // l1 和 l2 为当前遍历的节点,carry 为进位
    private ListNode addTwo(ListNode l1, ListNode l2, int carry) {
        if (l1 == null && l2 == null) // 递归边界:l1 和 l2 都是空节点
            return carry != 0 ? new ListNode(carry) : null; // 如果进位了,就额外创建一个节点
        if (l1 == null) { // 如果 l1 是空的,那么此时 l2 一定不是空节点
            l1 = l2;
            l2 = null; // 交换 l1 与 l2,保证 l1 非空,从而简化代码
        }
        carry += l1.val + (l2 != null ? l2.val : 0); // 节点值和进位加在一起
        l1.val = carry % 10; // 每个节点保存一个数位
        l1.next = addTwo(l1.next, (l2 != null ? l2.next : null), carry / 10); // 进位
        return l1;
    }
}

法二 : 

迭代 , 模拟这个加法的过程 ;

/**
 * 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* addTwoNumbers(ListNode* l1, ListNode* l2) {
        auto dmy = new ListNode() ;// 哨兵结点
        auto cur = dmy ;
        ListNode* tmp ;
        int cay = 0 ;
        while(l1 || l2 || cay){
            cay += (l1 ? l1->val : 0) + (l2 ? l2->val : 0) ;
            tmp = new ListNode(cay % 10);
            cur -> next = tmp ; 
            cur = cur -> next ;
            cay /= 10 ;
            if(l1) l1 = l1 -> next ;
            if(l2) l2 = l2 -> next ;
        }
        return dmy -> next ;
    }
};

21 . 合并两个有序链表

递归 

/**
 * 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* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(l1 == nullptr) return l2;
        else if(l2 == nullptr) return l1;
        else if(l1->val < l2->val){
            l1->next = mergeTwoLists(l1->next,l2);
            return l1;
        } else {
            l2->next = mergeTwoLists(l1,l2->next);
            return l2;
        }
    }
};

双指针迭代

/**
 * 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* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* ans = new ListNode(-1);

        ListNode* tmp = ans;
        while(l1!=nullptr && l2!=nullptr){
            if(l1->val <= l2->val){
                tmp->next = l1;
                l1 = l1->next;
            }else {
                tmp->next = l2;
                l2 = l2->next;
            }
            tmp = tmp->next;
        }
        tmp->next = l1==nullptr ? l2 : l1;
        return ans->next;
    }
};

138 . 随机链表的复制

题意

这一题的题意可能难以理解 ; 

下面给出在lc评论区的一段话,可能帮助理解 : 

 题目要求我们给定一个链表,每个节点除了包含一个指向下一个节点的指针(next),还包含一个随机指针(random),该随机指针可以指向链表中的任何节点或空节点。我们需要构造一个深拷贝的链表,使得新链表与原链表具有相同的结构和值,但是新链表中的节点均为全新的节点。

换句话说,我们需要创建一个与原链表结构相同的链表,其中每个节点的值与对应原节点的值相同,并且每个节点的next指针和random指针都指向新链表中对应的节点。

例如,原链表为:A -> B -> C,其中A.random指向C,B.random指向A,C.random指向B。那么深拷贝后的链表为:A' -> B' -> C',其中A'.random指向C',B'.random指向A',C'.random指向B'。

(难点就是创建链表时random指的可能还没创建出来,要解决的就是这个)

思路 : 

哈希表

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head == nullptr) return nullptr;
        Node* cur = head;
        unordered_map<Node*, Node*> map;
        // 3. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
        while(cur != nullptr) {
            map[cur] = new Node(cur->val);
            cur = cur->next;
        }
        cur = head;
        // 4. 构建新链表的 next 和 random 指向
        while(cur != nullptr) {
            map[cur]->next = map[cur->next];
            map[cur]->random = map[cur->random];
            cur = cur->next;
        }
        // 5. 返回新链表的头节点
        return map[head];
    }
};

92 . 反转链表II

图文题解见 : 

反转链表【基础算法精讲 06】-CSDN博客

这一题只需要反转[l,r]的部分结点

将反转链表的前一个结点成为p0 ;

然后和上一题一样反转链表 ;

也就是 : 

把p0的next指针指向cur,p0指向pre

有一个特殊的情况,当l = 1 的时候 , 没有p0 , 可以在前面加上一个哨兵结点为p0 ;

代码如下 : 

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode* dmy = new ListNode(0,head) ;
        ListNode* p0 = dmy ;
        for(int i=0;i<left-1;i++){
            p0 = p0 -> next ;
        }
        ListNode* pre = nullptr ;
        ListNode* cur = p0->next ;
        for(int i=1;i<=right-left+1;i++){
            ListNode* nxt = cur->next ;
            cur->next = pre ;
            pre = cur ;
            cur = nxt ;
        }
        p0->next->next = cur ;
        p0->next = pre ;
        return dmy->next ;
    }
};

25 . k个一组反转链表

和上题类似 ,每逢k个反转依次一次就好了 ;

/**
 * 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* reverseKGroup(ListNode* head, int k) {
        int n = 0 ;
        ListNode* cur = head ;
        while(cur!=nullptr){ // 拿到链表的长度 
            n++;
            cur = cur->next ;
        }
        ListNode* dmy = new ListNode(0,head) ;
        ListNode* p0 = dmy ;
        while(n>=k){
            n-=k;
            ListNode* pre = nullptr;
            ListNode* cur = p0->next ;
            for(int i=0;i<k;i++){
                ListNode* nxt = cur->next;
                cur->next = pre ;
                pre = cur ;
                cur = nxt ;
            }
            ListNode* tmp = p0->next ;
            p0->next->next = cur ;
            p0->next = pre ;
            p0 = tmp ;
        }
        return dmy -> next;
    }
};

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

快慢双指针!!!

快的先跑n个,然后快慢同时跑,快的跑到终点,慢的也就到了倒数第n+1个的位置!!!

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dmy = new ListNode(0,head) ;
        dmy->next = head ;
        ListNode* fast = head , *slow  = dmy ;
        // 快的先跑n个
        for(int i=0;i<n;i++) fast = fast -> next ;
        // 快的跑 len - n , 慢的也就跑到了倒数第n个
        while(fast){
            fast = fast -> next ;
            slow = slow -> next ;
        }
        slow -> next = slow -> next -> next ;
        ListNode* ans = dmy -> next ;
        delete dmy ;
        return ans ;
    }
};

82 . 删除排序链表中的重复元素 II

双指针解决!!!

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head == null){
            return head ;
        }
        ListNode dummy = new ListNode(0,head) ;

        ListNode cur = dummy ;

        while(cur.next != null && cur.next.next != null){
            if(cur.next.val == cur.next.next.val){
                int x = cur.next.val ;
                while(cur.next != null && cur.next.val == x){
                    cur.next = cur.next.next ;
                }
            }else{
                cur = cur.next ;
            }
        }
        return dummy.next ;
    }
}

61 . 旋转链表

先变成环 , 再剪开 ;

class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        if (k == 0 || head == nullptr || head->next == nullptr) {
            return head;
        }
        int n = 1;
        ListNode* iter = head;
        while (iter->next != nullptr) {
            iter = iter->next;
            n++;
        }
        int add = n - k % n;
        if (add == n) {
            return head;
        }
        iter->next = head;
        while (add--) {
            iter = iter->next;
        }
        ListNode* ret = iter->next;
        iter->next = nullptr;
        return ret;
    }
};

86 . 分隔链表

直观来说我们只需维护两个链表 smalll 和 large即可,small链表按顺序存储所有小于 xxx 的节点,large 链表按顺序存储所有大于等于 x 的节点。遍历完原链表后,我们只要将 small 链表尾节点指向 large链表的头节点即能完成对链表的分隔。

代码如下 : 

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        ListNode* small = new ListNode(0);
        ListNode* smallHead = small;
        ListNode* large = new ListNode(0);
        ListNode* largeHead = large;
        while (head != nullptr) {
            if (head->val < x) {
                small->next = head;
                small = small->next;
            } else {
                large->next = head;
                large = large->next;
            }
            head = head->next;
        }
        large->next = nullptr;
        small->next = largeHead->next;
        return smallHead->next;
    }
};

146 . LRU缓存

双链表 + 哈希表

struct DLinkedNode {
    int key, value;
    DLinkedNode* prev;
    DLinkedNode* next;
    DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}
    DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};

class LRUCache {
private:
    unordered_map<int, DLinkedNode*> cache;
    DLinkedNode* head;
    DLinkedNode* tail;
    int size;
    int capacity;

public:
    LRUCache(int _capacity): capacity(_capacity), size(0) {
        // 使用伪头部和伪尾部节点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head->next = tail;
        tail->prev = head;
    }
    
    int get(int key) {
        if (!cache.count(key)) {
            return -1;
        }
        // 如果 key 存在,先通过哈希表定位,再移到头部
        DLinkedNode* node = cache[key];
        moveToHead(node);
        return node->value;
    }
    
    void put(int key, int value) {
        if (!cache.count(key)) {
            // 如果 key 不存在,创建一个新的节点
            DLinkedNode* node = new DLinkedNode(key, value);
            // 添加进哈希表
            cache[key] = node;
            // 添加至双向链表的头部
            addToHead(node);
            ++size;
            if (size > capacity) {
                // 如果超出容量,删除双向链表的尾部节点
                DLinkedNode* removed = removeTail();
                // 删除哈希表中对应的项
                cache.erase(removed->key);
                // 防止内存泄漏
                delete removed;
                --size;
            }
        }
        else {
            // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
            DLinkedNode* node = cache[key];
            node->value = value;
            moveToHead(node);
        }
    }

    void addToHead(DLinkedNode* node) {
        node->prev = head;
        node->next = head->next;
        head->next->prev = node;
        head->next = node;
    }
    
    void removeNode(DLinkedNode* node) {
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }

    void moveToHead(DLinkedNode* node) {
        removeNode(node);
        addToHead(node);
    }

    DLinkedNode* removeTail() {
        DLinkedNode* node = tail->prev;
        removeNode(node);
        return node;
    }
};

参考 : 

代码随想录

链表总结 -- 《数据结构》-- c/c++-CSDN博客

141 . 环形链表-CSDN博客

反转链表【基础算法精讲 06】-CSDN博客

  • 17
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值