代码随想录-链表
本次记录代码随想录的链表部分的学习
一.移除链表元素
题意:删除链表中等于给定值 val 的所有节点。
示例 1: 输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
示例 2: 输入:head = [], val = 1 输出:[]
示例 3: 输入:head = [7,7,7,7], val = 7 输出:[]
思路
这里要思考两种情况
一种是要删除的元素位于头结点
另一种是不在头结点
如果是在链表头,那么删除起来不太方便,所以我们考虑加入一个头指针,方便我们操作,头结点不存储任何数据,它的作用只是指向链表的第一个元素
这样我们操作起来就会比较方便,在返回的之后只需要返回头指针指向的节点即可
如果有头指针,我们就可以不断遍历每个元素的值,如果检测到值为val,就删除这个节点
/** * 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* hp = new ListNode(0); hp->next = head; ListNode* cur = hp; while (cur->next != NULL && cur != NULL) { if (cur->next->val == val) { ListNode* tmp = cur->next; cur->next = cur->next->next; delete tmp; } else { cur = cur->next; } } head = hp->next; delete hp; return head; } };
上面是有头指针的情况,如果没有人为设置的头指针,那么应该怎么处理
那就要分两种情况,一种是删除头结点,另一种是删除其他节点
删除头结点
//消除头结点 //不断检测头结点,如果val域一直相等,就一直删除
while (head != NULL && head->val == val)
{ ListNode* tmp = head;
head = head->next; delete tmp;
}
注意这里使用的是while而不是if,因为可能从头结点开始连续几个值都是val,因此要使用while
删除其他节点,这部分代码就和上面带头指针的删除一样了
//删除非头结点 ListNode* p = head; while (p != NULL && p->next != NULL) { //删除节点 if (p->next->val == val) { ListNode* tmp = p->next; p->next = p->next->next; delete tmp; } else { p = p->next; } }
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: ListNode* removeElements(ListNode* head, int val) { //消除头结点 //不断检测头结点,如果val域一直相等,就一直删除 while (head != NULL && head->val == val) { ListNode* tmp = head; head = head->next; delete tmp; } //删除非头结点 ListNode* p = head; while (p != NULL && p->next != NULL) { //删除节点 if (p->next->val == val) { ListNode* tmp = p->next; p->next = p->next->next; delete tmp; } else { p = p->next; } } return head; } };
二.设计链表
题意:
在链表类中实现这些功能:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
这一题主要就是考验对链表的基本操作,需要自己设定一个链表的类
对于本题,我们给定两个成员变量
private: int size; ListNode* head;
get(index)函数
int get(int index) { if (index < 0 || index > (size - 1)) { return -1; } ListNode* tmp = head->next; while (index--) { tmp = tmp->next; } return tmp->val; }
addAtHead(val)
void addAtHead(int val) { ListNode* p = new ListNode(val); p->next = head->next; head->next = p; size++; }
addAtTail
void addAtTail(int val) { ListNode* p = new ListNode(val); ListNode* cur = head; while (cur->next != NULL) { cur = cur->next; } cur->next = p; size++; }
addAtIndex(index,val)
void addAtIndex(int index, int val) { if (index > size) { return ; } if (index < 0) { index = 0; } ListNode* cur = head; while (index--) { cur = cur->next; } ListNode* p = new ListNode(val); p->next = cur->next; cur->next = p; size++; }
deleteAtIndex(index)
void deleteAtIndex(int index) { if (index < 0 || index >= size) { return ; } ListNode* cur = head; while (index--) { cur = cur->next; } ListNode* tmp = cur->next; cur->next = cur->next->next; delete tmp; size--; }
完整LeetCode代码
class MyLinkedList { public: /** * Your MyLinkedList object will be instantiated and called as such: * MyLinkedList* obj = new MyLinkedList(); * int param_1 = obj->get(index); * obj->addAtHead(val); * obj->addAtTail(val); * obj->addAtIndex(index,val); * obj->deleteAtIndex(index); */ struct ListNode{ int val; ListNode* next; ListNode(int val) : val(val),next(NULL){} }; MyLinkedList() { size = 0; head = new ListNode(0); } int get(int index) { if (index < 0 || index > (size - 1)) { return -1; } ListNode* tmp = head->next; while (index--) { tmp = tmp->next; } return tmp->val; } void addAtHead(int val) { ListNode* p = new ListNode(val); p->next = head->next; head->next = p; size++; } void addAtTail(int val) { ListNode* p = new ListNode(val); ListNode* cur = head; while (cur->next != NULL) { cur = cur->next; } cur->next = p; size++; } void addAtIndex(int index, int val) { if (index > size) { return ; } if (index < 0) { index = 0; } ListNode* cur = head; while (index--) { cur = cur->next; } ListNode* p = new ListNode(val); p->next = cur->next; cur->next = p; size++; } void deleteAtIndex(int index) { if (index < 0 || index >= size) { return ; } ListNode* cur = head; while (index--) { cur = cur->next; } ListNode* tmp = cur->next; cur->next = cur->next->next; delete tmp; size--; } private: int size; ListNode* head; };
三.反转链表
题意:反转一个单链表。
示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
思路
一个很简单的思路,迭代法,不断修改next指针,让指针翻转,即指向后项的节点改为之指向前项
设置两个指针,pre指向前一个节点,cur指向当前节点的
每次先存储next指针指向的下一个节点,然后将cur指向pre,实现翻转指针,然后将当前指针赋值
LeetCode代码
class Solution { public: ListNode* reverseList(ListNode* head) { ListNode* pre = NULL; ListNode* cur = head; while (cur != NULL) { ListNode* tmp = cur->next; cur->next = pre; pre = cur; cur = tmp; } return pre; } };
四. 两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
思路
对于每两个节点,如果我们能找到这两个点前面的一个点,操作就会很方便
这样的操作为,大箭头为原来的指向,曲线箭头为修改后的指向
对应的代码操作为
ListNode* tmp = cur->next;
ListNode* tmp1 = cur->next->next->next;
cur->next = cur->next->next;
cur->next->next = tmp;
cur->next->next->next = tmp1;
这样就完成了两两交换的操作
LeetCode代码
class Solution { public: ListNode* swapPairs(ListNode* head) {
//增加虚拟头结点,方便交换操作 ListNode* hp = new ListNode(0); hp->next = head; ListNode* cur = hp; while (cur->next != NULL && cur->next->next != NULL) { ListNode* tmp = cur->next; ListNode* tmp1 = cur->next->next->next; //1. cur->next = cur->next->next;
//2. cur->next->next = tmp; //3.
cur->next->next->next = tmp1; cur = cur->next->next; } return hp->next; } };
五.删除链表的倒数第N个节点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
思路
普通的解决思路
先遍历一遍链表,得到链表的长度,用长度减去n,得到要删除节点的位置,然后在遍历一遍链表,删除节点
朴素代码实现
class Solution { public: ListNode* removeNthFromEnd(ListNode* head, int n) { int size = 0;
//创建一个虚拟头结点 ListNode* hp = new ListNode(0); hp->next = head; ListNode* cur = hp; //得到链表的长度
while (cur->next != NULL) { cur = cur->next; size++; }
//得到要删除的点的位置 int pos = size - n; cur = hp; while (pos--) { cur = cur->next; }
//直接跳过这个节点,完成删除 cur->next = cur->next->next; return hp->next; } };
第二种思路
采用双指针算法
开始时,两个指针都位于头部,让一个指针先走n步,然后两个指针同时走,当走的早的指针走到尾部时,走的慢的指针就到了要删除节点的位置
在上面的图中,红色的点使我们要删除的点,它位于倒数第二个位置处,n为2,则我们让红色指针先走两步,然后两个指针一起走,当红色的指针走到最后一个元素时,橙色指针指向的下一个元素就是我们要删除的元素
LeetCode代码
class Solution { public: ListNode* removeNthFromEnd(ListNode* head, int n) { ListNode* hp = new ListNode(0); hp->next = head; ListNode* fast = hp; ListNode* cur = hp; while (n-- && fast != NULL) { fast = fast->next; } //ast再提前走一步,因为需要让slow指向删除节点的上一个节点 fast = fast->next; while (fast != NULL) { fast = fast->next; cur = cur->next; } cur->next= cur->next->next; return hp->next; } };
六. 链表相交
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
示例 2:
示例 3:
思路
这题的题目描述不是非常的清晰,这里说的是两个单链表相交的起始节点,而不是两个单链表值相等的起始节点,也就是说我们要比较的是指针,相交的指针在内存中是同一块区域,而这两个链表都指向了这块内存,要求的是这块内存的起始节点
明白了要求什么,就可以继续向下思考
经过观察,发现要找的节点一定在长度较短的链表的子链里,而不可能在长链的前面的部分,因此我们需要让长链和短链的指针对齐,然后依次比较指针
为了实现对齐,可以先求出两个链表的长度,然后用长的减去短的,计算出长链指针需要移动的次数,然后进行对齐
如果没有找到,直接返回空即可
LeetCode代码
/** * 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) { ListNode* curA = headA; ListNode* curB = headB; int sizeA = 0,sizeB = 0; while (curA != NULL) { curA = curA->next; sizeA++; } while (curB != NULL) { curB = curB->next; sizeB++; } curA = headA; curB = headB; if (sizeA < sizeB) { swap(sizeA,sizeB); swap(curA,curB); } int pos = sizeA - sizeB; while (pos--) { curA = curA->next; } while (curA != NULL) { if (curA == curB) { return curA; } curA = curA->next; curB = curB->next; } return NULL; } };
七.环形链表II
题意: 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
说明:不允许修改给定的链表。
思路
主要考察两知识点:
- 判断链表是否环
- 如果有环,如何找到这个环的入口
1.如何判断链表有环
设置两个指针,fast和slow,每次fast指针移动两次,而slow指针只移动一次,如果有环,则两个指针一定会相遇
如何证明这一点呢?其实无论指针怎么移动,最后一定会转换为一种情况,就是下面两个指针差一位的情况,那上面的情况举例
上面的情况个移动一次后,就转换为了下面的情况,其他情况也是如此,所以如果有环,两个指针一定会相遇
2.确定有环之后,如何确定环的入口
当两个指针相遇时,slow指针走过的节点数为x+y,而fast指针走过的节点数为x+y+n*(y+z)
而fast走过的节点数又等于slow走过的节点的两倍,因此x+y+n*(y+z) = 2 * (x+y)
我们要求的值是x,即环形节点的如果,解方程得到x = n (y + z) - y,整理得到x = (n - 1) (y + z) + z
这里n-1是大于0的,所以fast指针至少要多走一圈
我们让n=1,解得x=z
这就意味着,一个指针从头结点开始走,另一个从相遇节点开始走,当两个指针相遇时,就是环的入口
那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。
其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。
LeetCode代码
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode *detectCycle(ListNode *head) { ListNode* fast = head; ListNode* slow = head; //先判断是否有环 while (fast != NULL && fast->next != NULL) { fast = fast->next->next; slow = slow->next; //寻找环的入口点 if (fast == slow) { ListNode* index1 = head; ListNode* index2 = fast; while (index1 != index2) { index1 = index1->next; index2 = index2->next; } return index2; } } return NULL; } };