目录
链表基本用法:
C++链表及基本操作
LeetCode链表经典题目汇总:
2. 两数相加
19. 删除链表的倒数第 N 个结点
21. 合并两个有序链表
23. 合并K个升序链表
24. 两两交换链表中的节点
25. K 个一组翻转链表
61. 旋转链表
92. 反转链表 II
141. 环形链表
142. 环形链表 II
160. 相交链表
328. 奇偶链表
//创建单链表
/*
1.创建头节点head,并且将当前结点p指向头结点(p=head)
2.创建下一个结点q,当前结点p的下一结点为q(p->next=q)
3.结点p后移一位(p = p->next)
*/
#include
#include
using namespace std;
struct ListNode {
int val;
struct ListNode* next;
ListNode(int x) :
val(x), next(NULL) {
}
};
//插入节点
/*
1.判断原链表是否是空链表,如果是,将head指向新增结点
2.如果不是空链表,向链表尾部插入新结点
*/
ListNode* insertNode(ListNode* head, int data) {
ListNode* newNode = new ListNode(data);
ListNode* p = head;
if (p == nullptr) {
head = newNode;
}
else {
while (p->next != nullptr) {
p = p->next;
}
p->next = newNode;
}
return head;
}
//删除节点
/*
1.空链表直接返回
2.非空链表需要删除的结点不存在
3.非空链表需要删除的结点存在,是头结点和不是头结点两种情况
*/
ListNode* deleteNode(ListNode* head, int data) {
ListNode* p = head;
//空链表直接返回
if (p == nullptr) {
return head;
}
else {
//判断是否为头结点
if (p->val == data) {
head = p->next;
delete p;
return head;
}
else {
//遍历到改结点,删除
while (p->next != nullptr && p->next->val != data) {
p = p->next;
}
//遍历完链表却没有要删除的结点
if (p->next == nullptr) {
return head;
}
else {
ListNode* deleteNode = p->next;
p->next = deleteNode->next;
delete deleteNode;
return head;
}
}
}
}
//反转链表
/*
假设pNode是当前的节点,pPrev是pNode前面的节点,PNext是PNode后面的节点,那么:
当pNode不为nullptr,且pNext不为nullptr的时候
1.将pNode指向pPrev(pNode->next = pPrev)
2.将pNode给pPrev(pPrev= pNode)
3.将pNext给pNode(pNode = pNext)
4.当pNode不为nullptr,且pNext==nullptr的时候,把反转后的头部指向pNode
*/
ListNode* reverseNode(ListNode* head) {
ListNode* prev = nullptr;
ListNode* p = head;
ListNode* reserveHead = nullptr;
while (p != nullptr) {
ListNode* pNext = p->next;
if (pNext == nullptr) {
reserveHead = p;
}
p->next = prev;
prev = p;
p = pNext;
}
return reserveHead;
}
//倒数第K个节点
/*思想:设置快慢指针,快指针比慢指针多走k - 1步,那么快指针走到终点的时候,慢指针指向倒数第K个结点*/
ListNode* FindKthToTail(ListNode* head, unsigned int k) {
if (head == nullptr || k == 0) {
return nullptr;
}
//快指针
ListNode* pAhead = head;
//判断K是不是超出了链表的长度
for (int i = 0; i < k - 1; i++) {
if (pAhead->next != nullptr) {
pAhead = pAhead->next;
}
else {
return nullptr;
}
}
//慢指针
ListNode* pBhead = head;
while (pAhead->next != nullptr) {
pAhead = pAhead->next;
pBhead = pBhead->next;
}
return pBhead;
}
//是否有环
//判断链表是否有环,如果有,找出环的入口节点
/*
判断链表是否有环
思想:可以设置快慢指针,快指针一次走两步,慢指针一次走一步,如果快指针追上了走的慢的指针,那么链表有环,如果走到了链表尾部都没有追上,说明链表无环。
注意:快指针与慢指针是否为nullptr的判断
如果有环,返回入口节点
思想:返回的节点一定在环内,如果计算出环中节点的个数count,快指针比慢指针多走count步,那么两个指针相遇时,就是环的入口节点
*/
//判断快慢指针是否相遇
ListNode* meetNode(ListNode* head) {
ListNode* p = head;
if (p == nullptr) {
return nullptr;
}
//设置慢指针
ListNode* slowNode = p->next;
if (slowNode == nullptr) {
return nullptr;
}
//设置快指针
ListNode* fastNode = slowNode->next;
while (fastNode != nullptr && slowNode != nullptr) {
//相遇
if (fastNode == slowNode) {
return fastNode;
}
//slowNode走一步
slowNode = slowNode->next;
//fastNode走两步
fastNode = fastNode->next;
if (fastNode->next != nullptr) {
fastNode = fastNode->next;
}
}
return nullptr;
}
//计算环中节点的个数
int countCycleNode(ListNode* pMeet) {
int count = 0;
ListNode* pNode = pMeet;
while (pNode->next != pMeet) {
++count;
pNode = pNode->next;
}
++count;
return count;
}
//计算环的入口节点
ListNode* entryNodeFloop(ListNode* phead) {
ListNode* meet = meetNode(phead);
if (meet == nullptr) {
return nullptr;
}
int count = countCycleNode(meet);
ListNode* aheadNode = phead;
ListNode* behindNode = phead;
for (int i = 0; i < count; i++) {
aheadNode = aheadNode->next;
}
while (aheadNode != behindNode) {
aheadNode = aheadNode->next;
behindNode = behindNode;
}
ListNode* entry = aheadNode;
return entry;
}
int main()
{
int num;
cin >> num;
ListNode* head = new ListNode(num);
ListNode* p = head;
尾插法创建单链表
//while (cin >> num) {
// ListNode* q = new ListNode(num);
// p->next = q;
// p = p->next;
//}
insertNode(head, 3);
insertNode(head, 4);
insertNode(head, 5);
deleteNode(head, 3);
ListNode* reverseN = reverseNode(head);
ListNode* pBhead = FindKthToTail(reverseN, 1);
cout << "pBhead:" << pBhead->val << endl;
//遍历链表
ListNode* m = reverseN;
while (m != nullptr) {
cout << m->val << endl;
m = m->next;
}
return 0;
}
首先对单链表的定义:
/**
* 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) {}
* };
*/
难度中等
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
来自 <力扣>
题解:重点考虑最后的进位
#构造新链表赋值
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
flag = 0 #进位
res = cur = ListNode()
while l1 or l2:
x = l1.val if l1 else 0
y = l2.val if l2 else 0
total = x + y + flag
cur.next = ListNode(total%10)
flag = total // 10
if l1: l1 = l1.next
if l2: l2 = l2.next
cur = cur.next
if flag: cur.next = ListNode(flag)
return res.next
难度中等
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
来自 <力扣>
题解:
解法1:
//快慢指针:时间O(n),空间O(1)
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(head==nullptr || n==0){
return nullptr;
}
ListNode *dummy = new ListNode(0,head);
//快慢指针,快指针比慢指针多走n-1步,快指针到链表尾端,慢指针在链表的倒数第n个结点
ListNode* slow = dummy,*fast=head;
for(int i=0;i
if(fast->next)
fast = fast->next;
else
return nullptr;
}
while(fast->next){
fast = fast->next;
slow = slow->next;
}
if(slow->next){
slow->next = slow->next->next;
}
ListNode* ans = dummy->next;
delete dummy;
return ans;
}
};
解法2:
//统计链表长度:时间O(n),空间O(1)
class Solution {
public:
int getLength(ListNode* head){
int length = 0;
while(head){
++length;
head = head->next;
}
return length;
}
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0,head);
int length = getLength(dummy);
ListNode* prev = dummy;
for(int i=0;i
prev = prev->next;
}
prev->next = prev->next->next;
ListNode* ans = dummy->next;
delete dummy;
return ans;
}
};
解法3:
//栈:时间O(n),空间O(n)
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(head==nullptr || n==0){
return nullptr;
}
ListNode *dummy = new ListNode(0,head);
stack
ListNode* cur = dummy;
while(cur){
stk.push(cur);
cur = cur->next;
}
for(int i=0;i
stk.pop();
}
ListNode* prev = stk.top();
prev->next = prev->next->next;
ListNode* ans = dummy->next;
delete dummy;
return ans;
}
};
难度简单
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
来自 <力扣>
题解:
// 方法一:迭代
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *dummy = new ListNode(-1);
ListNode *ans = dummy;
//因为后面要比较大小,所以必须用&&确保l1和l2都存在元素。
while(l1 && l2)
{
if(l1->val < l2->val)
{
ans->next = l1;
l1 = l1->next;
}
else
{
ans->next = l2;
l2 = l2->next;
}
ans = ans->next;
}
ans->next = l1 ? l1:l2;
ans = ans->next;
return dummy->next;
}
};
//方法二:递归
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(!l1)
{
return l2;
}else if(!l2)
{
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;
}
}
};
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
来自 <力扣>
题解:
//逐一合并: 空间O(1)
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1,ListNode* l2){
ListNode* dummy = new ListNode();
ListNode* cur = dummy;
while(l1&&l2)
{
if(l1->val<l2->val)
{
cur->next = l1;
l1 = l1->next;
}else{
cur->next = l2;
l2 = l2->next;
}
cur = cur->next;
}
cur->next = l1 ? l1 : l2;
return dummy->next;
}
ListNode* merge(vector<ListNode*>& lists,int left,int right){
if(left==right){
return lists[left];
}
int mid = left + (right-left)/2;
ListNode* l1 = merge(lists,left,mid);
ListNode* l2 = merge(lists,mid+1,right);
return mergeTwoLists(l1,l2);
}
ListNode* mergeKLists(vector<ListNode*>& lists) {
int num = lists.size();
if(lists.empty()){
return nullptr;
}
return merge(lists,0,num-1);
}
};
//优先队列:空间O(1)
class Solution{
public:
struct comp{
bool operator()(ListNode* a, ListNode* b){
return a->val > b->val;
}
};
priority_queue
ListNode* mergeKLists(vector<ListNode*>& lists) {
for(auto node:lists){
if(node){
q.push(node);
}
}
ListNode* head = new ListNode();
ListNode* tail = head;
while(!q.empty()){
ListNode* node = q.top();
q.pop();
tail->next = node;
tail = tail->next;
if(node->next) q.push(node->next);
}
return head->next;
}
};
难度中等
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
来自 <力扣>
题解:
//时间O(n) 空间O(1)
//一次平移两个结点,通过引入tmp结点交换两个节点指向,但需要记录前一个结点,将前一组的尾结点重新指向翻转后的头结点
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(head==nullptr)
return nullptr;
if(head && head->next==nullptr)
return head;
ListNode* dummy = new ListNode(0,head);
ListNode* cur = dummy; //从dummy节点开始,cur指每两个中头结点的前一个结点
while(cur->next && cur->next->next)
{
//交换两结点
ListNode* tmp = cur->next->next;
cur->next->next = tmp->next;
tmp->next = cur->next;
//将前一组的尾结点重新指向翻转后的头结点
cur->next = tmp;
cur = cur->next->next;
}
return dummy->next;
}
};
给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 1:
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
示例 2:
输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]
来自 <力扣>
题解:
//反转子链表,K为一组反转,一组中起始结点为start,尾部结点为end,只要end不为空就反转此组链表。
//反转前需要指定start前一个结点pre,也即上一组的反转后的尾结点(反转后尾结点已经变成start),还需指定end之后的结点next(也即下一组的start结点)
//每次反转介绍后pre,start,end,next均指定对应位置即可
class Solution {
public:
ListNode* reverse(ListNode* head){
ListNode* pre = new ListNode(0);
ListNode* cur = head;
while(cur){
ListNode* next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* dummy = new ListNode(0,head);
ListNode* pre = dummy;
ListNode* end = dummy;
while(end->next){
for(int i=0;i
if(end==nullptr) break;
ListNode* next = end->next;
ListNode* start = pre->next;
end->next = nullptr;
pre->next = reverse(start);
start->next = next;
pre = start;
end = pre;
}
return dummy->next;
}
};
难度中等
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
示例 1:
输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]
示例 2:
输入:head = [0,1,2], k = 4
输出:[2,0,1]
来自 <力扣>
题解:
//链表长度为len,如果k>len,只需移动k=k%len个位置,因为整数倍移动是没有变化的
//找到倒数第k-1个位置结点cur,该结点与后面结点断开,即cur指向null,让cur的下一个结点变成头结点,即dummy结点指向cur的下一个结点,链表的尾结点要指向原先的头结点。
//时间O(n),空间O(1)
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(head==nullptr || k==0){
return head;
}
ListNode* dummy = new ListNode(0,head);
ListNode* cur = dummy;
int len = 0;
while(cur->next){
len++;
cur = cur->next;
}
//整数倍不需反转
if(len==1 || k%len==0) return head;
if(k>len){
k = k % len;
}
ListNode* tail = cur;
cur = dummy;
for(int i=0;i
cur = cur->next;
}
dummy->next = cur->next;
cur->next = nullptr;
tail->next = head;
return dummy->next;
}
};
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
示例 1:
输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
示例 2:
输入:head = [5], left = 1, right = 1
输出:[5]
提示:
来自 <力扣>
题解:
//固定left前面的一个结点pre,依次取下一个结点放到pre后,并改变指向,直到right结点
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode *dummyNode = new ListNode(-1);
dummyNode->next = head;
ListNode *pre = dummyNode;
for(int i=0; i
pre = pre->next;
ListNode *next;
ListNode *cur = pre->next;
for(int i=0; i
{
next = cur->next;
cur->next = next->next;
next->next = pre->next;
pre->next = next;
}
return dummyNode->next;
}
};
难度简单
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
来自 <力扣>
题解:
//快指针走两步,慢指针走一步,相遇则有环
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast&&fast->next&&fast->next->next){
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
return true;
}
return false;
}
};
难度中等
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
来自 <力扣>
题解:
//快慢指针相遇后,快指针回到头结点,快指针慢指针都走一步,再次相遇的地方就是入环结点
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
bool flag = false;
while(fast&&fast->next&&fast->next->next){
fast = fast->next->next;
slow = slow->next;
if(fast==slow){
flag = true;
break;
}
}
fast = head;
if(flag){
while(fast!=slow){
slow = slow->next;
fast = fast->next;
}
return fast;
}else{
return NULL;
}
}
};
难度简单
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
来自 <力扣>
题解:
解法1:
//计算各自长度,长的链表多走差值,再同步走,走到最后若无相同结点则不相交,若有则相交
class Solution {
public:
int getLen(ListNode* head){
int len = 0;
while(head){
head=head->next;
len++;
}
return len;
}
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int lenA = getLen(headA);
int lenB = getLen(headB);
if(lenA>lenB){
for(int i=0;i<abs(lenA-lenB);i++){
headA = headA->next;
}
}else if(lenA
for(int i=0;i<abs(lenA-lenB);i++){
headB = headB->next;
}
}
while(headA && headB){
if(headA==headB)
return headA;
headA = headA->next;
headB = headB->next;
}
return NULL;
}
};
解法2:
//双指针
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == nullptr || headB == nullptr) {
return nullptr;
}
ListNode *pA = headA, *pB = headB;
while (pA != pB) {
pA = pA == nullptr ? headB : pA->next;
pB = pB == nullptr ? headA : pB->next;
}
return pA;
}
};
给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。
第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。
请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。
你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。
示例 1:
输入: head = [1,2,3,4,5]
输出: [1,3,5,2,4]
示例 2:
输入: head = [2,1,3,5,6,4,7]
输出: [2,3,6,7,1,5,4]
来自 <力扣>
题解:
//奇链表连奇链表,偶链表连偶链表,需要记录偶链表的头结点,奇链表的尾结点连偶链表头结点
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if(head==nullptr)
{
return head;
}
ListNode *evenHead = head->next;
ListNode *even = evenHead;
ListNode *odd = head;
while(even!=nullptr && even->next!=nullptr)
{
odd->next = even->next;
odd = odd->next;
even->next = odd->next;
even = even->next;
}
odd->next = evenHead;
return head;
}
};