力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
利用虚拟头节点
- 是否要添加虚拟头结点 :
虚拟头结点的主要目的是为了避免对头结点的特殊处理;这个处理就指的是修改操作。所以可以这样:涉及到对链表修改(如插入,删除,移动)的,都加个dummy,只是遍历取点就可以不用加
class Solution {
public:
ListNode* removeElements(ListNode* head, int val)
{
ListNode* dummyHead = new ListNode(0); //创建虚拟头节点
dummyHead->next = head;
ListNode* cur = dummyHead;
while (cur->next != NULL)
{
if (cur->next->val == val) {
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
tmp = nullptr;
}
else {
cur = cur->next;
}
}
head = dummyHead->next;
delete dummyHead;
return head;
}
};
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
这题在分析的时候就首先找特殊位置,在设计分析
int get(int index)
获取链表中下标为index
的节点的值。如果下标无效,则返回-1
。
void addAtIndex(int index, int val)
将一个值为val
的节点插入到链表中下标为index
的节点之前。如果index
等于链表的长度,那么该节点会被追加到链表的末尾。如果index
比长度更大,该节点将 不会插入 到链表中。void deleteAtIndex(int index)
如果下标有效,则删除链表中下标为index
的节点。
的时候就直接想在下标为0的位置进行操作,就能明白下面这两个在什么时候使用
ListNode* cur = _dummyHead->next;
ListNode* cur = _dummyHead;
class MyLinkedList {
public:
struct ListNode {
int val;
ListNode* next;
ListNode(int x) :val(x), next(NULL){ }
};
MyLinkedList() {
_dummyHead = new ListNode(0);
_size = 0;
}
int get(int index)
{
if (index >= _size || index < 0)
return -1;
ListNode* cur = _dummyHead->next;
while (index--) {
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
ListNode* ahead = new ListNode(val);
ahead->next = _dummyHead->next;
_dummyHead->next = ahead;
_size++;
}
void addAtTail(int val) {
ListNode* end = new ListNode(val);
ListNode* cur = _dummyHead; //第一遍写成了ListNode* cur = _dummyHead->next,因为如果是空链表就错了
while (cur->next)
{
cur = cur->next;
}
cur->next = end;
_size++;
}
void addAtIndex(int index, int val)
{
if (index > _size)
return;
if (index < 0) //第一遍做时以为如果index<0,那么他也不会插入,后来才知道要插入第0个下标之前
index = 0;
ListNode* cur = _dummyHead;
ListNode* newlist = new ListNode(val);
while (index--) {
cur = cur->next;
}
newlist->next = cur->next;
cur->next = newlist;
_size++;
}
void deleteAtIndex(int index)
{
if (index > _size - 1 || index < 0) return;
ListNode* cur = _dummyHead;
while (index--) {
cur = cur->next;
}
ListNode* tmp = cur->next;
cur->next = tmp->next;
delete tmp;
tmp = nullptr;
_size--;
}
private:
int _size;
ListNode* _dummyHead;
};
写完第二个发现在删除的时候要将删除的指针制为空指针
delete命令指示释放了tmp指针原本所指的那部分内存,
被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
刚开始不理解意思,后来大概明白了一点,下面是我的看法
我们使用delete
释放一个指针所指向的内存时,实际上是告诉操作系统将这块内存标记为可用状态,以供其他程序使用。但是,被释放的内存空间的值并没有被清除,它们仍然存在于内存中,只是变成了未定义的值。这就意味着,被释放的内存空间中的数据可能仍然保留着一些之前存在的值。
在这段代码中,delete tmp;
语句释放了tmp
指针所指向的内存空间。然而,如果不将tmp
置为空指针(即tmp = nullptr;
),那么tmp
仍然保留着之前指向的内存空间的地址,尽管这个内存空间已经被释放。在接下来的代码中,如果不小心使用了tmp
指针,就会访问到这块已经释放的内存空间,这被称为“野指针”。
将tmp
置为空指针(即tmp = nullptr;
)是为了避免出现野指针的情况。通过将指针置为空,我们可以明确地表示该指针不再指向任何有效的内存空间,以便后续的代码可以正确处理。这是一种良好的编程习惯,可以帮助我们避免出现潜在的错误。
而第1题中的tmp不会再被其他代码使用了,所以就不需要将删除的指针制为空指针
但是为了良好的代码习惯,还是将删除的都置为空指针,这样防止以后出错
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
首先想到的一个办法,有点笨
就是创建一个数组将值存进去,然后倒叙输出数组到链表中,但是这个方法适用于链表中存值的情况,并且比较麻烦
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == NULL) return NULL;
int a = 5000;
vector<int>v(a, 0);
int i = 0;
while (head) {
v[i++] = head->val;//这里是i++,第i个赋值后i+1;其实多了一个到后面要使用--i,先剪掉
head = head->next;
}
ListNode* newlist = new ListNode(0);
ListNode* cur = newlist;
while (i--) {
newlist->next = new ListNode(v[i]);
newlist = newlist->next;
}
return cur->next;
}
};
下面是双指针法
首先定义一个pre指针,因为它的目的是指向前面,所以一开始是NULL。
进行while循环,循环终止条件是head为空指针,就会跳出循环,再循环体内,先把head指针的next存储下来,因为接下来head的next会指向pre(向前指),这样就能更新head
head->next---head开始向前指向pre,接着pre进行更新到head当前的位置,
最后将保存的tmp赋给head。
循环结束后
因为head为空了,pre为最后一个节点了,并且现在是向前指的,所以return pre;
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = NULL;
while (head) {
ListNode* tmp = head->next;
head->next = pre;
pre = head;
head = tmp;
}
return pre;
}
};