【leetcode】单链表题集

1,反转单链表

来源:206. 反转链表  &&  剑指 Offer 24. 反转链表

迭代思路:采用头插法创建一个新的单链表。时间复杂度O(N),空间复杂度O(1)。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *result = NULL;
        while (head) {
            ListNode *next = head->next; // 保存下一个节点
            head->next = result; // 插入单链表开头
            result = head; // 更新单链表
            head = next; // 继续迭代
        }
        return result;
    }
};

递归思路:较复杂。先反转后面链表,再令下一节点指向自己,自己作为尾节点指向NULL。时间复杂度O(N),空间复杂度O(N)。

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

2,判断单链表是否有环

来源:141. 环形链表

思路:双指针,定义2个指针,一快一慢,快指针每次移动2个节点,慢指针每次移动1个节点。如果快指针结束,无环;如果快慢指针相等,有环。

class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode *fast = head;
        ListNode *slow = head;
        while (fast && fast->next) {
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow) return true; // 先移动后判断
        }
        return false;
    }
};

3,两个链表的第一个公共节点

来源:剑指 Offer 52. 两个链表的第一个公共节点  &&  160. 相交链表

思路:分别计算两个链表的长度,较长的一方先行移动长度差后再同时移动。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int la = size(headA);
        int lb = size(headB);
        if (la > lb) {
            int t = la - lb;
            while (t--) {
                headA = headA->next;
            }
        } else {
            int t = lb - la;
            while (t--) {
                headB = headB->next;
            }
        }
        while (headA) {
            if (headA == headB) {
                return headA;
            }
            headA = headA->next;
            headB = headB->next;
        }
        return NULL;
    }
    int size(ListNode *p) {
        int sz = 0;
        while (p) {
            sz++;
            p = p->next;
        }
        return sz;
    }
};

另一种简洁的思路:定义2个指针分别指向两个链表,当某个指针遍历完成后指向另一链表继续遍历,直到找到交叉点。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *p1 = headA;
        ListNode *p2 = headB;
        while (p1 != p2) {
            p1 = (p1 == NULL) ? headB : p1->next;
            p2 = (p2 == NULL) ? headA : p2->next;
        }
        return p1;
    }
};

4,两链表是否相交

若相交返回第一个交点,否则返回NULL。

来源:面试题 02.07. 链表相交

思路:如果两链表相交,尾节点必然相同,反之亦然。先判断尾节点是否相同,若相同则同题3。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == NULL || headB == NULL) return NULL;
        if (tail(headA) != tail(headB)) return NULL;
        ListNode *p1 = headA;
        ListNode *p2 = headB;
        while (p1 != p2) {
            p1 = (p1 == NULL) ? headB : p1->next;
            p2 = (p2 == NULL) ? headA : p2->next;
        }
        return p1;
    }
    ListNode *tail(ListNode *head) {
        while (head->next) head = head->next;
        return head;
    }
};

5,链表倒数第k个节点

来源:剑指 Offer 22. 链表中倒数第k个节点

思路1:双指针。定义2个指针,一前一后,前指针先移动k个节点后两指针同时移动,当前指针结束时,后指针即为结果。

class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode *first = head;
        while (k-- > 0) first = first->next;
        ListNode *second = head;
        while (first) {
            first = first->next;
            second = second->next;
        }
        return second;
    }
};

思路2:求链表长度n,正向第n-k个节点即为结果。

class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        k = size(head) - k;
        ListNode *p = head;
        while (k-- > 0) p = p->next;
        return p;
    }
    int size(ListNode *p) {
        int sz = 0;
        while (p) {
            sz++;
            p = p->next;
        }
        return sz;
    }
};

6,链表的中间节点

来源:876. 链表的中间结点

思路:双指针,定义2个指针,一快一慢,快指针每次移动2个节点,慢指针每次移动1个节点,当快指针无法移动时,慢指针即为结果。

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode *fast = head;
        ListNode *slow = head;
        while (fast && fast->next) {
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
};

同样,可先计算链表长度n,跳过前n/2个节点后返回。

7,删除非尾节点

函数参数只有一个:要被删除的节点。

来源:237. 删除链表中的节点

思路:将下一个节点数据保存到当前节点,删除下一个节点。想不到则写不出,小技巧而已。

class Solution {
public:
    void deleteNode(ListNode* node) {
        ListNode *next = node->next;
        node->val = next->val;
        node->next = next->next;
    }
};

8,删除指定元素的所有节点

来源:203. 移除链表元素(有重复元素)  &&  剑指 Offer 18. 删除链表的节点(无重复元素)

思路:无论重复与否,采用尾插法重新创建链表并返回。尾插法技巧:多创建一个节点,不再判断头节点是否为NULL,代码简洁。

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode *result = new ListNode(0); // 多创建一个节点,不再判断result是否为NULL
        ListNode *tail = result;
        while (head) {
            ListNode *next = head->next; // 保存下一节点
            head->next = NULL; // 断开链接
            if (head->val != val) { // 满足条件
                tail->next = head;
                tail = head;
            }
            head = next;
        }
        return result->next;
    }
};

9,合并两个有序链表

来源:21. 合并两个有序链表  &&  剑指 Offer 25. 合并两个排序的链表

迭代思路:尾插法重新生成新链表

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode *result = new ListNode(0);
        ListNode *tail = result;
        while (l1 && l2) {
            if (l1->val < l2->val) {
                ListNode *next = l1->next;
                l1->next = NULL;
                tail->next = l1;
                tail = l1;
                l1 = next;
            } else {
                ListNode *next = l2->next;
                l2->next = NULL;
                tail->next = l2;
                tail = l2;
                l2 = next;
            }
        }
        if (l1) tail->next = l1;
        else tail->next = l2;
        return result->next;
    }
};

递归思路:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (l1 == NULL) return l2;
        if (l2 == NULL) return l1;
        if (l1->val < l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        } else {
            l2->next = mergeTwoLists(l1, l2->next);
            return l2;
        }
    }
};

10,合并多个有序链表

来源:23. 合并K个升序链表

思路:分治,链表两两合并直到剩余1个。

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int sz = lists.size();
        if (sz == 0) return NULL;
        while (sz > 1) {
            int half = sz / 2;
            for (int i = 0; i < half; i++) {
                lists[i] = mergeTwoLists(lists[i], lists[sz - i - 1]);
            }
            sz = sz - half; // 大小减小一半
        }
        return lists[0];
    }
    // 合并两个有序链表
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode *result = new ListNode(0);
        ListNode *tail = result;
        while (l1 && l2) {
            if (l1->val < l2->val) {
                ListNode *next = l1->next;
                l1->next = NULL;
                tail->next = l1;
                tail = l1;
                l1 = next;
            } else {
                ListNode *next = l2->next;
                l2->next = NULL;
                tail->next = l2;
                tail = l2;
                l2 = next;
            }
        }
        if (l1) tail->next = l1;
        else tail->next = l2;
        return result->next;
    }
};

11,根据val分隔链表

将小于val的放置于链表前面,大于等于val的放置于链表后面,保持初始相对位置。

来源:86. 分隔链表  &&  面试题 02.04. 分割链表(无需保持初始相对位置)

思路:定义2个链表small和big,small存储小于val的节点,big存储其他节点。small和big采用尾插法创建。

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        ListNode *small = new ListNode(0); // 小于x的链表
        ListNode *stail = small;
        ListNode *big = new ListNode(0); // 大于等于x的链表
        ListNode *btail = big;

        while (head) {
            ListNode *next = head->next;
            head->next = NULL;
            if (head->val < x) {
                stail->next = head;
                stail = head;
            } else {
                btail->next = head;
                btail = head;
            }
            head = next;
        }
        stail->next = big->next;
        return small->next;
    }
};

12,将链表平均切割成k个链表

来源:725. 分隔链表

思路:链表总长度/k=n余m,每段链表长度为n,因为有余数m,前m段链表长度+1。

class Solution {
public:
    vector<ListNode*> splitListToParts(ListNode* root, int k) {
        vector<ListNode*> result(k, NULL);
        int sz = size(root);
        int n = sz / k;
        int m = sz % k;

        for (int i = 0; i < k; i++) {
            int l = n-1; // 为了断开最后一个节点的next指针,这里少数一个节点。
            if (i < m) l += 1;
            // 数前l个节点
            ListNode *head = root;
            while (l-- > 0) root = root->next;
            if (root == NULL) break;
            ListNode *next = root->next;
            root->next = NULL;
            result[i] = head;
            root = next;
        }
        return result;
    }
    int size(ListNode *p) {
        int sz = 0;
        while (p) {
            sz++;
            p = p->next;
        }
        return sz;
    }
};

13,反转单链表的一部分

来源:92. 反转链表 II

思路:重新构建单链表,链表分成前、中、后三部分,前部分采用尾插法构建,后面部分直接返回,中间部分采用头插法构建(需要注意tail指针,tail始终指向结果链表的最后一个节点)。

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode *result = new ListNode(0);
        ListNode *tail = result;
        ListNode *last = NULL;
        int cnt = 1;
        while (head) {
            ListNode *next = head->next;
            if (cnt < left) { // 前面部分
                tail->next = head;
                tail = head;
            } else if (cnt > right) { // 后面部分
                tail->next = head;
                break;
            } else { // 中间部分
                head->next = NULL;
                if (last == NULL) { // last指向前面部分的最后一个节点
                    last = tail;
                    last->next = head;
                    tail = head;
                } else {
                    head->next = last->next;
                    last->next = head;
                }
            }
            head = next;
            cnt++;
        }
        return result->next;
    }
};

14,回文单链表

来源:面试题 02.06. 回文链表  &&  234. 回文链表

思路:将链表平均分成前后两个链表,反转后面链表,对比两个链表是否一样。

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        int sz = size(head);
        int half = sz / 2;
        if (sz % 2 == 1) half += 1;
        ListNode *p = head;
        while (half-- > 0) p = p->next;
        // 链表分成两部分p和q,p的长度大于等于q的长度,反转q与p比较
        ListNode *q = reverseList(p);
        p = head;
        while (q) {
            if (p->val != q->val) return false;
            p = p->next;
            q = q->next;
        }
        return true;
    }
    int size(ListNode *p) {
        int sz = 0;
        while (p) {
            sz++;
            p = p->next;
        }
        return sz;
    }
    ListNode* reverseList(ListNode* head) {
        ListNode *result = NULL;
        while (head) {
            ListNode *next = head->next; // 保存下一个节点
            head->next = result; // 插入单链表开头
            result = head; // 更新单链表
            head = next; // 继续迭代
        }
        return result;
    }
};

15,奇偶链表

给定一个单链表,把所有奇数位置节点和偶数位置节点分别排在一起。

来源:328. 奇偶链表

思路:定义2个链表p和q,分别记录奇数位置节点和偶数位置节点。

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        ListNode *p = new ListNode(0);
        ListNode *ptail = p;
        ListNode *q = new ListNode(0);
        ListNode *qtail = q;

        int cnt = 1;
        while (head) {
            ListNode *next = head->next;
            head->next = NULL;
            if (cnt++ % 2 == 1) {
                ptail->next = head;
                ptail = head;
            } else {
                qtail->next = head;
                qtail = head;
            }
            head = next;
        }
        ptail->next = q->next;
        return p->next;
    }
};

16,链表入环的第一个节点

来源:142. 环形链表 II

不正常思路1:利用节点地址的大小,先申请的地址小,后申请的地址大。下一个节点地址小,则返回下一个节点。

struct ListNode *detectCycle(struct ListNode *head) {
    if (head == NULL) return NULL;
    while (head) {
        if (head >= head->next) return head->next;
        head = head->next;
    }
    return NULL;
}

不正常思路2:遍历链表,修改节点值,如果是正数,则+100000,否则-100000。当遇到>100000或<-100000的节点即为结果。

struct ListNode *detectCycle(struct ListNode *head) {
    int v = 1000000;
    while (head) {
        if (head->val < -v || head->val > v) {
            return head;
        } else {
            head->val += head->val < 0 ? -v : v;
            head = head->next;
        }
    }
    return NULL;
}

思路:讨论有环的情况,借用官方题解图如下,

fig1

定义快慢指针,快指针每次移动2个节点,慢指针每次移动1个节点,二者相遇于紫色点。

假如快指针沿着环走了n圈,则快指针移动节点数量:a + b + (b + c) * n。

假如慢指针沿着环走了m圈,则慢指针移动节点数量:a + b + (b + c) * m。

快指针移动数量是慢指针的2倍,所以:

a + b + (b + c) * n = 2 (a + b + (b + c) * m)

n(b + c) = a + b + 2m(b + c)

a + b = (n - 2m) (b + c)

n-2m必定是一个整数,假设为k,则:

a + b = k (b + c)

a - c = k(b+c) - b - c

a - c = (k-1)(b+c)

所以a-c后剩余的节点数量是环上节点数量的整数倍。

当快慢指针相遇时,定义一个指针p从head开始,当p与慢指针相遇时即为所求。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *fast = head;
        ListNode *slow = head;
        while (fast && fast->next) {
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow) { // 有环时判断
                ListNode *p = head;
                while (p != slow) {
                    p = p->next;
                    slow = slow->next;
                }
                return p;
            }
        }
        return NULL;
    }
};

17,两链表相加

来源:面试题 02.05. 链表求和

思路:类大数相加,注意进位。

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *result = new ListNode(0);
        ListNode *tail = result;

        int flag = 0; // 进位
        while (l1 || l2) { // 一方为空时继续相加
            int s = flag;
            if (l1) s += l1->val;
            if (l2) s += l2->val;
            if (s > 9) {
                s = s - 10;
                flag = 1;
            } else {
                flag = 0;
            }
            ListNode *n = new ListNode(s);
            tail->next = n;
            tail = n;

            if (l1) l1 = l1->next;
            if (l2) l2 = l2->next;
        }
        if (flag) {
            ListNode *n = new ListNode(flag);
            tail->next = n;
        }
        return result->next;
    }
};

18,链表向右移动k个位置

来源:61. 旋转链表

思路:将链表分成前后两部分A和B,长度分别为sz-k和k,返回BA。

class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        int sz = size(head);
        if (sz < 2) return head;
        k = k % sz;
        if (k == 0) return head;

        // 链表分成两部分A和B,长度分别为sz-k和k,返回BA
        ListNode *p = head;
        int a = sz - k;
        while (--a > 0) p = p->next; // p指向A的最后一个节点
        ListNode *result = p->next;
        p->next = NULL;
        // 链接B和A
        p = result;
        while (p->next) p = p->next;
        p->next = head;
        return result;
    }
    int size(ListNode *p) {
        int sz = 0;
        while (p) {
            sz++;
            p = p->next;
        }
        return sz;
    }
};

19,根据子集分成多段

来源:817. 链表组件

思路:子集创建成hash。如果节点在子集中,则计数并跳过其后在子集中的节点

class Solution {
public:
    int numComponents(ListNode* head, vector<int>& nums) {
        vector<int> g(10000, false);
        for (auto n : nums) g[n] = true;
        int result = 0;
        while (head) {
            if (g[head->val]) { // 如果节点在子集中,则计数并跳过其后在子集中的节点
                while (head && g[head->val]) {
                    head = head->next;
                }
                result++;
            } else {
                head = head->next;
            }
        }
        return result;
    }
};

20,链表替换另一链表的部分

来源:1669. 合并两个链表

思路:尾插法创建新链表。注意a,b区间。

class Solution {
public:
    ListNode* mergeInBetween(ListNode* list1, int a, int b, ListNode* list2) {
        ListNode *result = new ListNode(0);
        ListNode *tail = result;
        int cnt = 0;
        while (cnt++ < a) {
            tail->next = list1;
            tail = list1;
            list1 = list1->next;
        }
        while (list2) {
            tail->next = list2;
            tail = list2;
            list2 = list2->next;
        }
        while (cnt++ <= b) {
            list1 = list1->next;
        }
        tail->next = list1->next;
        return result->next;
    }
};

21,第k个节点与倒数第k个节点交换

来源:1721. 交换链表中的节点

思路:利用题5计算倒数第k个节点,然后正数第k个节点,交换。

class Solution {
public:
    ListNode* swapNodes(ListNode* head, int k) {
        ListNode *second = getKthFromEnd(head, k);
        ListNode *first = head;
        while (--k > 0) first = first->next;
        swap(first->val, second->val);
        return head;
    }
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode *first = head;
        while (k-- > 0) first = first->next;
        ListNode *second = head;
        while (first) {
            first = first->next;
            second = second->next;
        }
        return second;
    }
};

22,链表排序

要求:时间复杂度O(N logN),空间复杂度O(1)

来源:148. 排序链表

思路:递归不满足空间复杂度O(1),所以不能使用递归。要想满足时间复杂度,可使用归并排序。

长度为k的两个有序链表合并为长度为2K的链表,k=1,2,4,8...

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        int sz = size(head);

        ListNode *result = new ListNode(0, head);
        for (int k = 1; k < sz; k *= 2) {
            ListNode *prev = result;
            ListNode *curr = result->next;
            while (curr) {
                // 将curr链表分成3部分:head1, head2, left
                // head1和head2长度最大为k,left为剩余节点
                ListNode *head1 = curr;
                ListNode *head2 = NULL;
                ListNode *left = NULL;
                for (int i = 1; i < k && curr; i++) curr = curr->next;
                
                if (curr) {
                    head2 = curr->next;
                    curr->next = NULL; // 让head1有尾节点
                    
                    curr = head2;
                    for (int i = 1; i < k && curr; i++) curr = curr->next;
                
                    if (curr) {
                        left = curr->next; // 剩余节点
                        curr->next = NULL;
                    }
                }

                ListNode *merged = mergeTwoLists(head1, head2);
                prev->next = merged;
                prev = getTail(merged);

                curr = left;
            }
        }
        return result->next;
    }
    void print(char *title, ListNode *h) {
        printf("%s:", title);
        while (h) {
            printf("%d ", h->val);
            h = h->next;
        }
        printf("\n");
    }
    int size(ListNode *p) {
        int sz = 0;
        while (p) {
            sz++;
            p = p->next;
        }
        return sz;
    }
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode *result = new ListNode(0);
        ListNode *tail = result;
        while (l1 && l2) {
            if (l1->val < l2->val) {
                ListNode *next = l1->next;
                l1->next = NULL;
                tail->next = l1;
                tail = l1;
                l1 = next;
            } else {
                ListNode *next = l2->next;
                l2->next = NULL;
                tail->next = l2;
                tail = l2;
                l2 = next;
            }
        }
        if (l1) tail->next = l1;
        else tail->next = l2;
        return result->next;
    }
    ListNode *getTail(ListNode *l) {
        if (l == NULL) return NULL;
        while (l->next) l = l->next;
        return l;
    }
};

23,复制复杂链表

来源:剑指 Offer 35. 复杂链表的复制  &&  138. 复制带随机指针的链表

思路1:定义一个map,key是原节点地址,value是复制的新节点地址。value.random = first.random.second。

思路2:将复制的新节点挂接到原节点后,行程一个新的节点:原节点1 - 新节点1 - 原节点2 - 新节点2 - 原节点3 - 新节点3 。。。,新节点random=原节点random->next,最后拆分链表。

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (head == NULL) return NULL;
        Node *p = head;
        // 复制节点,接到原节点后
        while (p) {
            Node* tmp = new Node(p->val);
            tmp->next = p->next;
            p->next = tmp;
            p = tmp->next;
        }
        // 更新random指针
        p = head;
        while (p) {
            if (p->random) p->next->random = p->random->next;
            p = p->next->next;
        }
        // 拆分两链表
        Node *prev = head;
        Node *curr = head->next;
        Node *result = curr;
        while (curr->next) {
            prev->next = prev->next->next;
            curr->next = curr->next->next;
            prev = prev->next;
            curr = curr->next;
        }
        prev->next = NULL;
        return result;
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值