Leetcode.203 移除链表元素:
203. 移除链表元素 - 力扣(LeetCode)
对于本题,难点就在于对于头部结点的删除,以及给定链表为空时,如何进行遍历。因为需要遍历链表,假设访问链表下一个结点所对应的代码为,若此时的链表为空,则一旦运行该代码,则直接会被判定越界。造成错误。因此,为了解决上述问题,可以在给定的链表的头结点之前,人工添加一个哨兵位头结点。该链表中的,没有任何实际意义。为了方便表述,文章后面针对哨兵位头结点统一命名为。具体结构如下:
在测试用例中,可以看到,题目给了这样的数据:
如果用上面的图形来表示这种链式结构,即:
对于这种连续的头部删除结点,在设置了哨兵位头结点的情况下,只需要检测当前头结点中的值是否为需要删除的值,即:。一旦满足条件,则直接改变的位置,即。不过需要注意,如果此时给定的链表为空,即:
此时,如果不检查,在检查结点的值这一条件是否满足删除所对应的条件时,由于这句代码的运行顺序是先对解引用,在访问值,此时会对解引用,造成错误。因此,应该先检测此时的是否为。
在进了了上面的删除后,此时链表中,所有需要删除的头结点都已经被删除。但是,还需要再对链表进行一次遍历,检查链表中间结点是否存在需要删除的结点,例如:
按照题目的要求,需要删除值为的结点。前面说到,在测试用例中,存在空链表。因此,为了避免造成越界,设置了哨兵位头结点。在此处,考虑到遍历链表的过程中,需要不断访问不同的结点,因此,再设置一个专门用于遍历链表的结点。
在进行删除时,首先检测是否为,如果为空,则说明整个链表遍历完成。如果不为空,则检测,如果条件满足,则说明指向的下一个结点需要删除,因此令。对于此处,可能会有读者担心会不会造成越界。为了方便说明,此处给出一个临界情况:
对于上述给出的图片,首先进行第一层检测,即:,由于此时并不为,并且指向的下一个结点中的值为,因此需要对这个结点进行删除。删除时,需要获取,也就是值为的结点的下一个结点。也就是,需要注意,此时并不构成越界。因为在链表的定义中,最后一个结点通常都会让其的指向。
上面只给出了的情况,如果指向的下一个结点中的值不等于需要删除的值,则直接跳过该结点即可。
在遍历结束后,需要删除掉前面人为创建的哨兵位头结点。返回。代码如下:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* phead = new ListNode(0);
phead->next = head;
//针对头删的情况
while( head != nullptr && head->val == val)
{
phead->next = head->next;
head=head->next;
}
ListNode* cur = phead;
while(cur->next != nullptr)
{
if(cur->next->val == val)
{
cur->next = cur->next->next;
}
else
{
cur=cur->next;
}
}
delete phead;
phead = nullptr;
return head;
}
};
运行结果如下:
Leetcode.707 设计链表:
707. 设计链表 - 力扣(LeetCode)
对于本题,依旧采用人为添加一个虚拟头结点的方式进行结点。不过需要注意,在题目中并没有给出针对链表中单个结点的结构的定义,因此,需要认为加上上述代码:
struct ListNode
{
ListNode(int val)
:_val(val)
,_next(nullptr)
{}
int _val;
ListNode* _next;
};
在加入后,在给定的类中,将,作为类的两个成员变量。
随后,在给定类中的构造函数,完成对于虚拟头结点的结构的初始化,即:
MyLinkedList() {
_size = 0;
_phead = nullptr;
}
其中,表示链表中结点的数量。
对于头插函数,只需要先新建一个结点 ,为了方便表示,将这个结点命名为。令。再。具体代码如下:
void addAtHead(int val) {
ListNode* newnode = new ListNode(val);
newnode->_next = _phead->_next;
_phead->_next = newnode;
_size++;
}
其效果可以用下面的图进行表示:
此处需要注意,虚拟头结点并不算一个真正的结点。在题干中说到,链表中结点的下标是从开始的,也就是说,对于插入的第一个头结点,其下标为。
对于返回值函数,需要注意题中传递的参数是返回下标为的值。但是,由于链表中第一个结点的下标为,也就是说,链表中的第三个结点的下标为。但是表示的结点的个数,因此为。所以,在进行合法性检测时,需要检测和。
假设在链表中已有个结点,返回结点的下标为,即返回链表中最后一个结点。则首先定义一个指针指向第个结点,然后向后遍历次即可。对应代码如下:
int get(int index) {
if(index > (_size-1) || index < 0)
{
return -1;
}
ListNode* cur = _phead->_next;
while(index--)
{
cur = cur->_next;
}
return cur->_val;
}
对于尾插函数,原理简单,直接给出代码,不做多余解释
void addAtTail(int val) {
ListNode* cur = _phead;
while(cur->_next != nullptr)
{
cur = cur->_next;
}
ListNode* newnode = new ListNode(val);
newnode->_next = cur->_next;
cur->_next = newnode;
_size++;
}
对于插入函数,需要注意,题目要求在位置之前插入元素。因此,需要找到这个下标所对应的元素。前面说到,由于链表中第一个结点的下标是从开始的。所以在链表中下标为的结点,在链表中的位置其实是。例如当时,对应这个下标的结点是链表中的第四个结点。对于找到这个结点,需要从第个结点开始,向后遍历次。此时找到的结点是链表中第个结点。
由于要求招待位置前一个位置的结点,所以只需要改变遍历起点的位置即可,即改为哨兵位头结点。
但是需要注意,对于需要进行合法性检测。题目说到了,当时,视作尾插,因此只需要检测这一种情况即可。
对应代码如下:
void addAtIndex(int index, int val) {
if(index > _size || index < 0)
{
return ;
}
ListNode* cur = _phead;
while(index--)
{
cur = cur->_next;
}
ListNode* newnode = new ListNode(val);
newnode->_next = cur->_next;
cur->_next = newnode;
_size++;
}
对于删除结点函数,需要注意,按照删除结点的逻辑,在遍历时,能遍历到的最后一个结点便是链表中倒数第二个结点。因此,如果,此时会遍历到最后一个结点,在后续造成非法访问。因此,检测条件需要进行改变。对应代码如下:
void deleteAtIndex(int index) {
if(index >= _size || index < 0)
{
return ;
}
ListNode* cur = _phead;
while(index--)
{
cur = cur->_next;
}
ListNode* tmp = cur->_next;
cur->_next = cur->_next->_next;
delete tmp;
tmp = nullptr;
_size--;
}
题目整体代码如下:
class MyLinkedList {
public:
struct ListNode
{
ListNode(int val)
: _val(val)
, _next(nullptr)
{}
ListNode* _next;
int _val;
};
MyLinkedList() {
_phead = new ListNode(0);
_size =0;
}
int get(int index) {
if(index > _size-1 || index < 0)
{
return -1;
}
ListNode* cur = _phead->_next;
while(index--)
{
cur = cur->_next;
}
return cur->_val;
}
void addAtHead(int val) {
ListNode* newnode = new ListNode(val);
newnode->_next = _phead->_next;
_phead->_next = newnode;
_size++;
}
void addAtTail(int val) {
ListNode* cur = _phead;
while(cur->_next != nullptr)
{
cur = cur->_next;
}
ListNode* newnode = new ListNode(val);
newnode->_next = cur->_next;
cur->_next = newnode;
_size++;
}
void addAtIndex(int index, int val) {
if(index > _size || index < 0)
{
return ;
}
ListNode* cur = _phead;
while(index--)
{
cur = cur->_next;
}
ListNode* newnode = new ListNode(val);
newnode->_next = cur->_next;
cur->_next = newnode;
_size++;
}
void deleteAtIndex(int index) {
if(index >= _size || index < 0)
{
return ;
}
ListNode* cur = _phead;
while(index--)
{
cur = cur->_next;
}
ListNode* tmp = cur->_next;
cur->_next = cur->_next->_next;
delete tmp;
tmp = nullptr;
_size--;
}
ListNode* _phead;
int _size;
};
运行结果如下:
Leetcode.206 反转链表:
206. 反转链表 - 力扣(LeetCode)
依旧采用人为添加哨兵位头结点的方式,原理较为简单,只给出图形表示:
先保存的后一个结点,接着改变的指向,使其指向,对于可以看作哨兵位头结点,随后让,让即可。一直循环该过程。
对应代码如下:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head;
ListNode* pre = nullptr;
ListNode* tmp = nullptr;
while(cur)
{
tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
};