链表是一种常用的数据结构,它是由一系列节点构成的。每个节点都包含一个数据元素和一个指向下一个节点的指针。链表的实现方式很多,例如单向链表、双向链表等等。
在c++中,可以通过定义一个结构体或类来实现链表。一个基本的单链表节点定义如下。
//单链表
struct ListNode{
int val;//节点上存储的元素
ListNode* next;//指向下一个节点的指针
ListNode(int x) : val(x),next(NULL){}//节点的构造函数
};
分析:注意删除头节点与删除非头节点的操作不同。具体代码如下。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//方法1:不设置虚拟头节点
//当要删除的就是头节点的时候
//head !=NULL ,防止出现空指针的错误
//head ->val == val 找到说明头节点要删除
while( head != NULL && head->val == val){
ListNode * temp = head;//释放内存的操作
head = head->next;//节点移动完毕
delete temp;
}
//当要删除的不是头节点的时候
//先设置一个临时指针用来遍历,如果用head的话会改变,最后输出有问题的
ListNode * cur = head;
//cur 防一手空指针问题
//cur->next !=NULL 是因为要用到cur->next->val 与val对比,如果为空。那么又是操作空指针
while( cur != NULL && cur->next != NULL){
if(cur->next->val == val){
ListNode * temp = cur->next;
cur->next = cur->next->next;
delete temp;
}else{
cur = cur->next;
}
}
return head;
}
};
分析:设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//方法2:设置虚拟头节点
ListNode* dummyHead = new ListNode(0);//设置一个虚拟头节点
dummyHead->next = head;//将虚拟头节点指向head.为后面的删除操作提供方便
ListNode* cur = dummyHead;
//由于后面要用到cur->next->val 于val对比,所以while循环的条件是cur->next != NULL
while( cur -> next != NULL ){
if(cur->next->val == val){
ListNode* temp = cur->next;
cur->next = cur->next->next;
delete temp;
}else{
cur = cur->next;
}
}
head = dummyHead->next;
delete dummyHead;
return head;
}
};
分析:使用虚拟头节点,慢慢做。
class MyLinkedList {
public:
//定义结构体
struct LinkedNode{
int val;
LinkedNode* next;
LinkedNode(int val):val(val),next(nullptr){};
};
//初始化链表
MyLinkedList() {
_size = 0;
_dummyHead = new LinkedNode(0);//定义一个虚拟头节点
}
//获取到第index个节点数值,若index非法数值返回-1。注意index是从0开始的,第0个节点就是头节点
int get(int index) {
//_size是链表的长度,_size - 1即为链表最后一个节点的下标。
if(index < 0 || index > (_size - 1)){ return -1; }//index非法情况
//定义一个指针cur
//若用head,会改变head。那么无法返回链表的头节点了
LinkedNode* cur = _dummyHead->next;
while(index--){
cur=cur->next;
}
//直接考虑极端情况,当index为0的时候,此时不进入循环直接返回第一个值
return cur->val;
}
//在链表最前面插入一个节点,插入完成后,新插入的节点为链表的头结点。
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
newNode->next = _dummyHead->next;
_dummyHead->next = newNode;
_size++;
}
//在链表最后添加一个节点
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(cur->next != nullptr){
cur=cur->next;
}
cur->next = newNode;
_size++;
}
//如果index大于链表长度,则返回空
//如果index小于0,则在头部插入节点
//在第index个节点之前插入一个新节点。例如index=0,那么新插入的节点为链表的新头节点
//如果index等于链表的长度,则说明是新插入的节点为链表的尾节点
void addAtIndex(int index, int val) {
if(index > _size){return;}
if(index < 0) {index = 0;}
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(index--){
cur = cur->next;
}
newNode->next = cur->next;
cur-> next = newNode;
_size++;
}
//删除第index个节点,如果index大于链表长度直接return,注意index是从0开始的
void deleteAtIndex(int index) {
if(index < 0 || index > (_size - 1)){return;}
LinkedNode* cur = _dummyHead;
while(index--){
cur=cur->next;
}
LinkedNode* temp = cur->next;
cur->next = cur->next->next;
delete temp;
_size--;
}
private:
int _size;
LinkedNode* _dummyHead;
};
分析:定义一个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) {
//1.双指针法
ListNode* temp;//保存cur的下一个节点
ListNode* cur = head;
ListNode* per = NULL;
while(cur){
temp = cur->next;//保存操作
cur->next = per;//翻转操作
//跟新per与cur指针
per = cur;
cur = temp;
}
return per;
}
};
分析:参考双指针写
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);
}
};
代码随想录:https://www.programmercarl.com