链表
首元节点: 首元结点是链表中存储第一个数据元素的结点。它是链表数据部分的开始,包含了链表中的第一个元素的值。在没有头结点的链表中,首元结点就是链表的第一个结点。
头节点: 头结点是一个附加在首元结点之前的结点,它的指针域指向首元结点。头结点的数据域可以不存储任何信息,或者存储一些如链表长度等附加信息。头结点的存在使得对链表的第一个数据元素的操作与对其他数据元素的操作一致,无需特殊处理。此外,头结点的引入也便于空表和非空表的统一处理。
头指针: 是指向链表中第一个结点的指针。如果链表有头结点,头指针指向头结点;如果链表没有头结点,头指针指向首元结点。
虚拟头节点(dummy head): 引入虚拟头节点使得虚拟头节点指向头节点,head指向虚拟头节点,此时删除头节点与删除非头节点的代码一致。
LeetCode203 移除链表元素
题目链接: https://leetcode.cn/problems/remove-linked-list-elements/description/
不含虚拟头节点:
head头指针指向链表的首元节点,进行删除操作时,删除首元节点和非首元节点时所进行的操作不一样。
①删除第一个节点的代码为head = head->next
②删除非第一个节点的代码为cur->next = cur->next->next;
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while(head != NULL && head->val == val)
head = head->next;
ListNode* cur = head;
while(cur != NULL && cur->next != NULL){
if(cur->next->val == val)
cur->next = cur->next->next;
else
cur = cur->next;
}
return head;
}
};
注意点:
①删除首元节点时,要用while
而不是if
因为可能出现以下情况
target = 1
ele[i] | 1 | 1 | 1 | 1 |
---|
②删除首元节点时,要判断是否为空,因为要判断head->val
是否等于target
, 若不判断,head
为空时,取其值会报错
③删除非首元节点时,为何是cur = head
而不是cur = head->next
,因为经过删除首元节点的过程,已经把head->val==val
的首元节点都已经删除了,则当前head->val
一定不为val
,进而只能判断head->next
,则若删除head->next
,那需要找到其前驱节点,即head
节点
④删除非首元节点时,不仅要判断cur
是否为空,还要判断cur->next
是否为空,因为要删除的是cur->next
,若为空,取值时会报错
含虚拟节点:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyhead = new ListNode();
dummyhead->next = head;
ListNode* cur = dummyhead;
while(cur != NULL && cur->next != NULL){
if(cur->next->val == val)
cur->next = cur->next->next;
else
cur = cur->next;
}
return dummy->next;
}
};
注意:
①最终return
的是dummyhead->next
而不是head
,因为head
节点可能已经被删除了
LeetCode707 设计链表
**题目链接:**https://leetcode.cn/problems/design-linked-list/description/
思路:
用极端例子快速判断要增加/删除元素位置是否正确
1、获取下标为index节点的值
用index控制while
循环次数,从dummyHead->next
开始循环index次即可找到下标为index的节点
LinkNode* cur = dummyHead->next;
while(index){
cur = cur->next;
index--;
}
2、头部插入节点
LinkNode* node = new LinkNode();
node->val = val;
node->next = dummyHead->next;
dummyHead->next = node;
len++;
3、尾部插入节点
LinkNode* cur = dummyHead;
while(cur->next != NULL)
cur = cur->next;
LinkNode* node = new LinkNode();
node->val = val;
node->next = NULL;
cur->next = node;
len++;
4、下标为index节点前插入节点
若从dummyHead
节点开始遍历,则到达下标为index节点的前一个节点,cur=cur->next
需循环index次
举例验证: 当index=0时,while(0)
不执行,因此直接头插节点,符合题意故没问题
当index=len时,while(index)
执行len次,到达尾节点,直接尾插法,满足题意若index等于len则追加到末尾
LinkNode* cur = dummyHead;
while(index){
cur = cur->next;
index--;
}
LinkNode* node = new LinkNode();
node->val = val;
node->next = cur->next;
cur->next = node;
len++;
5、删下标为index的节点
与4类同,若从dummyHead
节点开始遍历,则到达下标为index节点的前一个节点,
cur=cur->next
需循环index次
举例验证: 当index=0时,while(0)
不执行,因此直接删除头节点,符合题意故没问题
LinkNode* cur = dummyHead;
while(index){
cur = cur->next;
index--;
}
cur->next = cur->next->next;
len--;
题解:
class MyLinkedList {
public:
struct LinkNode {
int val;
LinkNode* next;
};
LinkNode* dummyHead; // 虚拟头节点
int len; // 链表长度
public:
MyLinkedList() {
dummyHead = new LinkNode();
dummyHead->next = NULL;
len = 0;
}
int get(int index) {
if (index < 0 || index >= len)
return -1;
LinkNode* cur = dummyHead->next;
while (index) {
cur = cur->next;
index--;
}
return cur->val;
}
void addAtHead(int val) {
LinkNode* node = new LinkNode();
node->val = val;
node->next = dummyHead->next;
dummyHead->next = node;
len++;
}
void addAtTail(int val) {
LinkNode* cur = dummyHead;
while (cur->next != NULL)
cur = cur->next;
LinkNode* node = new LinkNode();
node->val = val;
node->next = NULL;
cur->next = node;
len++;
}
void addAtIndex(int index, int val) {
// 插入位置合法性判断
if (index > len || index < 0)
return;
LinkNode* cur = dummyHead;
while (index) {
cur = cur->next;
index--;
}
LinkNode* node = new LinkNode();
node->val = val;
node->next = cur->next;
cur->next = node;
len++;
}
void deleteAtIndex(int index) {
if (index < 0 || index >= len)
return;
LinkNode* cur = dummyHead;
while (index) {
cur = cur->next;
index--;
}
cur->next = cur->next->next;
len--;
}
};
/**
* 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);
*/
收获:
1、C++创建类的对象(类的初始化)的方法区别new和不用
使用new
会创建在堆中,MyLinkedList* obj = new MyLinkedList();
不使用new
会创建在栈中,MyLinkedList obj;
引用链接: newhttps://blog.youkuaiyun.com/ytusdc/article/details/88621223
LeetCode206 反转链表
题目链接: https://leetcode.cn/problems/reverse-linked-list/description/
双指针解法:
思路:
head→口→口→口→口→null
变为
null ←口←口←口←口←head
定义一个指针pre=NULL,一个指针cur=head,cur指针向后遍历,分别把途中的节点的next指向前一个节点,直到最后一个节点
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = NULL;
ListNode* cur = head;
while (cur != NULL) {
ListNode* temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
};
双指针改递归解法:
写递归的思路:
1、确定递归函数参数个数,根据双指针法可知需要两个变量实现反转,一个是cur,一个是pre,故需要两个参数
ListNode* reverse(ListNode* cur, ListNode* pre){
}
2、确定递归函数的跳出条件,根据双指针法可知,当cur指向最后一个元素,由此确定跳出递归的条件如下
class Solution {
public:
ListNode* reverse(ListNode* cur, ListNode* pre){
if(cur == NULL)
return pre;
}
}
3、书写处理逻辑
3.1 根据双指针法可知,最开始cur=head
pre=NULL
,因此在第一次调用时,需要给cur与pre赋head与NULL,如下
class Solution {
public:
ListNode* reverse(ListNode* cur, ListNode* pre){
if(cur == NULL)
return pre;
}
ListNode* ListNode* reverseList(ListNode* head) {
return reverse(head, NULL);
}
}
3.2 当cur不是最后一个节点时,需要改变每个节点的指向,临时变量temp记录当前节点cur,cur指向下一个节点,更改temp节点next指向,如下
class Solution {
public:
ListNode* reverse(ListNode* cur, ListNode* pre){
if(cur == NULL)
return pre;
ListNode* temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
return reverse(cur, pre);
}
ListNode* reverseList(ListNode* head) {
return reverse(head, NULL);
}
}
反思:
最开始我个人思路是临时指针temp保存当前节点,然后cur指向下一个节点,等改变了temp节点的方向后,pre指针指向temp节点,这样导致无法改写成递归
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == NULL)
return head;
ListNode* pre = NULL;
ListNode* cur = head;
while (cur->next != NULL) {
ListNode* temp = cur;
cur = cur->next;
temp->next = pre;
pre = temp;
}
cur->next = pre;
head = cur;
return head;
}
};