1.基本操作
1.两数相加(leetcode 2)
- 题目描述:
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
- 分析:
首先,两个链表为逆序存放,那么开始位置为个位,依次十位,百位...,
- 同时遍历两个链表,两个链表对应的值相加,再加上进位标识符(sum);
- 判断当前左右链表的地址是否为NULL
- 接着,判断其和(sum)是否大于等于10,如果大于等于10,则标志位为1,sum为余数,否则为0,将sum赋值为左结点的数据域,更新左右结点;
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* left=l1;
ListNode* right=l2;
ListNode* Pre_l=l1;
ListNode* Pre_r=l2;
//进位标志
int flag=0;
int sum=0;
while(left||right)
{
if(left==NULL)
{
//和
sum=right->val+flag;
if(sum>=10)
{
sum %=10;
flag=1;
}
else
flag=0;
//赋值给当前右指针
right->val=sum;
//前一帧的下一结点的地址为当前右指针的地址
Pre_l->next=right;
//更新前一帧的指针
Pre_l=Pre_l->next;
Pre_r=right;
right=right->next;
}
else if(right==NULL)
{
sum=left->val+flag;
if(sum>=10)
{
sum %=10;
flag=1;
}
else
flag=0;
left->val=sum;
Pre_l=left;
left=left->next;
}
else
{
//和
sum=left->val+right->val+flag;
if(sum>=10)
{
sum %=10;
flag=1;
}
else
flag=0;
//赋值给左链表当前结点
left->val=sum;
//更新前一帧指针和当前指针
Pre_l=left;
Pre_r=right;
left=left->next;
right=right->next;
}
}
if(flag==1)
{
ListNode* node=new ListNode(1);
Pre_l->next= node;
}
return l1;
}
};
2.两数相加II(leetcode 445)
- 题目描述:
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
示例:
输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 8 -> 0 -> 7
- 实现:
首先,我们知道单链表无法像数组一样通过索引值获得其对应的元素,而且我们也不知道其对应的长度(有多少个元素)。对于加法实现,依次是:个位->十位->百位->....,然后个位在链表的末尾,我们无法直接获取,因此我们需要对链表进行遍历,但是当遍历到末尾时,由于链表内部存储的是下一个节点的地址,所以我们无法获得十位,百位...,因此我们在遍历的同时需要对数据进行存储,什么数据结构能满足此过程呢?答案是栈(先进后出),我们在遍历的同时,将遍历的结果压入栈中,然后在依次出栈,进行加法操作,并对其进行重构链表,但是我们需要一个正序的链表,而不是倒序,因此我们可以对栈进行翻转,这里我又使用了一次栈结构,将重构的链表节点存储在栈中,然后再依次出栈连接起来。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
stack<ListNode* > s_L;
stack<int> s1,s2;
//遍历链表,压入栈中
ListNode* l1_temp=l1;
while(l1_temp)
{
s1.push(l1_temp->val);
l1_temp=l1_temp->next;
}
ListNode* l2_temp=l2;
while(l2_temp)
{
s2.push(l2_temp->val);
l2_temp=l2_temp->next;
}
//出栈,进行加法操作
int flag=0;//进位标志
while(!s1.empty()||!s2.empty())
{
ListNode* new_node=new ListNode;
if(s1.empty())
{
new_node->val=0+s2.top()+flag;
// cout<<"val: "<<new_node->val<<endl;
s2.pop();
}
else if(s2.empty())
{
new_node->val=s1.top()+0+flag;
// cout<<"val: "<<new_node->val<<endl;
s1.pop();
}
else
{
new_node->val=s1.top()+s2.top()+flag;
// cout<<"val: "<<new_node->val<<endl;
s1.pop();
s2.pop();
}
//判断是否进位
if(new_node->val>=10)
{
new_node->val-=10;
flag=1;
}
else
flag=0;
s_L.push(new_node);
}
//重构链表
ListNode* res=new ListNode;
ListNode* temp=res;
//如果进位标志还存在,新建一个节点
if(flag)
{
res->val=flag;
}
//如果不存在,则取栈顶元素
else
{
res->val=s_L.top()->val;
s_L.pop();
}
while(!s_L.empty())
{
temp->next=s_L.top();
temp=temp->next;
s_L.pop();
}
return res;
}
};
2.快慢指针
1.返回倒数第K个节点(leetcode 面试题22)(剑指Offer 22)
- 题目描述:
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
注意:本题相对原题稍作改动
示例:
输入: 1->2->3->4->5 和 k = 2
输出: 4
说明:给定的 n 保证是有效的。
- 实现:
- 首先,快、慢指针同时指向head;
- 其次,快指针比慢指针多走K步;
- 最后,快指针和慢指针同步前进,直到快指针到达末尾。
- 快指针的主要目标:遍历整个单向链表。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
int kthToLast(ListNode* head, int k) {
ListNode* slow=head;
ListNode* fast=head;
while(k--)
{
fast=fast->next;
}
while(fast)
{
fast=fast->next;
slow=slow->next;
}
return slow->val;
}
};
2.链表的中间节点(leetcode 876)
- 题目描述:
给定一个带有头结点 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
提示:
给定链表的结点数介于 1 和 100 之间。
- 实现:
由于要返回链表的中间节点,就需要知道链表的头到中间节点的长度,因为链表的长度未知。
- 常规作法:
- 遍历整个链表,知道链表的长度;
- 通过链表的长度得到链表中间节点的位置;
- 然后再次遍历到中间位置,返回该节点。
- 快慢指针做法:
- 使用慢指针
slow
和快指针fast
两个指针同时遍历链表; - 快指针一次前进两个结点,速度是慢指针的两倍;
- 直到fast和fast->next为空。
- 常规做法:
/**
* 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) {
int k=0;
ListNode* temp=head;
while(temp)
{
k++;
temp=temp->next;
}
k=k/2+1;
// cout<<k<<endl;
ListNode* slow=head;
ListNode* fast;
while(--k)
{
slow=slow->next;
}
// cout<<slow->val<<endl;
return slow;
}
};
- 快慢指针:
/**
* 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* slow=head;
ListNode* fast=head;
while(fast&&fast->next)
{
fast=(fast->next)->next;
slow=slow->next;
}
return slow;
}
};
3.相交链表(leetcode 160)(剑指offer 52)
- 题目描述:
编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表:
在节点 c1 开始相交。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。
注意:
如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
- 分析:
这道题题目稍微有点难懂,相交指的是节点的地址相同而不是值相同。
- 对于不相交的情况可以将其看作相交节点为NULL的情况。
- 对于相交的第一个节点后面的所有结点,快慢指针走的步数是相同的;
- 对于不相交的部分,快指针走完A+快指针走完B=慢指针走完B+慢指针走完A,总的步数是一样的,所以会相遇。
- 最后只需判断相遇结点是否为空。
/**
* 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==NULL||headB==NULL)
return NULL;
ListNode* cur1=headA;
ListNode* cur2=headB;
while(cur1!=cur2)
{
cur1=cur1?cur1->next:headB;
cur2=cur2?cur2->next:headA;
}
return cur1;
}
};
4.环形链表(leetcode 141)
- 题目描述:
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
进阶:
你能用 O(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==NULL||head->next==NULL)
return false;
ListNode* fast=head->next;
ListNode* slow=head;
while(fast!=slow)
{
if(fast==NULL||fast->next==NULL)
return false;
fast=fast->next->next;
slow=slow->next;
}
return true;
}
};
3.删除节点
1.删除中间节点(面试题02.03)
- 题目描述:
实现一种算法,删除单向链表中间的某个节点(即不是第一个或最后一个节点),假定你只能访问该节点。
示例:
输入:单向链表a->b->c->d->e->f中的节点c
结果:不返回任何数据,但该链表变为a->b->d->e->f
- 分析:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
node->val=node->next->val;
node->next=node->next->next;
}
};
2.删除排序链表中重复元素(leetcode 83)
-
题目描述:
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2
输出: 1->2
示例 2:
输入: 1->1->2->3->3
输出: 1->2->3
- 分析:
/**
* 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) {
ListNode* fast=head;
while(fast&&fast->next)
{
if(fast->val==fast->next->val)
fast->next=fast->next->next;
else
fast=fast->next;
}
return head;
}
};
1.反转链表(leetcode 24,206)
- 题目描述:
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
- 实现:
- 定义三个指针: pre 、cur 、next。
- pre 在前 ,cur在中,next在后。
- next暂存cur->next的地址。
- 让cur 的 next 指向 pre ,实现一次局部反转。
- 局部反转完成之后, pre 和 cur 同时往前移动一个位置。
- 循环上述过程,直至 cur 到达链表尾部。
/**
* 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* pre=NULL;
ListNode* cur=head;
ListNode* next;
while(cur)
{
next=cur->next;
cur->next=pre;
pre=cur;
cur=next;
}
return pre;
}
};
2.K个一组反转链表(leetcode 25)
- 题目描述:
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例 :
给定这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
- 实现:
本题的实现步骤和上一道题反转链表类似,只不过这是一个子过程,当K个数反转完后,剩余的部分使用递归重复前面的过程,直到剩余的部分的节点数少于K时,返回头指针(递归结束)。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
//判断剩余的链表节点数是否还有K个
ListNode* temp=head;
for(int i=0;i<k;i++)
{
//如果链表已经遍历到末尾,但是未满K个,直接返回head
if(temp==NULL)
return head;
temp=temp->next;
}
//从head处开始反转
ListNode* pre=NULL;
ListNode* cur=head;
ListNode* next;
int t=0;
while(t!=k)
{
next=cur->next;
cur->next=pre;
pre=cur;
cur=next;
t++;
}
//递归调用
head->next=reverseKGroup(cur,k);
return pre;
}
};
3.反转从位置 m 到 n 的链表(leetcode 92)
- 题目描述:
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
- 实现:
- 判断m是不是头节点,如果是,那么直接按反转前n-m+1个节点
- 如果不是,先找到需要反转的起始节点的前一个节点,保存该节点。
- 对后面的节点进行反转;
- 连接各部分
/**
* 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) {
ListNode* pre=head;
ListNode* cur;
ListNode* next;
int s;
//反转前N-M+1个结点
if(m<2)
{
pre=NULL;
cur=head;
s=n-m+1;
while(cur&&s--)
{
next=cur->next;
cur->next=pre;
pre=cur;
cur=next;
}
//反转后的尾部指向未反转部分的头部
head->next=cur;
head=pre;
}
//m!=1
else
{
//先找到反转的起始点
int t=m-2;
while(t--)
{
pre=pre->next;
}
//保存前m个的末尾
ListNode* temp=pre;
//反转的起点
cur=pre->next;
s=n-m+1;
while(cur&&s--)
{
next=cur->next;
cur->next=pre;
pre=cur;
cur=next;
}
//连接
(temp->next)->next=cur;
temp->next=pre;
}
return head;
}
};
6.合并两个有序链表(leetcode 21)
- 题目描述:
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
- 分析:
这道题和归并排序的合并操作类似。首先,我们需要合并两个链表,那么就需要比较链表中的元素,这时我们需要两个指针用来移动索引链表的节点再比较,合并后的链表也需要一个指针来进行索引。
/**
* 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 head;
ListNode* pointL1=l1;
ListNode* pointL2=l2;
ListNode* point=&head;
while(pointL1||pointL2)
{
if(pointL1==NULL)
{
point->next=pointL2;
point=point->next;
pointL2=pointL2->next;
}
else if(pointL2==NULL)
{
point->next=pointL1;
point=point->next;
pointL1=pointL1->next;
}
else if(pointL1->val>pointL2->val)
{
point->next=pointL2;
point=point->next;
pointL2=pointL2->next;
}
else
{
point->next=pointL1;
point=point->next;
pointL1=pointL1->next;
}
}
return head.next;
}
};