2. 两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
解题思路: 给定的两个链表,由于是逆序排列,从链表第一个结点开始是低位。设置一个进位标志,将两链表的结点值用sum
依次相加,和大于等于十时需要进位。结果存在另一链表中。
/**
* 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* addTwoNumbers(ListNode* l1, ListNode* l2) {
//ListNode *p=l1,*q=l2; //链表指针
ListNode* result=new ListNode();//存放结果的链表头
ListNode* r=result;
int sum=0;
bool carry=false; //进位标志
//当两链表都不为空时
while(l1!=NULL||l2!=NULL)
{
sum=0;
//将两链表对应位置的结点值加起来
if(l1!=NULL)
{
sum+=l1->val;
l1=l1->next;
}
if(l2!=NULL)
{
sum+=l2->val;
l2=l2->next;
}
//如果上一次相加发现有进位,则sum再加一
if(carry)
sum++;
//将求和得到的结点对10取余后值赋给下一结点
r->next=new ListNode(sum%10);
//位置指针向后
r=r->next;
//本次求和后的进位与否标志
carry=(sum>=10)?true:false;
}
//当两链表均为空,而还需要再进位(两数相加后的位数超过原来数的位数)时,再开辟一个新节点
if(carry)
{
r->next=new ListNode(1);
}
return result->next;
}
};
237. 删除链表中的节点
写一个函数来删除单链表中的某个节点,在不给出链表的情况下直接删除这个节点。待删除节点不是尾节点。
解题思路:
- 这道题真的非常妙,刚开始看的时候还以为题出错了,怎么少给了参数,没有链表怎么删这个节点。后来又读了几遍搞懂了题意,但是还是不会做啊呜呜呜,看看评论。解法真的很简单很妙,我自己是真的想不到。
- 要直接删除一个节点,可以将这个节点的值变成他的下一个节点,然后跳过下一个节点。
- 也就是说成为另一个人,然后再让那个人消失,那么原来的我也就消失了。细思极恐。
- 两行代码搞定。
/**
* 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;
}
};
19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第n
个结点,并且返回链表的头结点。
解法一:计算链表长度
- 先遍历一次链表,计算长度
L
,然后再遍历一次到L-n+1
的位置,下一个结点就是要删除的,跳过它。 - 构造一个头结点
dummy
,接在head
的前面,当需要删除首元结点时就可以不用做判断,最终返回dummy
的下一个结点指针。 - 这个方法挺慢的,时间
O(L)
,空间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* removeNthFromEnd(ListNode* head, int n) {
int size=0;
//构造一个头节点
ListNode * dummy=new ListNode(0,head);
ListNode *i=head,*j=dummy;
//第一次遍历,计算链表的大小
while(i)
{
i=i->next;
size++;
}
//第二次,遍历到要删除的倒数第n个结点前面的位置
for(int c=1;c<size-n+1;c++)
{
j=j->next;
}
//跳过待删除的那个结点
j->next=j->next->next;
ListNode* ans = dummy->next;
delete dummy;
return ans;
}
};
解法二:双指针
- 利用快慢两个指针,初始时
first
快指针指向head
,慢指针second
指向dummy
。 - 快指针先走
n
步,到达第n
个结点,此时快慢指针间相隔n个结点,然后快慢指针开始同时走,直到快指针到达表尾。 - 慢指针从头开始走,当快指针指向空时,慢指针的下一个结点就是待删除的倒数第
n
个结点
/**
* 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) {
//双指针解法
//先构造一个头结点,其下一结点为head,便于删除第一个结点
ListNode * dummy=new ListNode(0,head);
ListNode * first=head,*second=dummy; //first是快指针,second是慢指针
//i先走n步
for(int i=0;i<n;i++)
{
first=first->next;
}
//当快指针走到了头时(first为空),慢指针指向结点的下一个结点就是待删除结点。
while(first)
{
first=first->next;
second=second->next;
}
//跳过倒数第n个结点
second->next=second->next->next;
ListNode * ans=dummy->next;
delete dummy;
return ans;
}
};
206. 反转链表
反转一个单链表。
解法一 迭代
遍历链表,将当前结点i
的next
域设为它的前一个结点,相当于将箭头转个向。设置之前需要用一个临时结点保存i
原本的下一个结点。
/**
* 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* reverseList(ListNode* head) {
//遍历链表,将当前结点的next设为它前面的那个结点
ListNode * pre=nullptr;
ListNode * i=head;
while(i)
{
//保存i原本的下一个结点
ListNode * next=i->next;
i->next=pre;
pre=i;
i=next;
}
return pre;
}
};
解法二 递归
递归的三条件:
- 大问题分解成两个问题
- 子问题的求解方式和大问题一样(将链表分解为首元结点和除首元结点以外的所有节点,然后递归调用)
- 存在最小子问题(递归终止)
- 参考
/**
* 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* reverseList(ListNode* head) {
//用递归,将原问题分解为首元结点和除首元结点以外结点的子问题
//终止条件,当递的过程到达子链表只剩一个元素时,直接将其返回
//因为一个单节点不用进行反转
if(!head||!head->next)
{
return head;
}
//递归调用
ListNode * p=reverseList(head->next);
head->next->next=head;
head->next=nullptr;
//返回的部分已经做好了反转
return p;
}
};
21. 合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
l1
和l2
均按 非递减顺序 排列
解法一 暴力遍历
- 设置一个头结点,然后遍历
l1
和l2
,将值较小的那个结点接在头结点后,然后指向下一结点。 - 时间
O(M+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* mergeTwoLists(ListNode* l1, ListNode* l2) {
//用双指针,暴力解法
ListNode*dummy=new ListNode(0);
//总链表指针
ListNode*pre=dummy;
//遍历两个链表,将较小结点接在总链表后面
while(l1&&l2)
{
if(l1->val<l2->val)
{
pre->next=l1;
pre=l1;
l1=l1->next;
}
else
{
pre->next=l2;
pre=l2;
l2=l2->next;
}
}
//当有一方到达表尾时,将将另一个表的剩余部分全部接在总链表之后
if(l1)
pre->next=l1;
if(l2)
pre->next=l2;
ListNode*ans=dummy->next;
delete dummy;
return ans;
}
};
解法二 递归
- 合并
l1
和l2
,先看l1
和l2
的首元结点哪个更小 - 然后较小结点的
next
指针指向其余节点的合并结果(递归调用) - 终止条件是两个链表中有一个到达表尾,然后返回另外一个链表所有剩余部分
- 官方题解
- 时间·O(M+N)·,空间·O(M+N)·,·M·和·N·是两个链表的长度
/**
* 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* mergeTwoLists(ListNode* l1, ListNode* l2) {
//递归解法
//判断 l1 和 l2 头结点哪个更小,然后较小结点的 next 指针指向其余结点的合并结果。(调用递归)
//终止条件
if(!l1)
return l2;
if(!l2)
return l1;
if(l1->val<=l2->val)
{
//递归调用
l1->next=mergeTwoLists(l1->next,l2);
return l1;
}
l2->next=mergeTwoLists(l1,l2->next);
return l2;
}
};
234. 回文链表
请判断一个链表是否为回文链表。
用 O(n)
时间复杂度和 O(1)
空间复杂度解决此题
解题思路: (来自官方题解)
- 将链表后半部分反转(会修改链表结构),将前半部分和后半部分进行比较,比较后将链表恢复原样。
- 分成五个步骤
- 找到前半部分链表的尾节点。我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。若链表有奇数个节点,则中间的节点应该看作是前半部分。
- 反转后半部分链表。可以使用「206. 反转链表」问题中的解决方法来反转链表的后半部分。
- 判断是否回文。比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。
- 恢复链表。与步骤二使用的函数相同,再反转一次恢复链表本身。
- 返回结果。
代码
/**
* 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:
bool isPalindrome(ListNode* head) {
//快慢指针
ListNode * fast=head;
ListNode * slow=head;
while(fast->next&&fast->next->next)
{
fast=fast->next->next;
slow=slow->next;
}
//快指针到达表尾时退出
//翻转后半段链表
ListNode * p=reverseList(slow->next);
ListNode * q=head;
ListNode * second_half=p;
//逐个比较前半段和后半段子链表
//ListNode * temp=fast;
while(p)
{
if(p->val==q->val)
{
p=p->next;
q=q->next;
}
else
return false;
}
//还原链表
slow->next=reverseList(second_half);
return true;
}
ListNode* reverseList(ListNode* head) {
//用递归,将原问题分解为首元结点和除首元结点以外结点的子问题
//终止条件,当递的过程到达子链表只剩一个元素时,直接将其返回
//因为一个单节点不用进行反转
if(!head||!head->next)
{
return head;
}
//递归调用
ListNode * p=reverseList(head->next);
head->next->next=head;
head->next=nullptr;
return p;
}
};