1,链表的删除
1.1 Q237 删除链表中的结点
有一个单链表的 head,我们想删除它其中的一个节点 node。
给你一个需要删除的节点 node 。你将 无法访问 第一个节点 head。
链表的所有值都是 唯一的,并且保证给定的节点 node 不是链表中的最后一个节点。
删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:
给定节点的值不应该存在于链表中。
链表中的节点数应该减少 1。
node 前面的所有值顺序相同。
node 后面的所有值顺序相同。示例:
输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:指定链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9
将待删除结点的下一个结点的值node->next->val赋值给待删除结点node->val,此时变成4-1-1-9,待删除结点的值已经不复存在,再通过将node结点的下一结点删除,实现链表中结点的删除,此时变成4-1-9.
class Solution {
public:
void deleteNode(ListNode* node) {
node->val = node->next->val; //赋值
node->next = node->next->next; //删除下一个结点
}
};
1.2 Q19 删除链表的倒数第n个结点
给你一个链表,删除链表的倒数第
n
个结点,并且返回链表的头结点。示例:
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
这种类型的题目往往采用快慢指针的方法,快指针fast比慢指针slow多走n步,之后再一起朝后移动。当fast走到链表最后一个节点时,slow移动到待删除结点的前一个结点,通过和上面一样的操作,slow->next = slow->next->next,实现结点的删除。
注意这种往往需要哑节点的使用,以防只有一个结点时程序fast->next或者slow->next = slow->next->next;部分报错。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0, nullptr); //哑节点的使用,当链表只存在一个结点时,哑节点的存在不会使程序报错
ListNode* fast = dummy;
ListNode* slow = dummy;
dummy->next = head;
for(int i = 0; i < n; i++)
{
fast = fast->next;
}
while(fast->next) //fast运动到最后一个结点,slow运动到待删除结点的前一个结点
{
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
return dummy->next;
}
};
1.3 Q203 移除链表中的元素
给你一个链表的头节点
head
和一个整数val
,请你删除链表中所有满足Node.val == val
的节点,并返回 新的头节点 。示例:
输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
进行数组的遍历,找到和val值相同的结点的前一个结点,通过1.1,1.2中的方法进行此结点的删除(将下下个结点赋值给下个节点)。也需要哑节点的使用。
注意p的移动条件,在删除了结点后指针p不能进行移动,因为此时p->next已经更新,若此时也移动,会造成结点漏掉判断。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummy = new ListNode(val, nullptr);
dummy->next = head;
ListNode* p = dummy;
while(p->next)
{
if(p->next->val == val)
{
p->next = p->next->next;
}
else //注意在找到一个和目标值相等的val时,p不能移动,因为此时p->next的指向换了新的值。
p = p->next;
}
return dummy->next;
}
};
2,链表的旋转与反转
2.1 Q61 旋转链表
给你一个链表的头节点
head
,旋转链表,将链表每个节点向右移动k
个位置示例:
输入:head = [1,2,3,4,5], k = 2 输出:[4,5,1,2,3]
先实现每个节点向右移动一个位置,不需要一步一步移动,通过将最后一个结点变成头节点,即先将head赋给最后一个结点的下一个结点,变成环形链表,再在倒数第二位置断开。
之后进行k次循环,这样会导致如果k很大会做很多无用功。所以应该求出实际移动的次数,即k=k%len。
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(head == nullptr || head->next == nullptr) //当链表只包含一个节点或者没有节点时,返回原链表
{
return head;
}
ListNode* p = head;
int len = 0;
while(p) //求出链表长度
{
len++;
p = p->next;
}
k = k % len; //由于k很大时,会做出很多没有意义的移动步,所以求出真实移动步数
for(int i = 0; i < k; i++)
{
p = head;
while(p->next->next) //此时p在倒数第三个结点
{
p = p->next; //p移动到倒数第二个结点
}
p->next->next = head; //注意,此步使链表变成环形链表
head = p->next; //记录更新链表的头节点,便于输出时找到
p->next = nullptr; //将环形链表断开
}
return head;
}
};
2.2 Q24 两两交换链表中的节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例:
输入:head = [1,2,3,4] 输出:[2,1,4,3]
1.暴力解法,通过记录链表结点的位置,在奇数位置时实现此结点与后一个结点的交换。注意在第一次进行交换的时候要重新记录新的头节点。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(head == nullptr || head->next == nullptr) //链表为空或者只有一个结点时,直接返回原链表
{
return head;
}
ListNode* p = head;
ListNode* newhead = new ListNode(0, nullptr);
int n = 0;
while(p->next)
{
n++;
if(n == 1) //首先交换头两个结点的值,此时需要记录新的头结点,便于后期返回
{
int temp = p->val;
p->val = p->next->val;
p->next->val = temp;
newhead = p;
}
else if(n % 2 == 1) //实现后面每两个结点的交换
{
int temp = p->val;
p->val = p->next->val;
p->next->val = temp;
}
p = p->next;
}
return newhead;
}
};
2.递归,分清楚第一,第二,第三个结点的具体位置,先将前两个交换。再将第三个节点作为参数传到函数中递归,得到的节点作为交换后的第二个节点的下一个节点。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(head == nullptr || head->next == nullptr) //链表为空或者只有一个结点时,直接返回原链表
{
return head;
}
ListNode* one = head;
ListNode* two = one->next;
ListNode* three = two->next;
two->next = one; //先实现前两个结点的交换之后将第三个结点作为参数传到
one->next = swapPairs(three); //将第三个结点作为参数传到函数里进行递归,作为交换后的第二个结点的下一个节点
return two;
}
};
2.3 Q206 反转链表
给你单链表的头节点
head
,请你反转链表,并返回反转后的链表。示例:
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
1,暴力解法,求出链表长度,将链表中的每一个值都存入数组中,再将数组中的值反向放置在新的链表中。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
//链表只存在一个节点或者为空时,直接返回原链表
if(!head || !head->next){
return head;
}
ListNode* p = head;
int n = 0;
while(p){
n++;
p = p->next;
} //n为链表总长度
int arr[n]; //新建一个数组用于存储链表中的值
int i = 0;
p = head;
while(p){
arr[i] = p->val;
i++;
p = p->next;
} //将链表中的值存储到数组中
ListNode* newhead = new ListNode(arr[n-1], nullptr);
ListNode* p2 = newhead;
for(int j = 1; j < n; j++) //将数组中的值反向放置在新的链表中
{
p2->next = new ListNode(arr[n-j-1], nullptr);
p2 = p2->next;
}
return newhead;
}
};
2,头节点插入法,将p结点的下一个结点存储起来,删除p结点的下一个结点,将存储起来的结点指向头节点,并更新为新的头节点,直到p结点移到最后一个结点。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(!head)
{
return head;
}
ListNode* p = head;
while(p->next) //一遍遍历,利用头节点逐个插入更新头节点的方法,直到p节点成为链表的尾节点
{
ListNode* temp = p->next; //存储p节点的下一个节点
p->next = p->next->next; //将当前节点的下一个节点删除
temp->next = head; //将已经存储起来的p节点的下一个节点指向头节点,即让其成为新的头节点
head = temp; //更新目前的头节点
}
return head;
}
};
3,新建链表实现头节点插入法,使用dummy结点一直记录头节点的位置。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head) return head;
ListNode* dummy = new ListNode(0, nullptr);
while (head) {
dummy->next = new ListNode(head->val, dummy->next); //新建链表进行头节点插入,将新的头结点逐个插入dummy结点后面,并且将新插入的头节点指向dummy结点的原来的下一个结点,实现插入。此步画图比较好理解。
head = head->next;
}
return dummy->next;
}
};
2.4 Q92 反转链表Ⅱ
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
示例:
输入:head = [1,2,3,4,5], left = 2, right = 4 输出:[1,4,3,2,5]
1,头节点插入法,与上面不同的是需要多记录反转首结点的前一个结点。定义变量必须在函数的开头进行定义,如果在if或者for或者while循环里定义,那在循环外面将找不到,此题也是采用头节点插入法,注意一定要先保存下一个结点,再删除,再插入。
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* dummy = new ListNode(0, head);
ListNode* p = dummy;
ListNode* p1 = nullptr, *p2 = nullptr, *front = nullptr;
int i = 1;
while(p)
{
if(i == left+1) p1 = p; //记录反转首位置
else if(i == right+2) p2 = p;//记录反转尾部结点的下一个结点
else if(i == left) front = p;//记录反转首部结点的前一个结点
p = p->next;
i++;
}
ListNode* newhead = p1;
while(p1->next != p2) //头节点插入法(必须在下个结点删除后再进行头节点插入)
{
ListNode* temp = p1->next;
p1->next = p1->next->next; //注意,此删除下一个结点的语句必须写在以下语句前面,否则会造成逻辑错乱!!!
front->next = temp;
temp->next = newhead;
newhead = temp;
}
return dummy->next;
}
};
3,链表高精度加法
3.1 Q2 两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[7,0,8] 解释:342 + 465 = 807.
两个链表对应位置的数字相加,注意进位,尤其是链表循环完成后最后一步的进位。
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(0, nullptr);
ListNode* l3 = dummy;
int count = 0;
while(l1 || l2) //当链表的指针没有移动到最后时,进行前面每个值的逐个对应相加
{
int n1 = l1 ? l1->val : 0; //指针非空,将指针指向的值给n1,否则给0
int n2 = l2 ? l2->val : 0;
int sum = n1 + n2 + count; //对应位置指针和相加
if (sum >= 10) //和大于十需要有进位
{
sum = sum % 10;
count = 1;
}
else //和小于10,进位为0
{
count = 0;
}
l3->next = new ListNode(sum, nullptr); //将加的和放到新建的链表中
l3 = l3->next;
if(l1) //链表指针如果没移动到最后,就继续朝下移动
{
l1 = l1->next;
}
if(l2)
{
l2 = l2->next;
}
}
if(count > 0) //遍历完成后,如果仍有进位,需要在最后加一
{
l3->next = new ListNode(1,nullptr);
}
return dummy->next;
}
};
3.2 Q445 两数相加Ⅱ
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例:
输入:l1 = [7,2,4,3], l2 = [5,6,4] 输出:[7,8,0,7]
此题是反转列表与两数之和的结合,注意在用到头插法反转列表时,将一个结点指向链表的头节点,语法顺序的写法,temp->next = head!!!!!
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* p1 = l1, * head1 = l1;
ListNode* p2 = l2, * head2 = l2;
ListNode* dummy = new ListNode(0, nullptr);
ListNode* p3 = dummy;
while(p1->next) //使用头节点插入法将链表进行反转
{
ListNode* temp = p1->next;
p1->next = p1->next->next;
temp->next = head1;
head1 = temp;
}
while(p2->next) //使用头节点插入法将链表进行反转
{
ListNode* temp = p2->next;
p2->next = p2->next->next;
temp->next = head2; //一定需要注意此句等于号两边的顺序,将temp指向头节点语法是temp->next=head!!!!!!
head2 = temp;
}
int carry = 0;
while(head1 || head2) //逐个结点的位置的值相加,注意进位
{
int n1 = head1 ? head1->val : 0;
int n2 = head2 ? head2->val : 0;
int sum = n1 + n2 + carry;
if(sum >= 10){
sum = sum % 10;
carry = 1;
}
else if(sum < 10){
carry = 0;
}
p3->next = new ListNode(sum, nullptr);
p3 = p3->next;
if(head1) head1 = head1->next;
if(head2) head2 = head2->next;
}
if(carry == 1) p3->next = new ListNode(1, nullptr);
ListNode* l3 = dummy->next;
ListNode* head3 = dummy->next;
while(l3->next)
{
ListNode* temp = l3->next;
l3->next = l3->next->next;
temp->next = head3;
head3 = temp;
}
return head3;
}
};
4,链表的合并
4.1 Q21合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]
1,暴力解法,新建一个链表,将比较小的链表值放到新的链表里,这种解法占用内存比较多,代码也比较复杂。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* dummy = new ListNode(0, nullptr);
ListNode* p3 = dummy;
ListNode* p1 = list1;
ListNode* p2 = list2;
while(p1 && p2) //当p1与p2均不为空时,分别比较两个链表中的元素大小,并创建到新的链表中
{
if(p1->val < p2->val){
p3->next = new ListNode(p1->val, nullptr);
p3 = p3->next;
p1 = p1->next;
}
else if(p1->val == p2->val){
p3->next = new ListNode(p1->val, nullptr);
p3 = p3->next;
p3->next = new ListNode(p2->val, nullptr);
p3 = p3->next;
p1 = p1->next;
p2 = p2->next;
}
else{
p3->next = new ListNode(p2->val, nullptr);
p3 = p3->next;
p2 = p2->next;
}
}
if(!p1 && p2) p3->next = p2; //当比较完成后,如果p1到尾,p2还没结束,就再把剩下的p2加到后面
else if(!p2 && p1) p3->next = p1; //比较完成后,如果p2到尾,p1还没结束,就再把剩下的p1加到后面
return dummy->next;
}
};
2,相较于上面的解法有了一定的改进,新建了一个节点,直接在原来的两个链表上操作,减少了内存的占用,代码方面也简单很多。直接让新节点指向较小值的结点,在原链表上进行操作。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* list3 = new ListNode(0, nullptr);
ListNode* head = list3; //记录头节点
while(list1 && list2)
{
if(list1->val < list2->val) //比较list1与list2的值,list3指向比较小的那个值
{
list3->next = list1;
list1 = list1->next;
}
else
{
list3->next = list2;
list2 = list2->next;
}
list3 = list3->next;
}
if(list1) list3->next = list1; //若比较完成后,list1非空,则接到list3后面
if(list2) list3->next = list2; //若比较完成后,list2非空,则接到list3后面
return head->next;
}
};