LeetCode题解之链表(一)

160 阅读7分钟

1.链表基础知识

LeetCode的链表基本上是结构体实现的。

 * 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) {}
 * };


分类:单链表、双链表
操作类型:插入、删除、修改、查询,凡是头节点head可能被操作的题目,通常设置dummy虚拟节点,可以减少边界情况~

2.合并类题目

21. 合并两个有序链表(剑指 Offer 25. 合并两个排序的链表)

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        auto dummy = new ListNode(-1);//虚拟头节点
        auto p1 = l1, p2 = l2, tmp = dummy;
        while(p1 && p2)
        {
            if(p1->val <= p2->val) {tmp->next = p1; p1 = p1->next;}
            else {tmp->next = p2; p2 = p2->next;}
            tmp = tmp->next;
        }
        if(p1) tmp->next = p1;
        if(p2) tmp->next = p2;
        return dummy->next;
    }
};

23. 合并K个升序链表

归并思想,借助两两合并

class Solution {
public:
    //两个链表合并,返回链表头
    ListNode* mergeTwoLists(ListNode* l1,ListNode* l2)
    {
        auto dummy = new ListNode(-1);//虚拟头节点
        auto p1 = l1, p2 = l2, tmp = dummy;
        while(p1 && p2)
        {
            if(p1->val <= p2->val) {tmp->next = p1; p1 = p1->next;}
            else {tmp->next = p2; p2 = p2->next;}
            tmp = tmp->next;
        }
        if(p1) tmp->next = p1;
        if(p2) tmp->next = p2;
        return dummy->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) {
     if(lists.size() < 1) return nullptr;
     return merge(lists, 0,lists.size() - 1);  
    }
};

3.删除类题目

一般而言,删除某个节点需要找到前驱节点~可以借助两个指针

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

此题保证了n有效

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        //删除倒数第n个,那就找倒数第n+1个
        auto dummy = new ListNode(-1);
        dummy ->next = head;
        auto p1 = dummy, p2 = dummy;
        while(n -- ) p1 = p1->next;//first往后走n步,
        while(p1->next)//second指向倒数第n+1个点
        {
            p1 = p1->next;
            p2 = p2->next;
        }
        p2->next = p2->next->next;
        return dummy->next;
    }
};

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

另外返回倒数第k个节点的题目,k可能大于链表总数,需要判断:p1走k-1步后若为空,说明整个就不足k个,返回头节点即可

class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        //找倒数第k个
        auto p1 = head, p2 = head;
        //不用设置虚拟节点,多了以下一个判断而已
        k --;
        while(k -- && p1 ) p1 = p1->next;
        if(p1 == nullptr) return head;
        
        while(p1->next)//second指向倒数第n+1个点
        {
            p1 = p1->next;
            p2 = p2->next;
        }
        return p2;

    }
};

剑指 Offer 18. 删除链表的节点

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        auto dummy = new ListNode(-1);
        dummy->next = head;
        auto p1 = dummy;
        //找要删除的节点的前一个节点,所以用p1->next判断是不是找到了
        while(p1->next && p1->next->val != val) 
            p1 = p1->next;
        //while停止循环的两种可能:找到了,为空了
        if(p1->next->val == val) p1->next = p1->next->next;
        return dummy->next; 
    }
};

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

这个题可以保留第一个出现的元素,所以比较简单,目的还是找需要删除节点的上一个节点,遍历的时候判断next的val和当前是否相同,相同则删掉

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        //采取保留第一个元素的方法:遍历每个元素,和下一个相同则删除下一个,不同则移动到下一个
        ListNode* cur = head;
        while(cur)
        {
            ListNode* tmp = cur->next;
            while( tmp && tmp->val == cur->val) tmp = tmp->next;
            cur->next = tmp;
            cur = cur->next;
        }
        return head;
    }
};

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

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        //1.dummy节点;2.扫描一段节点,个数>1则全部删除,=1则遍历移动
        ListNode* dummy = new ListNode(-1);
        dummy->next = head;
        ListNode* cur = dummy;
        while(cur->next)//每次扫描后一段节点
        {
            ListNode* tmp = cur->next;
            while(tmp && tmp->val == cur->next->val) tmp = tmp->next;
            if(tmp == cur->next->next) 
            {
                cur = cur->next;
            }
            else cur->next = tmp;
        }
        return dummy->next;
    }
};

4.有环链表类题目

141. 环形链表

class Solution {
public:
    //方法一:hash表,如果遍历到已经存在的点,就是有环,否则能遍历到null就是无
    bool hasCycle(ListNode *head) {
        //1.链表为空或者只有一个元素;false
        if(!head || !head->next) return false;//空节点或者只有一个节点
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast)
        {
            fast = fast->next;
            slow = slow->next;
            if(fast) fast = fast->next;
            if(fast == slow) break; 
        } 
        if(fast) return true;
        else return false;
    }
};

142. 环形链表 II

在上一道题基础上,多了相遇后继续走的过程,可以严格证明的

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        auto fast = head,slow = head;
        //第一部分和上一道题完全一样~
        while(fast)
        {
            fast = fast->next;
            slow = slow ->next;
            if(fast) fast = fast->next;
            else break;
            //在环内第一次相遇后,让fast回到起点继续走,走a后两个肯定都到达入口点
            if(fast == slow)
            {
                fast = head;
                while(slow!=fast)
                {
                    slow = slow->next;
                    fast = fast->next;
                }
                return slow;
            }
        }
        return NULL;
    }
};

5.相交链表类题目

160. 相交链表(剑指 Offer 52. 两个链表的第一个公共节点)

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        //让p走a1a2+c1c2c3+b1b2b3
        //让q走b1b2b3+c1c2c3+a1a2
        //然后两个链表走完所有点后肯定会在c1相遇,如果不相交,c1就为null,也直接返回就好了
        auto p = headA, q = headB;
        while(p != q)  
        {
            if(p) p = p->next;
            else p = headB;
            if(q) q = q->next;
            else q = headA;
        }
        return p;
    }
};

6.调整顺序类题目

206. 反转链表(剑指 Offer 24. 反转链表)(剑指 Offer 06. 从尾到头打印链表)

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head==nullptr || head->next == nullptr) return head;
        ListNode* pre = nullptr;
        auto cur = head;
        while(cur)
        {
            auto next = cur->next;
            cur->next = pre;
            pre = cur,cur = next;
        }
        return pre;
    }
};

92. 反转链表 II

在上一题基础上主要是多了寻找区间和保留节点的步骤~

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        if(m == n) return head;
        auto dummy = new ListNode(-1);
        dummy->next = head;
        //找ad两点
        auto a = dummy, d = dummy;
        m --;
        while(m --) a = a->next;
        while(n --) d = d->next;
        //找bc两点
        auto b = a->next;
        auto c = d->next;
        //修改b->next开始到d的指针,用pre和cur变量
        auto pre = b, cur = b->next;
        while(cur != c)
        {
            auto next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        //最后连接起来
        a->next = d;
        b->next = c;
        return dummy->next;
    }
};

61. 旋转链表

class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        if(!head || !head->next) return head;
        //先求总长度,k%n
        int n = 0;
        for(auto tmp = head;tmp;tmp = tmp->next) n ++;
        k %= n;
        //找倒数第k+1个点和最后一个点
        auto p1 = head,p2 = head;
        while(k --) p1 = p1->next;//先走k步
        while(p1->next)
        {
            p1 = p1->next;
            p2 = p2->next;
        }
        //p2在倒数第k+1个点,p1在最后一个点
        p1->next = head;
        head = p2->next; 
        p2->next = nullptr; 
        return head;

    }
};

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

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        auto dummy = new ListNode(-1);
        dummy ->next = head;
        for(auto p = dummy; p && p->next && p->next->next;)
        {//p和要交换的一对ab一起,移动时p要移动到下一对ab的前面,也就是直接p=a
            auto a = p->next, b = a->next;
            p->next = b;
            a->next = b->next;
            b->next = a;
            p = a;
        }
        return dummy->next;
    }
};

234. 回文链表

class Solution {
public:
/*
方法一:用数组存储再用首尾指针判断:时间o(n)空间o(n)
方法二:由于只需要o(1)空间,所以是两个指针同时移动和比较,需要翻转后一半的链表:用快慢指针找到中间的点,翻转中间点以后的点
*/
    bool isPalindrome(ListNode* head) {
        if(!head || !head->next) return true;
        ListNode* fast = head;//找中间节点
        ListNode* slow = head;
        while(fast)
        {
            fast = fast->next;
            slow = slow->next;
            if(fast) fast = fast->next;
        }
        ListNode* pre = nullptr;//旋转右边的链表
        ListNode* cur = slow;
        while(cur)
        {
            ListNode* tmp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = tmp;
        }
        ListNode* p1 = pre;//逐个比较
        ListNode* p2 = head;
        while(p1 && p2)
        {
            if(p1->val != p2->val) return false;
            else
            {
                p1 = p1->next;
                p2 = p2->next;
            }
        }
        return true;
    }
};

7.其余简单顺序题目

2. 两数相加

从低位开始加,用t存储往前的进位即可,最后还要判断是否有进位1

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        if(!l1 && !l2) return nullptr;
        auto p1 = l1,p2 = l2;
        auto dummy = new ListNode(-1);
        auto head = dummy;
        int t = 0;
        while(p1 || p2)
        {
            int x = t;
            if(p1) x += p1->val,p1 = p1->next;
            if(p2) x += p2->val,p2 = p2->next;
            auto tmp = new ListNode(x % 10);
            head -> next = tmp;
            head = tmp;
            t = x / 10;
        }
        if(t) {auto tmp = new ListNode(1); head->next = tmp;}
        return dummy->next;
    }
};

328. 奇偶链表

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        //e找偶数点,o找奇数点,两个互相借助,最后拼接头节点
        if(!head || !head->next) return head;
        ListNode* ehead = head->next;
        ListNode* e = ehead;
        ListNode* o = head;
        while(e && e->next)
        {
            o->next = e->next;
            o = o->next;
            e->next = o->next;
            e = e->next;
        }
        o->next = ehead;//奇前偶后
        return head;

    }
};