203.移除链表元素
1、题目
2、题解
思路一:在原链表上进行删除
注意考虑删除的是中间结点还是头结点
如果是中间结点,只需要让要删除的结点的上一个结点的next指针直接指向要删除结点的下一个结点就可以了
移除头结点只要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点。
class Solution
{
public:
ListNode *removeElements(ListNode *head, int val)
{
//注意这里要先进行判空再进行值的判断,否则会报错
while (head != NULL&&head->val == val )
{
ListNode *temp = head;
head = head->next;
delete temp;
}
ListNode *current = head;
//这里跟上面一样
while (current != NULL && current->next != NULL)
{
if (current->next->val == val)
{
ListNode *temp = current->next;
current->next = current->next->next;
delete temp;
}
else
{
current = current->next;
}
}
return head;
}
};
思路二:设置虚拟头结点
设置虚拟头结点的好处就是可以把删除头结点的情况包含在删除中间结点的情况中,可以把他们统一进行处理,不用分开写逻辑
class Solution
{
public:
ListNode *removeElements(ListNode *head, int val)
{
ListNode* virNode=new ListNode(0); // 设置一个虚拟头结点
virNode->next=head; // 将虚拟头结点指向head,这样方便后面做删除操作
ListNode* current=virNode;
while (current->next != NULL)
{
if (current->next->val == val)
{
ListNode *temp = current->next;
current->next = current->next->next;
delete temp;
}
else
{
current = current->next;
}
}
head = virNode->next;
delete virNode;
return head;
}
};
3、总结
在进行链表操作的时候要先进行链表的判空,否则会报错
707.设计链表
1、题目
2、题解
这道题目设计链表的五个接口:
-
获取链表第index个节点的数值
-
在链表的最前面插入一个节点
-
在链表的最后面插入一个节点
-
在链表第index个节点前面插入一个节点
-
删除链表的第index个节点
可以说这五个接口,已经覆盖了链表的常见操作,是练习链表操作非常好的一道题目
链表操作的两种方式:
-
直接使用原来的链表来进行操作。
-
设置一个虚拟头结点在进行操作。
下面采用的设置一个虚拟头结点。
class MyLinkedList
{
public:
// 定义链表节点结构体
struct LinkedNode
{
int val;
LinkedNode* next;
LinkedNode(int val) :val(val), next(nullptr)
{
}
};
// 初始化链表
MyLinkedList()
{
virHead = new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点
size = 0;
}
// 获取到第index个节点数值,如果index是非法数值直接返回-1, 注意index是从0开始的,第0个节点就是头结点
int get(int index)
{
if (index > (size - 1) || index < 0)
{
return -1;
}
LinkedNode* cur = virHead->next;
while (index--)
{
// 如果--index 就会陷入死循环
cur = cur->next;
}
return cur->val;
}
// 在链表最前面插入一个节点,插入完成后,新插入的节点为链表的新的头结点
void addAtHead(int val)
{
LinkedNode* newNode = new LinkedNode(val);
newNode->next = virHead->next;
virHead->next = newNode;
size++;
}
// 在链表最后面添加一个节点
void addAtTail(int val)
{
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = virHead;
while (cur->next != nullptr)
{
cur = cur->next;
}
cur->next = newNode;
size++;
}
// 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果index大于链表的长度,则返回空
// 如果index小于0,则在头部插入节点
void addAtIndex(int index, int val)
{
if (index > size) return;
if (index < 0) index = 0;
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = virHead;
while (index--)
{
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
size++;
}
// 删除第index个节点,如果index 大于等于链表的长度,直接return,注意index是从0开始的
void deleteAtIndex(int index)
{
if (index >= size || index < 0)
{
return;
}
LinkedNode* cur = virHead;
while (index--)
{
cur = cur->next;
}
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
//delete命令指示释放了tmp指针原本所指的那部分内存,
//被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
//如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
//如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
tmp = nullptr;
size--;
}
// 打印链表
void printLinkedList()
{
LinkedNode* cur = virHead;
while (cur->next != nullptr)
{
cout << cur->next->val << " ";
cur = cur->next;
}
cout << endl;
}
private:
int size;
LinkedNode* virHead;
};
3、总结
这五个接口都是链表的常见操作,可以多看看多理解。
NULL是一个宏,它被定义为 0(也就是 int 型的零),或者 0L( long 型的零),或者其他零指针常数;但是一般来说我们还是把它当作整形来看待。零指针常数之所以得名,是因为他们能够被转化为空指针。
nullptr,它可以被隐式转换为任何指针类型,可以保证在任何情况下都代表空指针,而不会出现上述的情况,因此,建议用 nullptr 替代 NULL ,而 NULL 就当做 0 使用。
206.翻转链表
1、题目
2、题解
只需要改变链表的next指针的指向,直接将链表反转
首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
先要把 cur->next 节点用tmp指针保存一下,然后改变要改变 cur->next 的指向了,将cur->next 指向 pre ,此时已经反转了第一个节点了。
接下来,就是不断循环,继续移动pre和cur指针。
最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们 return pre 指针就可以了,pre 指针就指向了新的头结点。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* preNode=NULL;
ListNode* endNode=head;
while(endNode!=NULL)
{
ListNode* temp=endNode->next;
endNode->next=preNode;
preNode=endNode;
endNode=temp;
}
return preNode;
}
};
这题的循环部分可以用递归来实现
class Solution
{
public:
ListNode* reverse(ListNode* pre, ListNode* cur)
{
if (cur == NULL) return pre;
ListNode* temp = cur->next;
cur->next = pre;
// 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
// pre = cur;
// cur = temp;
return reverse(cur, temp);
}
ListNode* reverseList(ListNode* head)
{
// 和双指针法初始化是一样的逻辑
// ListNode* cur = head;
// ListNode* pre = NULL;
return reverse(NULL, head);
}
};
3、总结
这题还有很多解决思路,比如从后往前翻转,使用虚拟头结点解决链表反转,使用栈解决反转链表。可以都尝试去做一做,但是要重点理解并记忆一种思路,最好是双指针法,可以和前面的题联系起来加强对双指针法的理解。