学习链表之前,需要对链表的理论知识进行一个理解:
我大概总结了以下几点:
1)链表的组成部分:有数据域,有指针域(用来指向下一个节点),两个需要注意的点:最后一个节点的指针域指向的是NULL,头节点是用head来表示的
2)链表的种类;单链表,双链表
3)链表的储存方式:非连续分布,是通过指针域中的指针连接起来,并占用内存
4)链表的定义:
//来自代码随想录
struct ListNode {
int val; // 节点上存储的元素,放到数据域中
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
5)链表中删除节点,增加节点的原理:
删除节点:删除节点的上一个节点的指针域指向删除节点的下一个数据域
注意在c++需要将删除节点的内存手动清除,下面是代码
ListNode* tmp = head;
delete tmp;
增加节点:在增加的位置的前节点的指针指向该节点,将该节点的指针指向后节点的数据域
下面就可以做题了:
Leetcode 203 移除链表元素
题目链接203 移除链表元素
两种方法:
第一种方法:基于移除元素的原理
直接上代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while(head!=NULL&&head->val==val){//处理头节点时,需要拿出来单独处理,头节点不能为空
ListNode* tmp = head;
head = head->next;//头指针后移,指向下一个节点,表示头节点已经被删除
delete tmp;
}
//处理非头节点
ListNode* cur = head;//新建一个指针,从head开始
//原因:删除节点要记录上一个节点的指针域,从head->next开始的话,无法记录上一个节点
while(cur!=NULL&&cur->next!=NULL){
if(cur->next->val==val){
ListNode* tmp = cur->next;
cur->next = cur->next->next;//核心
delete tmp;
}else{
cur = cur->next;//没有找到val,继续下一个节点
}
}
return head;//返回头指针,代表整个链表
}
};
第二种方法(虚拟头节点法),不用单独讨论头节点的情况
加一个虚拟头节点就可以解决问题,直接上代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyhead = new ListNode(0); // 设置一个虚拟头结点
dummyhead->next = head; // 将虚拟头结点指向head
ListNode* cur = dummyhead;//建立cur新指针
//剩下的和普通法一样,这里就不过多赘述
while (cur->next != NULL) {
if(cur->next->val == val) {
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
} else {
cur = cur->next;
}
}
//这个可以把dummyhead删除,返回到head;
//也可以直接写 return dummyhead->next;
head = dummyhead->next;
delete dummyHead;
return head;
}
};
Leetcode 707 设计链表
题目链接707 设计链表
本题目涉及到链表的删除,查找,添加,这基本上包括了链表的所有的基础操作
直接上代码加注释:
class MyLinkedList {
public:
struct LinkedNode{
int val;//数据值
LinkedNode*next;//指针
LinkedNode(int val):val(val),next(nullptr){}//变量函数
};
MyLinkedList() {//初始对象
dummyHead = new LinkedNode(0);//新建一个虚拟头节点
size = 0;//链表长度初始化
}
int get(int index) {//获得下表为index的值
if(index<0||index>(size-1)){//不在去链表区间范围内
return -1;
}
LinkedNode*cur = dummyHead->next;//建立新指针指向dummyHead的下个节点,为了使寻找区间值合法
while(index--){//这里同样是为了使寻找区间合法
cur = cur->next;//右移
}
return cur->val;//最后输出index结点的值
}
void addAtHead(int val) {//在链表首位加入一个新节点
LinkedNode*newnode = new LinkedNode(val);//新建立一个节点
//因为这里确定了插入节点的位置,所以就不需要在建立一个新的指针了
newnode->next = dummyHead->next;//注意添加节点时的顺序,反过来时dummyHead->next的值会被改变
dummyHead->next = newnode;
size++;//链表节点数加一
}
void addAtTail(int val) {//增加到末尾位置,由于没有向head头指针一样的尾指针的,所以要建立一个新指针
LinkedNode*newnode = new LinkedNode(val);//新节点
LinkedNode*cur = dummyHead;//新指针
while(cur->next!=nullptr){//最后一个节点指针指向的是空
cur = cur->next;//遍历到最后一个节点
}
newnode->next = nullptr;//最后一个节点为空
cur->next = newnode;//此时cur指向的节点连接到新节点上
size++;//节点数加一
}
void addAtIndex(int index, int val) {
if(index<0){//下表数小于0,将该节点放到第一个节点
index = 0;//称之为0节点
}
if(index>size){
return ;//超出范围直接退出循环
}
//下面讨论范围内的
LinkedNode*newnode = new LinkedNode(val);//新建一个新节点
LinkedNode*cur = dummyHead;//我们不知道具体的位置,所以我们要建立一个新指针
while(index--){//不停遍历,找到index节点
cur = cur->next;
}//此时循环条件中为0时,就已经停止了,此时cur指针指向的是index下标节点的上一个节点
newnode->next = cur->next;//这里的cur->next指的是index节点
cur->next = newnode;//注意连接顺序顺寻
size++;//节点加一
}
void deleteAtIndex(int index) {
if(index<0||index>size-1){
return ;//删除节点超出范围直接pass
}
LinkedNode*cur = dummyHead;//建立新指针
while(index--){
cur = cur->next;//找到删除项,且cur指向删除的前一项
}
LinkedNode* tmp = cur->next;//定义目标节点的内存
cur->next = cur->next->next;//删除节点
delete tmp;//删除内存
tmp = nullptr;//delete命令指示释放了tmp指针原本所指的那部分内存,
//被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
//如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
//如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
size--;//节点减一
}
//打印链表,根据题目要求来
void printLinkedList(){
LinkedNode*cur = dummyHead;//定义新指针和虚拟头
while(cur->next!=nullptr){
cout<next->val<<" ";//打印链表所有节点的值
cur = cur->next;//遍历
}
cout<get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
Leetcode 206 反转链表
题目链接206 反转链接
本题目还是用两种方法;
首先介绍第一种,双指针法:
先画一个图更方便大家理解:
加上代码会更好的理解:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode*temp;
ListNode*cur = head;
ListNode*pre = NULL;
while(cur != NULL){
temp = cur->next;//将cur后一个节点记录
cur->next = pre; //改变链表指向
pre = cur;//不停遍历,两指针前移
cur = temp;//temp只用来节点记录,不用来移动
}
return pre;//此时pre指向的就是头节点
}
};
另一种方法是递归法,下面是代码加注释:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode*reverse(ListNode*pre,ListNode*cur){
if(cur==NULL){
return pre;
}
ListNode*temp = cur->next;//记录cur的下个节点
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);
}
};
end