leetcode刷题 链表 C++ 23个(剑指offer 8个)

目录

数据结构与算法

面试注意点

练习

 链表

基本技能

常见题型

​83. 删除排序链表中的重复元素:remove-duplicates-from-sorted-list​

82. 删除排序链表中的重复元素 II remove-duplicates-from-sorted-list-ii​

206. 反转链表:reverse-linked-list​

剑指 Offer 24. 反转链表

92. 反转链表 II:reverse-linked-list-ii​

​25. K 个一组翻转链表

21. 合并两个有序链表merge-two-sorted-lists​

剑指 Offer 25 合并两个排序的链表  

​86. 分隔链表:partition-list​

​148. 排序链表sort-list​ 复杂​

143. 重排链表reorder-list​

​141. 环形链表linked-list-cycle​

142 ​环形链表 Ⅱ linked-list-cycle-ii​

剑指offer 牛客

138. 复制带随机指针的链表copy-list-with-random-pointer​

剑指 Offer 35. 复杂链表的复制

剑指offer

剑指 Offer 06. 从尾到头打印链表

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

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

总结

练习

1. 两数之和

方法二:哈希表

2. 两数相加 

160 相交链表(easy)

剑指 Offer 52. 两个链表的第一个公共节点

19 删除链表的倒数第N个结点(medium)

203 移除链表元素(easy)

328 奇偶链表

430 扁平化多级双向链表 还没看

61 旋转链表(medium)

数据结构与算法

数据结构是一种数据的表现形式,如链表、二叉树、栈、队列等都是内存中一段数据表现的形式。 算法是一种通用的解决问题的模板或者思路,大部分数据结构都有一套通用的算法模板,所以掌握这些通用的算法模板即可解决各种算法问题。

后面会分专题讲解各种数据结构、基本的算法模板、和一些高级算法模板,每一个专题都有一些经典练习题,完成所有练习的题后,你对数据结构和算法会有新的收获和体会。

先介绍两个算法题,试试感觉~

示例 1

​strStr​

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从 0 开始)。如果不存在,则返回 -1。

思路:核心点遍历给定字符串字符,判断以当前字符开头字符串是否等于目标字符串

需要注意点
  • 循环时,i 不需要到 len-1

  • 如果找到目标字符串,len(needle)==j

示例 2

​subsets​

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

思路:这是一个典型的应用回溯法的题目,简单来说就是穷尽所有可能性,算法模板如下

通过不停的选择,撤销选择,来穷尽所有可能性,最后将满足条件的结果返回

说明:后面会深入讲解几个典型的回溯算法问题,如果当前不太了解可以暂时先跳过

面试注意点

我们大多数时候,刷算法题可能都是为了准备面试,所以面试的时候需要注意一些点

  • 快速定位到题目的知识点,找到知识点的通用模板,可能需要根据题目特殊情况做特殊处理

  • 先去朝一个解决问题的方向!先抛出可行解,而不是最优解!先解决,再优化!

  • 代码的风格要统一,熟悉各类语言的代码规范。

    • 命名尽量简洁明了,尽量不用数字命名如:i1、node1、a1、b2

  • 常见错误总结

    • 访问下标时,不能访问越界

    • 空值 nil 问题 run time error

练习

  • ​strStr​

  • ​subsets​

 链表

基本技能

链表相关的核心点

  • null/nil 异常处理

  • dummy node 哑巴节点

  • 快慢指针

  • 插入一个节点到排序链表

  • 从一个链表中移除一个节点

  • 翻转链表

  • 合并两个链表

  • 找到链表的中间节点

常见题型

​83. 删除排序链表中的重复元素:remove-duplicates-from-sorted-list​

83. 删除排序链表中的重复元素:模拟题,直接遍历链表,遇到重复值的节点删除即可。

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 

/*解法一:递归 
主要思想:
链表具有天然的递归性,可以把一个链表看成头节点后挂接一个更短的链表,
这个更短的链表看成其的头节点后面挂接一个更更短的链表,依次类推。
所以可以先处理头节点后面挂接的更短的链表,处理完之后,
判断头节点的值是否等于其挂接的更短的链表的头节点的值,
如果相等则直接返回更短的链表的头节点,
否则返回原链表的头节点。

边界条件
1、 链表为空;

2、 链表只有头节点。

 */

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

        // 删除头节点后面挂接的链表中的重复元素
        head->next = deleteDuplicates(head->next);
        
        //  头节点与后面挂接的链表中的节点值相同,则头节点也删除,否则不删除
        return head->val == head->next->val ? head->next : head;
    }        
};

/*解法二:直接法(单指针)
由于题目已经明确说了是排序链表,所以只要挨个比较相邻节点的值是否相等就可以了,相等就通过将当前节点指向其后继节点的后继节点去删除后继节点,否则继续查找。*/

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
    if (head == NULL || head->next == NULL) {  //  特判
        return head;
    }

    ListNode* cur = head;  //  当前节点
    while (cur != NULL && cur->next != NULL) {  //  一直遍历,直到当前节点为空或者当前节点是尾节点
        if (cur->val == cur->next->val) {       //  当前节点的值与其后继节点的值相同,则删除当前节点的后继节点
            cur->next = cur->next->next;
        } else {                                //  否则继续遍历
            cur = cur->next;
        }
    }

    return head;    
    }
};




/*解法二:直接法(双指针)
和leetcode 26. 删除排序数组中的重复项  思路一样
*/

和
ListNode deleteDuplicates(ListNode head) {
    if (head == null) return null;
    ListNode slow = head, fast = head;
    while (fast != null) {
        if (fast.val != slow.val) {
            // nums[slow] = nums[fast];
            slow.next = fast;
            // slow++;
            slow = slow.next;
        }
        // fast++
        fast = fast.next;
    }
    // 断开与后面重复元素的连接
    slow.next = null;
    return head;
}

82. 删除排序链表中的重复元素 II remove-duplicates-from-sorted-list-ii​

82. 删除排序链表中的重复元素 II:模拟题,遍历链表,若head的节点值与head的next节点值不相等,则pre指向head,也就是不重复节点;若相等,我们需要找到重复值子链表的最后一个节点,然后令pre指向head->next,同时head移动到下一个节点。

思路:链表头结点可能被删除,所以用 dummy node 辅助删除

给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现的数字。

示例 1:
输入: 1->2->3->3->4->4->5
输出: 1->2->5

示例 2:
输入: 1->1->1->2->3
输出: 2->3

 

方法一:迭代。先放C++代码,思路清晰明了。

https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/solution/zi-jie-ti-ku-82-zhong-deng-shan-chu-pai-xu-lian-bi/

/**
 * 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* deleteDuplicates(ListNode* head) {
        if (head==NULL ||head->next==NULL){
            return head;
        }
        ListNode *dummy=new ListNode(INT_MAX);
        dummy->next=head;
        ListNode *a=dummy;
        ListNode *b=head;
        while (b && b->next)
        {
            if (a->next->val!=b->next->val)
            {
                a=a->next;
                b=b->next;
            }
            else
            {
                while(b && b->next && a->next->val==b->next->val)
                  {
                        b=b->next;
                  }
                a->next=b->next;
                b=b->next;
            }

        }
        return dummy->next;
}
};

方法二:递归。先放C++代码,思路清晰明了。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if(!head || !head->next) return head;
        if(head->next->val != head->val)
        {
            // 如果head后面一位的元素的值不等于head的值,就从head->next开始接着处理后面的链表
            head -> next = deleteDuplicates(head -> next);
            return head;
        } 
        else
        {
            // 如果head后面一位的元素的值等于head的值,就跳过所有与head的值相等的元素,从第一个不等于head值的元素开始处理
            ListNode *curr = head;
            while(curr->next && curr->next->val==curr->val) curr = curr -> next;
            return deleteDuplicates(curr -> next);
        }
    }
};

206. 反转链表reverse-linked-list

剑指 Offer 24. 反转链表

206. 反转链表:双指针法,指针pre用来表示前驱节点,指针cur用来遍历链表,每次循环改变将pre->cur的方向改变为pre<-cur,直到遍历结束。

反转一个单链表。

思路:用一个 prev 节点保存向前指针,temp 保存向后的临时指针

https://leetcode-cn.com/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-by-leetcode-solution-d1k2/

https://mp.weixin.qq.com/s?__biz=MzAxODQxMDM0Mw==&mid=2247484467&idx=1&sn=beb3ae89993b812eeaa6bbdeda63c494&chksm=9bd7fa3baca0732dc3f9ae9202ecaf5c925b4048514eeca6ac81bc340930a82fc62bb67681fa&scene=21#wechat_redirect

方法一:迭代

复杂度分析

  • 时间复杂度:O(n),其中 n 是链表的长度。需要遍历链表一次。

  • 空间复杂度:O(1)。

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

方法二:递归

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

复杂度分析

时间复杂度:O(n),其中 n 是链表的长度。需要对链表的每个节点进行反转操作。

空间复杂度:O(n),其中 n 是链表的长度。空间复杂度主要取决于递归调用的栈空间,最多为 n 层。

92. 反转链表 II:reverse-linked-list-ii​

92. 反转链表 II:双指针法,指针pre指针指向m的前驱节点,用来将cur的next节点插入到pre后面,指针cur指向位置m起始节点,该节点保持不变,每次需要将cur连接上nxt后边的部分。换句话说,我们要将[m+1,n]的节点每次都要插到位置m之前,这样就完成了反转。

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

思路:先遍历到 m 处,翻转,再拼接后续,注意指针处理

# recurse 递归
# Reverse 反转,颠倒
回溯法(Backtrcking)
读音huí sù,也叫试探法,它是一种系统地搜索问题的解的方法,是一种思想。
有一类问题,我们不知道它明确的计算法则。而是先进行试探,试探到最终状况,发现不满足问题的要求,则回溯到上一个状态继续试探。这种不断试探和回溯的思想,称为回溯法(Backtrcking)
这类问题有求最优解、一组解、求全部解这类问题,例如八皇后问题
【回溯的算法思想】一直往下走,然后再一步步往回走
面对一个问题,每一步有多种做法。先做其中一个做法,然后再此基础上一直做下去,把这个做法的所有可能全部做完,再回来,做第二种做法。

【例子】

深度优先搜索
求一个序列的幂集
八皇后问题
涂色问题

【回溯法实质】它的求解过程实质上是先序遍历一棵“状态树”的过程。
只不过,这棵树不是遍历前预先建立的,而是隐含在遍历过程中。
如果认识到这点,很多问题的递归过程设计也就迎刃而解了。
https://blog.csdn.net/summer_dew/article/details/83921581

解法:

https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/fan-zhuan-lian-biao-ii-by-leetcode/

 /**
 * 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:
    //翻转前N个链表
    ListNode* successor=nullptr;
    
    ListNode* reverseN(ListNode* head,int n) {
        if (n==1) {
            successor=head->next;
            return head;
        }
        ListNode* last= reverseN(head->next,n-1);
        head->next->next = head;
        head->next =successor;
        return last;
    }
    //反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        if (left==1)
        {
            return reverseN(head,right);
        }
        head->next=reverseBetween(head->next,left-1,right-1);
        return head;
    }
};


  1. 方法二: 迭代链接反转:未通过编译

  2. 复杂度分析

  3. 时间复杂度: O(N)。考虑包含 N 个结点的链表。对每个节点最多会处理(第 n个结点之后的结点不处理)。

  4. 空间复杂度: O(1)。我们仅仅在原有链表的基础上调整了一些指针,只使用了 O(1)的额外存储空间来获得结果。

/**
 * 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* reverseBetween(ListNode* head, int m, int n) {
        if(!head) return NULL;
        ListNode* dummy=new ListNode(-1);
        dummy->next=head;
        ListNode* a=dummy;
        ListNode* d=head;
        while(m>1)
        {
            a=d;
            d=d->next;
            m=m-1;
            n=n-1;
        }

       ListNode* b=a;
       ListNode* c=d;

        while (n)
        {
            ListNode* third=d->next;
            d->next=a;
            a=d;
            d=third;
            n=n-1;

        }
        if(b)
        {
            b->next = a;

        }
        else
        {
            head = a;
            c->next=d;
        }
        return dummy->next;
    }
};

​25. K 个一组翻转链表

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

示例:

给你这个链表:1->2->3->4->5

当 k = 2 时,应当返回: 2->1->4->3->5

当 k = 3 时,应当返回: 3->2->1->4->5

https://mp.weixin.qq.com/s?__biz=MzAxODQxMDM0Mw==&mid=2247484597&idx=1&sn=c603f1752e33cb2701e371d84254aee2&chksm=9bd7fabdaca073abd512d8fff18016c9092ede45fed65c307852c65a2026d8568ee294563c78&scene=21#wechat_redirect

 /**
 * 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) {
        if (head == NULL) return NULL;
        ListNode *a = head;
        ListNode *b = head;
        for (int i = 0; i < k; i++) {
            if (b == NULL) return head;
            b = b->next;
        }

        ListNode *newNode = reverse(a,b);
        a->next = reverseKGroup(b,k);
        return newNode;
    }

    ListNode* reverse(ListNode* a,ListNode *b) {
        ListNode *pre, *cur, *third;
        pre = NULL; cur = a; third = a;
        while (cur != b) {
            third = cur->next;
            cur->next = pre;
            pre = cur;
            cur = third;
        }
        return pre;
    }
};

21. 合并两个有序链表merge-two-sorted-lists

剑指 Offer 25 合并两个排序的链表  

21. 合并两个有序链表:模拟题,每次循环比较l1->vall2->val,若l1->valval,则在cur后面添加l1;否则在cur后面添加l2

将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

思路:通过 dummy node 链表,连接各个元素

//递归
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;
        }
    }
};
//迭代:
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* preHead = new ListNode(-1);

        ListNode* prev = preHead;
        while (l1 != nullptr && l2 != nullptr) {
            if (l1->val < l2->val) {
                prev->next = l1;
                l1 = l1->next;
            } else {
                prev->next = l2;
                l2 = l2->next;
            }
            prev = prev->next;
        }

        // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
        prev->next = l1 == nullptr ? l2 : l1;

        return preHead->next;
    }
};

​86. 分隔链表:partition-list​

86. 分隔链表:双指针法

before_head链表存放比x小的节点,after_head链表存放比x大于或等于的节点,我们分别用before和after来前面两个链表添加节点,用head来遍历原始链表。当原始链表遍历完成时,我们需要将before_head链表连接上after_head链表,即before->next=after_head->next;after->next=nullptr;。

给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。

思路:将大于 x 的节点,放到另外一个链表,最后连接这两个链表

哑巴节点使用场景

当头节点不确定的时候,使用哑巴节点

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;
    }
};

​148. 排序链表sort-list​ 复杂

148. 排序链表:归并排序,先2个2个的 merge,完成一趟后,再 4个4个的 merge,直到结束。

在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。

思路:归并排序,找中点和合并操作

/*自顶向下归并排序
复杂度分析

时间复杂度:O(n \log n)O(nlogn),其中 nn 是链表的长度。

空间复杂度:O(\log n)O(logn),其中 nn 是链表的长度。空间复杂度主要取决于递归调用的栈空间。
*/
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        return sortList(head, nullptr);
    }

    ListNode* sortList(ListNode* head, ListNode* tail) {
        if (head == nullptr) {
            return head;
        }
        if (head->next == tail) {
            head->next = nullptr;
            return head;
        }
        ListNode* slow = head, *fast = head;
        while (fast != tail) {
            slow = slow->next;
            fast = fast->next;
            if (fast != tail) {
                fast = fast->next;
            }
        }
        ListNode* mid = slow;
        return merge(sortList(head, mid), sortList(mid, tail));
    }

    ListNode* merge(ListNode* head1, ListNode* head2) {
        ListNode* dummyHead = new ListNode(0);
        ListNode* temp = dummyHead, *temp1 = head1, *temp2 = head2;
        while (temp1 != nullptr && temp2 != nullptr) {
            if (temp1->val <= temp2->val) {
                temp->next = temp1;
                temp1 = temp1->next;
            } else {
                temp->next = temp2;
                temp2 = temp2->next;
            }
            temp = temp->next;
        }
        if (temp1 != nullptr) {
            temp->next = temp1;
        } else if (temp2 != nullptr) {
            temp->next = temp2;
        }
        return dummyHead->next;
    }
};

/*
方法二:自底向上归并排序
复杂度分析

时间复杂度:O(n \log n)O(nlogn),其中 nn 是链表的长度。

空间复杂度:O(1)O(1)。
*/
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if (head == nullptr) {
            return head;
        }
        int length = 0;
        ListNode* node = head;
        while (node != nullptr) {
            length++;
            node = node->next;
        }
        ListNode* dummyHead = new ListNode(0, head);
        for (int subLength = 1; subLength < length; subLength <<= 1) {
            ListNode* prev = dummyHead, *curr = dummyHead->next;
            while (curr != nullptr) {
                ListNode* head1 = curr;
                for (int i = 1; i < subLength && curr->next != nullptr; i++) {
                    curr = curr->next;
                }
                ListNode* head2 = curr->next;
                curr->next = nullptr;
                curr = head2;
                for (int i = 1; i < subLength && curr != nullptr && curr->next != nullptr; i++) {
                    curr = curr->next;
                }
                ListNode* next = nullptr;
                if (curr != nullptr) {
                    next = curr->next;
                    curr->next = nullptr;
                }
                ListNode* merged = merge(head1, head2);
                prev->next = merged;
                while (prev->next != nullptr) {
                    prev = prev->next;
                }
                curr = next;
            }
        }
        return dummyHead->next;
    }

    ListNode* merge(ListNode* head1, ListNode* head2) {
        ListNode* dummyHead = new ListNode(0);
        ListNode* temp = dummyHead, *temp1 = head1, *temp2 = head2;
        while (temp1 != nullptr && temp2 != nullptr) {
            if (temp1->val <= temp2->val) {
                temp->next = temp1;
                temp1 = temp1->next;
            } else {
                temp->next = temp2;
                temp2 = temp2->next;
            }
            temp = temp->next;
        }
        if (temp1 != nullptr) {
            temp->next = temp1;
        } else if (temp2 != nullptr) {
            temp->next = temp2;
        }
        return dummyHead->next;
    }
};

143. 重排链表reorder-list​

143. 重排链表:首尾指针法,首先将原始链表的每一个节点存放在一个数组中,然后我们取首尾指针向中间遍历,每次循环我们需要将左指针的节点连上右指针的节点,在节点连上之后,我们需要将右指针连上未排序的首节点。

给定一个单链表 LLL→…→L__nL 将其重新排列后变为: LL__nLL__nLL__n→…

思路:找到中点断开,翻转后面部分,然后合并前后两个链表

class Solution {
public:
    void reorderList(ListNode* head) {
        if (head == nullptr) {
            return;
        }
        ListNode* mid = middleNode(head);
        ListNode* l1 = head;
        ListNode* l2 = mid->next;
        mid->next = nullptr;
        l2 = reverseList(l2);
        mergeList(l1, l2);
    }

    ListNode* middleNode(ListNode* head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast->next != nullptr && fast->next->next != nullptr) {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }

    ListNode* reverseList(ListNode* head) {
        ListNode* prev = nullptr;
        ListNode* curr = head;
        while (curr != nullptr) {
            ListNode* nextTemp = curr->next;
            curr->next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
    }

    void mergeList(ListNode* l1, ListNode* l2) {
        ListNode* l1_tmp;
        ListNode* l2_tmp;
        while (l1 != nullptr && l2 != nullptr) {
            l1_tmp = l1->next;
            l2_tmp = l2->next;

            l1->next = l2;
            l1 = l1_tmp;

            l2->next = l1;
            l2 = l2_tmp;
        }
    }
};

​141. 环形链表linked-list-cycle​

141. 环形链表:快慢指针法,若存在环最终快慢指针会相遇;若不存在环,那么快指针一定会先走到链表尾部。

给定一个链表,判断链表中是否有环。

思路:快慢指针,快慢指针相同则有环,证明:如果有环每走一步快慢指针距离会减 1 leetcode刷题 链表 C++ 23个(剑指offer 8个)_第1张图片

/**
 * 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;
        while (fast != nullptr && fast->next != nullptr) {
            
            slow = slow->next;
            fast = fast->next->next;
            if (fast == slow) {
                return true;
            }
        }
        return false;
    }
};

142 ​环形链表 Ⅱ linked-list-cycle-ii​

剑指offer 牛客

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

思路:快慢指针,快慢相遇之后,慢指针回到头,快慢指针步调一致一起移动,相遇点即为入环点 leetcode刷题 链表 C++ 23个(剑指offer 8个)_第2张图片

 class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *slow = head, *fast = head;
        while (fast != nullptr) {
            slow = slow->next;
            if (fast->next == nullptr) {
                return nullptr;
            }
            fast = fast->next->next;
            if (fast == slow) {
                ListNode *ptr = head;
                while (ptr != slow) {
                    ptr = ptr->next;
                    slow = slow->next;
                }
                return ptr;
            }
        }
        return nullptr;
    }
};

234. 回文链表palindrome-linked-list

234. 回文链表:快慢指针法,快指针走两步,慢指针走一步,找到链表的中点。然后,翻转后半部分。最后从前半部分链表和后半部分链表是否相同。

请判断一个链表是否为回文链表。

  1. 234. 回文链表

  2. 请判断一个链表是否为回文链表。

  3.  
  4. 示例 1:

  5.  
  6. 输入: 1->2

  7. 输出: false

  8. 示例 2:

  9.  
  10. 输入: 1->2->2->1

  11. 输出: true

  12. 进阶:

  13. 你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

  14.  
  15. 方法三:我们可以分为以下几个步骤:

  16.  
  17. 找到前半部分链表的尾节点。

  18. 反转后半部分链表。

  19. 判断是否为回文。

  20. 恢复链表。

  21. 返回结果。

  22. 链接:https://leetcode-cn.com/problems/palindrome-linked-list/solution/hui-wen-lian-biao-by-leetcode/

    class Solution {
    public:
        bool isPalindrome(ListNode* head) {
            if (head == nullptr) {
                return true;
            }
    
            // 找到前半部分链表的尾节点并反转后半部分链表
            ListNode* firstHalfEnd = endOfFirstHalf(head);
            ListNode* secondHalfStart = reverseList(firstHalfEnd->next);
    
            // 判断是否回文
            ListNode* p1 = head;
            ListNode* p2 = secondHalfStart;
            bool result = true;
            while (result && p2 != nullptr) {
                if (p1->val != p2->val) {
                    result = false;
                }
                p1 = p1->next;
                p2 = p2->next;
            }        
    
            // 还原链表并返回结果
            firstHalfEnd->next = reverseList(secondHalfStart);
            return result;
        }
    
        ListNode* reverseList(ListNode* head) {
            ListNode* prev = nullptr;
            ListNode* curr = head;
            while (curr != nullptr) {
                ListNode* nextTemp = curr->next;
                curr->next = prev;
                prev = curr;
                curr = nextTemp;
            }
            return prev;
        }
    
        ListNode* endOfFirstHalf(ListNode* head) {
            ListNode* fast = head;
            ListNode* slow = head;
            while (fast->next != nullptr && fast->next->next != nullptr) {
                fast = fast->next->next;
                slow = slow->next;
            }
            return slow;
        }
    };

138. 复制带随机指针的链表copy-list-with-random-pointer

剑指 Offer 35. 复杂链表的复制

 中等 

138. 复制带随机指针的链表:模拟题,分三步,第一步在原链表的每个节点后面拷贝出一个新的节点,第二步拷贝random,第三步断开链表。

给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。 要求返回这个链表的 深拷贝。

思路:1、hash 表存储指针,2、复制节点跟在原节点后面

https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/solution/jian-zhi-offer-35-fu-za-lian-biao-de-fu-zhi-ha-xi-/

/*
思路一:哈希
借助哈希保存节点信息。

代码
时间复杂度:O(n)
空间复杂度:O(n)
*/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head == nullptr) return nullptr;
        Node* cur = head;
        unordered_map 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];
    }
};

剑指offer

剑指 Offer 06. 从尾到头打印链表

难度简单107

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

示例 1:

输入:head = [1,3,2]
输出:[2,3,1]

C++4种解法:reverse反转法、堆栈法、递归法、改变链表结构法

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    vector res;
    vector reversePrint(ListNode* head) {
        //方法1:reverse反转法
        /*
        while(head){
            res.push_back(head->val);
            head = head->next;
        }
        //使用algorithm算法中的reverse反转res
        reverse(res.begin(),res.end());
        return res;
        */

        //方法2:入栈法
        /*
        stack s;
        //入栈
        while(head){
            s.push(head->val);
            head = head->next;
        }
        //出栈
        while(!s.empty()){
            res.push_back(s.top());
            s.pop();
        }
        return res;
        */

        //方法3:递归
        /*
        if(head == nullptr)
            return res;
        reversePrint(head->next);
        res.push_back(head->val);
        return res;
        */

        //方法4:改变链表结构
        ListNode *pre = nullptr;
        ListNode *next = head;
        ListNode *cur = head;
        while(cur){
            next = cur->next;//保存当前结点的下一个节点
            cur->next = pre;//当前结点指向前一个节点,反向改变指针
            pre = cur;//更新前一个节点
            cur = next;//更新当前结点
        }
        while(pre){//上一个while循环结束后,pre指向新的链表头
            res.push_back(pre->val);
            pre = pre->next;
        }
        return res;
    }
};

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

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

注意:此题对比原题有改动

示例 1:

  1. 输入: head = [4,5,1,9], val = 5

  2. 输出: [4,1,9]

  3. 解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

示例 2:

  1. 输入: head = [4,5,1,9], val = 1

  2. 输出: [4,5,9]

  3. 解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.

说明:

  • 题目保证链表中节点的值互不相同
  • 若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点
 class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if(head->val == val) return head->next;
        ListNode *pre = head, *cur = head->next;
        while(cur != nullptr && cur->val != val) {
            pre = cur;
            cur = cur->next;
        }
        if(cur != nullptr) pre->next = cur->next;
        return head;
    }
};

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

难度简单146

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

示例:

给定一个链表: 1->2->3->4->5, 和 k = 2.

返回链表 4->5.
 /*
快慢指针
定义两个指针,快指针 fastfast, 慢指针 lowlow .
让 fastfast 先向前移动 kk 个位置,然后 lowlow 和 fastfast 再一起向前移动 .
当 fastfast 到达链表尾部,返回 lowlow .
*/

class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode* fast = head;
        ListNode* low = head;
        while (fast != NULL) {
            fast = fast->next;
            if (k == 0) {
                low = low->next;
            } else {
                k--;
            }
        }
        return low;        
    }
};


/*
递归
先一直递归到链表尾部,再返回
定义位置变量 pospos ,每次函数返回时 pos++pos++
当 pos == kpos==k 时,说明此时递归函数位于第 kk 个结点位置,返回该结点
*/


class Solution {
public:
    int pos = 0;
    ListNode* getKthFromEnd(ListNode* head, int k) {
        if (head == NULL) {
            return 0;
        }
        ListNode* ret = getKthFromEnd(head->next, k);
        pos++;
        if (pos == k) {
            return head;
        }
        return ret;    
    }
};

总结

链表必须要掌握的一些点,通过下面练习题,基本大部分的链表类的题目都是手到擒来~

  • null/nil 异常处理

  • dummy node 哑巴节点

  • 快慢指针

  • 插入一个节点到排序链表

  • 从一个链表中移除一个节点

  • 翻转链表

  • 合并两个链表

  • 找到链表的中间节点

练习

  • ​remove-duplicates-from-sorted-list​

  • ​remove-duplicates-from-sorted-list-ii​

  • ​reverse-linked-list​

  • ​reverse-linked-list-ii​

  • ​merge-two-sorted-lists​

  • ​partition-list​

  • ​sort-list​

  • ​reorder-list​

  • ​linked-list-cycle​

  • ​linked-list-cycle-ii​

  • palindrome-linked-list​

  • ​copy-list-with-random-pointer​

1. 两数之和

1. 两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
方法一:暴力枚举

/*
方法一:暴力枚举
复杂度分析
时间复杂度:O(N^2),其中 N 是数组中的元素数量。最坏情况下数组中任意两个数都要被匹配一次。
空间复杂度:O(1).
*/
class Solution {
public:
    vector twoSum(vector& nums, int target) {
        int n = nums.size();
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                if (nums[i] + nums[j] == target) {
                    return {i, j};
                }
            }
        }
        return {};
    }
};

方法二:哈希表

/*
方法二:哈希表
复杂度分析
时间复杂度:O(N),其中 N 是数组中的元素数量。对于每一个元素 x,我们可以 O(1) 地寻找 target - x。

空间复杂度:O(N),其中 N 是数组中的元素数量。主要为哈希表的开销。
*/
class Solution {
public:
    vector twoSum(vector& nums, int target) {
        unordered_map hashtable;
        for (int i = 0; i < nums.size(); ++i) {
            auto it = hashtable.find(target - nums[i]);
            if (it != hashtable.end()) {
                return {it->second, i};
            }
            hashtable[nums[i]] = i;
        }
        return {};
    }
};

2. 两数相加 

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式
存储的,并且它们的每个节点只能存储一位数字。
如果我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
模拟题,由于链表是逆序存放数字的,所以链表数字从左至右低位对低位,高位对高位,
因此我们从左至右遍历两个链表模拟加法运算即可,注意向高位进位。

这个解法里面的图片很形象
https://leetcode-cn.com/problems/add-two-numbers/solution/hua-jie-suan-fa-2-liang-shu-xiang-jia-by-guanpengc/

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *head = nullptr, *tail = nullptr;
        int carry = 0;
        while (l1 || l2) {
            int n1 = l1 ? l1->val: 0;
            int n2 = l2 ? l2->val: 0;
            int sum = n1 + n2 + carry;
            if (!head) {
                head = tail = new ListNode(sum % 10);
            } else {
                tail->next = new ListNode(sum % 10);
                tail = tail->next;
            }
            carry = sum / 10;
            if (l1) {
                l1 = l1->next;
            }
            if (l2) {
                l2 = l2->next;
            }
        }
        if (carry > 0) {
            tail->next = new ListNode(carry);
        }
        return head;
    }
};

160 相交链表(easy)

剑指 Offer 52. 两个链表的第一个公共节点

编写一个程序,找到两个单链表相交的起始节点。

方法三:双指针法
创建两个指针 pA 和 pB,分别初始化为链表 A 和 B 的头结点。然后让它们向后逐结点遍历。
当 pA 到达链表的尾部时,将它重定位到链表 B 的头结点 (你没看错,就是链表 B); 类似的,当 pB 到达链表的尾部时,将它重定位到链表 A 的头结点。
若在某一时刻 pA 和 pB 相遇,则 pA/pB 为相交结点。
想弄清楚为什么这样可行, 可以考虑以下两个链表: A={1,3,5,7,9,11} 和 B={2,4,9,11},相交于结点 9。 由于 B.length (=4) < A.length (=6),pB 比 pA 少经过 22 个结点,会先到达尾部。将 pB 重定向到 A 的头结点,pA 重定向到 B 的头结点后,pB 要比 pA 多走 2 个结点。因此,它们会同时到达交点。
如果两个链表存在相交,它们末尾的结点必然相同。因此当 pA/pB 到达链表结尾时,记录下链表 A/B 对应的元素。若最后元素不相同,则两个链表不相交。
复杂度分析

时间复杂度 : O(m+n)。
空间复杂度 : O(1)。

视频牛逼啊https://leetcode-cn.com/problems/intersection-of-two-linked-lists/solution/jiao-ni-yong-lang-man-de-fang-shi-zhao-dao-liang-2/

https://leetcode-cn.com/problems/intersection-of-two-linked-lists/solution/tu-jie-xiang-jiao-lian-biao-by-user7208t/

 class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *A = headA, *B = headB;
        while (A != B) {
            A = A != nullptr ? A->next : headB;
            B = B != nullptr ? B->next : headA;
        }
        return A;
    }
};

19 删除链表的倒数第N个结点(medium)

给定一个链表,删除链表的倒数第 个节点,并且返回链表的头结点。

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.

说明:给定的 n 保证是有效的。

进阶:你能尝试使用一趟扫描实现吗?

快慢指针法,起始快指针走n步后,若此时快指针已为空,表示我们删除第一个节点,直接返回head->next即可;否则此时快慢指针一起走,也就是慢指针走size-n步到达倒数第N个节点的前驱节点,快指针会到达链表的尾节点,此时我们删除slow->next节点即可。

链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/solution/chao-ke-ai-dong-hua-jiao-ni-ru-he-shan-chu-lian-bi/

/**
 * 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* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0,head);
        ListNode* first = dummy;
        ListNode* second = dummy;
        for (int i = 0; i <= n; ++i) {
            first = first->next;
        }
        while (first) {
            first = first->next;
            second = second->next;
        }
        second->next = second->next->next;
        return dummy->next;
    }
};

203 移除链表元素(easy)

模拟题,直接遍历链表确定是否删除节点即可。

删除链表中等于给定值 val 的所有节点。

示例:

输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5

复杂度分析

  • 时间复杂度:O(N),只遍历了一次。
  • 空间复杂度:O(1)。
 /**
 * 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* removeElements(ListNode* head, int val) {
        ListNode* dummy=new ListNode(0,head);
        ListNode* pre=dummy;
        ListNode* cur=head;
        while(cur)
        {
            if (cur->val==val)
            {
                pre->next=cur->next;
            }
            else
            {
                pre=cur;
            }
            cur=cur->next;
        }
        return dummy->next;


    }
};

328 奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:

输入: 2->1->3->5->6->4->7->NULL 
输出: 2->3->6->7->1->5->4->NULL

奇数 :Odd number

偶数:even numbers

https://leetcode-cn.com/problems/odd-even-linked-list/solution/zui-po-su-de-xiang-fa-dai-ma-zhu-shi-fei-chang-xia/

/**
 * 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* oddEvenList(ListNode* head) {
        if (head == nullptr) {
            return head;
        }
        ListNode* evenHead = head->next;
        ListNode* odd = head;
        ListNode* even = evenHead;
        while (even != nullptr && even->next != nullptr) {
            odd->next = odd->next->next;
            odd = odd->next;
            even->next = even->next->next;
            even = even->next;
        }
        odd->next = evenHead;
        return head;
    }
};

430 扁平化多级双向链表 还没看

模拟题,迭代法,遍历链表,若发现该链表存在child节点那么就将[child,tail]这段子链表插入到当前节点的后面去,然后继续遍历链表。

多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。

给你位于列表第一级的头节点,请你扁平化列表,使所有结点出现在单级双链表中。

复杂度分析

  • 时间复杂度:O(N)。
  • 空间复杂度:O(N)。

61 旋转链表(medium)

模拟题,先求出链表长度size,若k取余size为空,那么不用旋转了,直接返回head;否则将链表首尾相连形成环形链表,由于k表示尾节点移动k%size位,那么头节点移动size-k%size位。

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

示例 1:

输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
示例 2:

输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL

 /**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        if(!head) return NULL;
        int n=0;
        for(auto p=head;p;p=p->next) n++;
        k%=n;
        auto first=head,second=head;
        while(k--){
            first=first->next;
        }
        while(first->next){
            first=first->next;
            second=second->next;
        }
        first->next=head;
        head=second->next;
        second->next=NULL;
        return head;
    }
};

 

你可能感兴趣的:(leetcode,刷题,C++)