算法链表链表相关问题总结

Posted trevo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法链表链表相关问题总结相关的知识,希望对你有一定的参考价值。

剑指offer

6. 从尾到头打印链表

题目链接:https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/

递归

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        if(!head)
            return {};
        vector<int> v = reversePrint(head->next);
        v.push_back(head->val);
        return v;
    }
};

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> v;
        ListNode* cur = head;
        while (cur) {
            v.push_back(cur->val);
            cur = cur->next;
        }
        reverse(v.begin(), v.end());
        return v;
    }
};

reverse数组

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> v;
        ListNode* cur = head;
        while (cur) {
            v.push_back(cur->val);
            cur = cur->next;
        }
        reverse(v.begin(), v.end());
        return v;
    }
};

18. 删除链表的节点

题目链接:https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        auto dummy = new ListNode(-1);
        dummy -> next = head;
        auto tmp = dummy;
        while(head && head -> val != val)
        {
            tmp = tmp -> next;
            head = head -> next;
        }
        // cout << tmp -> val <<endl;
        // cout << head -> val <<endl;
        tmp -> next = head -> next;
        return dummy -> next;
    }
};

22. 链表中倒数第k个节点

题目链接:https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/

快慢指针

快指针比慢指针快k个节点,当快指针指向尾结点的下一个节点的时候,慢指针指向倒数第k个节点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode* fast = head;
        ListNode* low = head;
        while(fast){
            fast = fast -> next;
            if(k > 0) k--;
            else low = low -> next;
        }
        return low;
    }
};

24. 反转链表

题目链接:https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/

思路

依次遍历链表,遍历一个逆置一个。

/**
 * 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 *new_head = nullptr;
        while(head)
        {
            ListNode *next = head->next;
            head->next = new_head;
            new_head = head;
            head = next;
        }
        return new_head;
    }
};

25. 合并两个排序的链表

题目链接:https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/

使用一个

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* new_head = new ListNode(-1); //保存合并后的头结点
        ListNode* tmp = new_head; //遍历链表
        while(l1 && l2)
        {
            //小的追加到tmp后面
            if(l1 -> val <= l2 -> val)
            {
                tmp -> next = l1;
                l1 = l1 -> next;
            }
            else
            {
                tmp -> next = l2;
                l2 = l2 -> next;
            }
            //更新tmp
            tmp = tmp -> next;
        }
        //没处理完的直接追加到后面
        tmp -> next = l1 ? l1 : l2;
        return new_head -> next;
    }
};

35. 复杂链表的复制

题目链接:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/

哈希

用哈希表做一个映射,hashmap的key是原先的节点,val是复制的节点。先初始化hashmap,然后分别next指针和random指针。

哈希表的作用:找到每个点的对应点(复制点)

时间复杂度O(n) 空间复杂度O(n)

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(!head) return head;

        unordered_map<Node*, Node*> hashmap;
        hashmap[NULL] = NULL;
        //初始化hashmap
        //hashmap key是原先的节点p val是新节点
        for(auto p = head; p; p = p -> next)
            hashmap[p] = new Node(p -> val);

        for(auto p = head; p; p = p -> next)
        {
            //复制next指针
            hashmap[p] -> next = hashmap[p -> next];
            //复制random指针
            hashmap[p] -> random = hashmap[p -> random];
        }
        return hashmap[head];
    }
};

迭代

先复制正常的next节点,不复制random节点。之后复制指针的指向

时间复杂度O(n) 空间复杂度O(1)

*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
public:
    Node* copyRandomList(Node* head) {
        //在每个节点后面添加它的复制
        //复制next节点 复制的节点保存在原先节点的next指针处
        //新复制的节点的next指针的原先节点的next指针
        //原先: 1->2->3->4->5->null
        //now: 1->1->2->2->3->3->4->4->5->5->null
        for(auto p = head; p;)
        {
            auto np = new Node(p -> val);
            auto next = p -> next;
            p -> next = np;
            np -> next = next;
            p = next;
        }
		//复制random指针
        for(auto p = head; p; p = p -> next -> next)
            if(p -> random)
                p -> next -> random = p -> random -> next;
        //原链表不能修改 还原
        //遍历链表 分开两个链表
        auto dummy = new Node(-1);
        auto cur = dummy;
        for(auto p = head; p; p = p -> next)
        {
            cur -> next = p -> next;
            cur = cur -> next;
            p -> next = p -> next -> next;
        }
        return dummy -> next;
    }
};

36. 二叉搜索树与双向链表

题目链接:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/

递归

Leetcode要求改成双向循环链表


剑指offer是改成双向链表

题目链接:https://www.acwing.com/problem/content/87/

left 改成 前驱

right 改成后继

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* convert(TreeNode* root) {
        if (!root) return NULL;
        auto sides = dfs(root);
        return sides.first;
    }

    pair<TreeNode*, TreeNode*> dfs(TreeNode* root)
    {
        //叶子节点
        if (!root->left && !root->right) return {root, root};
        //左右子树都有
        if (root->left && root->right)
        {
            //递归处理左右子树
            auto lside = dfs(root->left), rside = dfs(root->right);
            //拼接左右子树
            lside.second->right = root, root->left = lside.second;
            root->right = rside.first, rside.first->left = root;
            return {lside.first, rside.second};
        }
        //只有左子树
        if (root->left)
        {
            auto lside = dfs(root->left);
            lside.second->right = root, root->left = lside.second;
            return {lside.first, root};
        }
        //只有右子树
        if (root->right)
        {
            auto rside = dfs(root->right);
            rside.first->left = root, root->right = rside.first;
            return {root, rside.second};
        }
    }
};

Leetcode

92. 反转链表II

题目链接:https://leetcode-cn.com/problems/reverse-linked-list-ii/

几个关键节点:

1. 逆置前头节点->逆置后尾节点

2. 逆置前尾节点->逆置后头节点
  	3. 逆置前头节点的前驱
     	4. 逆置前尾节点的后继

思路:

  1. 找到开始逆置的节点,记录该节点的前驱和该节点;
  2. 从head开始,逆置n-m+1个节点;
  3. 将pre_head与new_head连接,aft_tail与head连接。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        int len = n - m + 1; // 需要写在改变m之前
        ListNode* res = head; //最终的头节点
        ListNode *pre_head = NULL;//开始逆置的节点的前驱
        while(head && --m) 
        {   
            pre_head = head; //head的前驱
            head = head -> next; //head向后移动m-1个位置
        }
        ListNode* aft_tail = head; //逆置后的尾节点
        ListNode* new_head = NULL; //逆置后的头节点
       
        while(head && len) //逆置m到n
        {
            ListNode *next = head -> next;
            head -> next = new_head;
            new_head = head;
            head = next;
            len--;
        }

        aft_tail -> next = head; //逆置后尾节点的下一个节点是逆置前尾节点的后继
        if(pre_head) pre_head -> next = new_head; //逆置节点的前驱部位空,第一个逆置的不是头节点
        else res = new_head;//从头节点开始逆置的
        return res;
    }
};

146. LRU缓存

题目链接:https://leetcode-cn.com/problems/lru-cache/solution/lru-ce-lue-xiang-jie-he-shi-xian-by-labuladong/

思路:

使用两个双链表和一个哈希表:第一个双链表存储未被使用的位置;第二个双链表存储已被使用的位置,且按最近使用时间从左到右排好序;哈希表存储key对应的链表中的节点地址;

class LRUCache {
public:

    struct Node
    {
        int val, key;
        Node *left, *right;
        Node() : key(0), val(0), left(NULL), right(NULL) {}
    };
    Node *hu, *tu; // hu: head_used, tu: tail_used; head在左侧,tail在右侧
    Node *hr, *tr; // hr: head_remains, tr: tail_remains; head在左侧,tail在右侧
    int n;
    unordered_map<int, Node*> hash;

    void delete_node(Node *p)
    {
        p->left->right = p->right, p->right->left = p->left;
    }

    void insert_node(Node *h, Node *p)
    {
        p->right = h->right, h->right = p;
        p->left = h, p->right->left = p;
    }
	/*初始化*/
    LRUCache(int capacity) {
        n = capacity;
        hu = new Node(), tu = new Node();
        hr = new Node(), tr = new Node();
        hu->right = tu, tu->left = hu;
        hr->right = tr, tr->left = hr;
		
        //第一个双链表插入 n 个节点,n 是缓存大小;
        //第二个双链表和哈希表都为空;
        for (int i = 0; i < n; i ++ )
        {
            Node *p = new Node();
            insert_node(hr, p);
        }
    }

    int get(int key) {
        if (hash[key]) //用哈希表判断key是否存在
        {
            Node *p = hash[key];
            delete_node(p);
            insert_node(hu, p);
            return p->val; //如果key存在,则返回对应的value,同时将key对应的节点放到第二个双链表的最左侧;
        }
        return -1; //如果key不存在,则返回-1
    }

    void put(int key, int value) {
        if (hash[key]) //用哈希表判断key是否存在
        {
            //key存在,则修改对应的value,同时将key对应的节点放到第二个双链表的最左侧;
            Node *p = hash[key];
            delete_node(p);
            insert_node(hu, p);
            p->val = value;
            return;
        }
		//key不存在:
		//如果缓存已满,则删除第二个双链表最右侧的节点(上次使用时间最老的节点),同时更新三个数据结构;
        if (!n)
        {
            n ++ ;
            Node *p = tu->left;
            hash[p->key] = 0;
            delete_node(p);
            insert_node(hr, p);
        }
		//否则,插入(key, value):从第一个双链表中随便找一个节点,修改节点权值,然后将节点从第一个双链表删除,插入第二个双链表最左侧,同时更新哈希表;
        n -- ;
        Node *p = hr->right;
        p->key = key, p->val = value, hash[key] = p;
        delete_node(p);
        insert_node(hu, p);
    }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

使用一个双链表和一个哈希表

class LRUCache {
public:
    LRUCache(int capacity) {
        cap = capacity;
        used = 0;
        head = new Node(0, 0);
        tail = new Node(0, 0);
        head->next = tail;
        tail->pre = head;
    }

    int get(int key) {
        if (m.count(key)) {
            Node *p = m[key];
            int val = p->val;
            delete_node(p);
            m[key] = insert_node(key, val);
            return val;
        }
        return -1;
    }

    void put(int key, int value) {
        if (m.count(key)) {       
            delete_node(m[key]);
            used--;
        }
        m[key] = insert_node(key, value);
        used++;
        if (used > cap) {           
            m.erase(tail->pre->key); 
            delete_node(tail->pre);          
            used--;
        }

    }
private:
    int cap, used;
    struct Node{
        int val, key;
        Node *pre, *next;
        Node(int key, int val): key(key), val(val), pre(NULL), next(NULL){};
    };
    unordered_map<int, Node*> m;
    Node *head, *tail;
    void delete_node(Node *p) {
        p->pre->next = p->next;
        p->next->pre = p->pre;    
        delete p;

    }
    Node* insert_node(int key, int val) {
        Node *node = new Node(key, val);
        head->next->pre = node;
        node->next = head->next;
        head->next = node;
        node->pre = head;
        return node;
    }    
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

147. 链表插入排序

题目链接:https://leetcode-cn.com/problems/insertion-sort-list/

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* insertionSortList(ListNode* head) {
        //借助dummy指针
        ListNode* dummy = new ListNode(-1);
        while(head)
        {
            ListNode* next = head -> next;
            ListNode* p = dummy;
            //找到待插入元素的合适位置
            while(p -> next && p -> next -> val <= head -> val) p = p -> next;
            //将待插入元素出入到链表中
            head -> next = p -> next;
            p -> next = head;
            head = next;
        }
        return dummy -> next;
    }
};

148. 排序链表

题目链接:https://leetcode-cn.com/problems/sort-list/

快速排序

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    //得到尾节点
    ListNode* get_tail(ListNode* head){
        while(head -> next) head = head -> next;
        return head;
    }
    ListNode* sortList(ListNode* head) {
        if(!head || !head->next) return head;
        auto left = new ListNode(-1), mid = new ListNode(-1), right = new ListNode(-1);
        auto ltail = left, mtail = mid, rtail = right;
        
        int val = head -> val;
        for(auto p = head; p; p = p -> next)
        {
            if(p -> val < val) ltail = ltail -> next = p; 
            else if(p -> val == val) mtail  = mtail -> next = p;
            else rtail = rtail -> next = p;
        }
        ltail -> next = mtail -> next = rtail -> next = NULL;
        left -> next = sortList(left -> next);
        right -> next = sortList(right -> next);
        
        //拼接三个链表
        get_tail(left) -> next = mid -> next;
        get_tail(left) -> next = right -> next;
        
        auto p = left -> next;
        // delete left;
        // delete mid;
        // delete right;
        return p;
    }
};

归并排序

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if(!head || !head->next) return head;
        ListNode* pre = head, *slow = head, *fast = head;
        while(fast && fast->next) {
            pre = slow;
            slow = slow->next;
            fast = fast->next->next;
        }
        pre->next = nullptr;
        return mergeTwoList(sortList(head), sortList(slow));
    }
    ListNode* mergeTwoList(ListNode* h1, ListNode* h2) {
        if(!h1) return h2;
        if(!h2) return h1;
        if(h1->val < h2->val) {
            h1->next = mergeTwoList(h1->next, h2);
            return h1;
        }
        else {
            h2->next = mergeTwoList(h1, h2->next);
            return h2;
        }
    }
};

143. 重排链表

题目链接:https://leetcode-cn.com/problems/reorder-list/

思路:

  1. 将后半段的指针都反向;
  2. 用两个指针分别从1和n开始往中间扫描,将后半段交替插入到前半段。

时间复杂度分析:整个链表总共扫描三次,第一次求总长度,第二次将后半段反向,第三次将后半段交替插入前半段,所以总时间复杂度是 O(n)。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    void reorderList(ListNode* head) {
        //获取链表长度
        int n = 0;
        for (ListNode *p = head; p; p = p->next) n ++ ;
        //长度小于2,不用重排
        if (n <= 2) return;
        
        //获取节点
        ListNode *later = head;
        for (int i = 0; i + 1 < (n + 1) / 2; i ++ )
            later = later->next;
        //逆置后半串节点
        ListNode *a = later, *b = later->next;
        while (b)
        {
            ListNode *c = b->next;
            b->next = a;
            a = b;
            b = c;
        }
        //依次将后部分的节点插入到前面正确的位置
        later->next = 0;
        while (head && head != a)
        {
            b = a->next;
            a->next = head->next;
            head->next = a;
            head = head->next->next;
            a = b;
        }
    }
};

141. 环形链表

题目链接:https://leetcode-cn.com/problems/linked-list-cycle/

快慢指针

快指针一次走两步,慢指针一次走一步,当两个指针第一次相遇的时候,快和慢指针一次都走一步,第二次相遇的点就是环的入口。可以证明

/**
 * 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 || !head -> next) return nullptr;
        ListNode* fast = head;
        ListNode* low = head;
        
        while(fast && low)
        {
            fast = fast -> next;
            low = low -> next;
            if(fast) fast = fast -> next;
            //如果快指针走到nullptr,说明链表没有环
            else return false;
            
            //第一次相遇后,令fast = head 两个节点都走一步
            if(fast == low)
            {
                fast = head;
                while(fast != low)
                {
                    fast = fast -> next;
                    low = low -> next;
                }
                return true;
            }
        }
        return false;
    }
};

142 环形链表II

题目链接:

160. 相交链表

题目链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/

双指针

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(!headA || !headB) return nullptr;
        auto A = headA, B = headB;
        while(A != B)
        {
            A = A -> next;
            B = B -> next;
            if(!A && !B) return nullptr;
            if(!A) A = headB;
            if(!B) B = headA;
        }
        return A;
    }
};

86. 分隔链表

题目链接:https://leetcode-cn.com/problems/partition-list/

快速排序中的一步,将元素放到合适的位置,左边比他小,右边比他大或者相等

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    //得到left的尾结点
    ListNode* get_tail(ListNode* head){
        while(head -> next) head = head -> next;
        return head;
    }
    ListNode* partition(ListNode* head, int x) {
        if(!head) return head;
        auto left = new ListNode(-1), right = new ListNode(-1);
        auto ltail = left, rtail = right;
        for(auto p = head; p; p = p -> next)
        {
            if(p -> val < x) ltail = ltail -> next = p;
            else rtail = rtail -> next = p;
        }
        ltail -> next  = rtail -> next = nullptr;
        get_tail(left) -> next = right -> next;
        return left -> next;
    }
};

23. 合并K个有序的链表

题目链接:https://leetcode-cn.com/problems/merge-k-sorted-lists/

顺序合并

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* new_head = new ListNode(-1); //保存合并后的头结点
        ListNode* tmp = new_head; //遍历链表
        while(l1 && l2)
        {
            //小的追加到tmp后面
            if(l1 -> val <= l2 -> val)
            {
                tmp -> next = l1;
                l1 = l1 -> next;
            }
            else
            {
                tmp -> next = l2;
                l2 = l2 -> next;
            }
            //更新tmp
            tmp = tmp -> next;
        }
        //没处理完的直接追加到后面
        tmp -> next = l1 ? l1 : l2;
        return new_head -> next;
    }

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode *ans = nullptr;
        for (size_t i = 0; i < lists.size(); ++i) {
            ans = mergeTwoLists(ans, lists[i]);
        }
        return ans;
    }
};

分治合并

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* new_head = new ListNode(-1); //保存合并后的头结点
        ListNode* tmp = new_head; //遍历链表
        while(l1 && l2)
        {
            //小的追加到tmp后面
            if(l1 -> val <= l2 -> val)
            {
                tmp -> next = l1;
                l1 = l1 -> next;
            }
            else
            {
                tmp -> next = l2;
                l2 = l2 -> next;
            }
            //更新tmp
            tmp = tmp -> next;
        }
        //没处理完的直接追加到后面
        tmp -> next = l1 ? l1 : l2;
        return new_head -> next;
    }
    ListNode* merge(vector<ListNode*>& lists, int l, int r)
    {
        if(l == r) return lists[l];
        if(l > r) return nullptr;
        int mid = (l + r) >> 1;
        return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
    }

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        return merge(lists, 0, lists.size() - 1);
    }
};

234. 回文链表

题目链接:https://leetcode-cn.com/problems/palindrome-linked-list/

逆置链表

逆置链表的后半部分然后比较

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(!head || !head -> next) return true;
        auto slow = head, fast = head;
        while(fast && fast -> next)
        {
            slow = slow -> next;
            fast = fast -> next -> next;
        }
        
        fast = nullptr; //逆置后的头节点 逆置前的尾节点
        while(slow)
        {
            auto next = slow -> next;
            slow -> next = fast;
            fast = slow;
            slow = next;
        }
        while(head && fast)
        {
            if(head -> val != fast -> val) return false;
            head = head -> next;
            fast = fast -> next;
        }
        delete slow;
        delete fast;
        return true;
    }
};

小技巧

fast一次走2步,slow一次走1步,链表全长为len,len为偶数时,slow到达前半段的最后一个节点,len为奇数时,slow到达正中间的节点,两种情况中,slow->next均为后半段的起始节点。

ListNode* getMid(ListNode* head){
    ListNode* fast = head, * slow = head;
    while(fast && fast -> next){
        slow = slow->next;
        fast = fast->next->next;
    }
    delete fast;
    return slow;
}

2. 两数相加

题目链接:

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

题目链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/submissions/

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(-1);
        dummy -> next = head;
        auto fast = dummy, low = dummy;
        while(fast)
        {
            fast = fast -> next;
            if(n >= 0) n--;
            else low = low -> next;
        }
        return dummy -> next;
    }
};

21. 合并两个有序链表

题目链接:https://leetcode-cn.com/problems/merge-two-sorted-lists/submissions/

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(-1);
        ListNode* tmp = dummy;
        while(l1 && l2)
        {
            if(l1 -> val < l2 -> val) 
            {
                tmp -> next = l1;
                l1 = l1 -> next;
            }
            else 
            {
                tmp -> next = l2;
                l2 = l2 -> next;
            }
            tmp = tmp -> next;
        }
        tmp -> next = l1 ? l1 : l2;
        return dummy -> next;
    }
};

876. 链表的中间节点

题目链接:https://leetcode-cn.com/problems/middle-of-the-linked-list/

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        auto fast = head, low = head;
        while(fast  && fast -> next)
        {
            low = low -> next;
            fast = fast -> next -> next;    
        }
        return low;
    }
};
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode* dummy = new ListNode(-1);
        dummy -> next = head;
        auto fast = dummy, low = dummy;
        while(fast -> next  && fast -> next -> next)
        {
            low = low -> next;
            fast = fast -> next -> next;    
        }
        return low -> next;

    }
};

114. 二叉树展开为链表

题目链接:https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/

递归

在还没操作节点右子树前,不能破坏该节点的右子树指向。所以采用后序遍历。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
  TreeNode* last = nullptr;
    void flatten(TreeNode* root) {
        if (root == nullptr) return;
        flatten(root->right);
        flatten(root->left);
        root->right = last;
        root->left = nullptr;
        last = root;
    }

};

迭代(先序遍历)

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void flatten(TreeNode* root) {
        //先序遍历
        stack<TreeNode*> node_stack;
        if(nullptr != root){
          node_stack.push(root);
        }

        TreeNode* current_node = root;

        while(!node_stack.empty()){
          TreeNode *node = node_stack.top();
          node_stack.pop();
 
          if(nullptr != node->right){
              node_stack.push(node->right);
          }

          if(nullptr != node->left){
              node_stack.push(node->left);
          }

          if(node != root){
             current_node->left = nullptr;
             current_node->right = node;
          }
          current_node = node;
        }
    }
};

总结

链表的问题比较考察逻辑思维,可以通过画图来理清思路。

常用操作

获取链表中间位置的元素

ListNode* getMid(ListNode* head){
    ListNode* fast = head, * slow = head;
    while(fast && fast -> next){
        slow = slow->next;
        fast = fast->next->next;
    }
    delete fast;
    return slow;
}

删除倒数第K个节点

ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode* dummy = new ListNode(-1);
    dummy -> next = head;
    auto fast = dummy, low = dummy;
    while(fast)
    {
        fast = fast -> next;
        if(n >= 0) n--;
        else low = low -> next;
    }
    return dummy -> next;
}

逆置链表

ListNode* reverseList(ListNode* head) {
    ListNode *new_head = nullptr;
    while(head)
    {
        ListNode *next = head->next;
        head->next = new_head;
        new_head = head;
        head = next;
    }
    return new_head;
}

技巧和注意事项

指针丢失和内存泄漏

插入节点

插入之前,应该先把要插入位置之前的元素c的下一个节点保存下来

/*
a -> b -> c -> d -> e
c后面插入f
*/
//写法1
cur = c -> next;
c -> next = f;
f -> next = cur;

//写法2
f -> next = c -> next;
c -> next = f;

删除节点

p -> next  = p -> next -> next;

(哨兵)dummy节点

用来解决边界问题,不参与业务逻辑,引入链表以后,不管链表是否为空,dummy->next为头结点;

边界问题

1. 链表为空

2. 链表只包含一个节点

3.链表只包含两个节点

4. 链表对头尾节点的处理

以上是关于算法链表链表相关问题总结的主要内容,如果未能解决你的问题,请参考以下文章

一文讲透链表操作,看完你也能轻松写出正确的链表代码

一文讲透链表操作,看完你也能轻松写出正确的链表代码

一文讲透链表操作,看完你也能轻松写出正确的链表代码

[11道链表经典笔试题]优化的算法思想:反转链表链表相交快慢指针

数据结构:链表链队列

数据结构与算法之线性表(超详细顺序表链表)