- 什么是链表
- 特性
- 链表和顺序表都是线性表的一种
- 链表每个数据的存储是不连续的
- 基本单位结构体中有两种数据
- 数据域
- 指针域
- 头指针与头结点
- 头指针:头指针是指向链表第一个结点的指针,如果有头结点那么头指针就是头结点的指针
- 头结点:
- 目的:为了操作统一和方便
- 位置:第一个元素的结点之前
- 数据域一般没有意义(也可以放链表长度)
- 链表的分类
- 有头链表
- 无头链表
- 双向链表
- 双向循环链表
- 特性
/**
* 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) { // 注意这里不是if
ListNode* tmp = head;
head = head->next;
delete tmp;
}
// 删除非头结点
ListNode* cur = head;
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;
}
}
return head;
}
};
我不太懂链表定义的逻辑,比如如何递归,很难理解next的大小没有定义
首先明确指针
Int *p
这句话定义了一个指向int类型的指针,指针和变量不一样,变量需要定义它的大小和位置。
而指针不用定义它的大小。Int*p这里并不是定义指针的大小,而是定义一个指向int类型的指针;而指针的大小就是地址的大小,地址的大小是由电脑的内存决定的,而不是人为决定的。
定义指针就是定义指针指向的数据类型。当我们需要一个指向变量A地址的指针的时候,如果我们知道变量A是double类型,那么我们就不能用p指向它,但是我们知道变量B的类型是INT类型的时候,我们就可以用p指向它了:
Int B;
Int *p=&B;
所以回到我们的问题上来,ListNode *next其实就是定义了一个指向Listnode类型的指针,而这个指针在的定义在ListNode里面,这里就是定义了一个链表,这个数据结构里面包含两个成员,一个是存储整数的成员,另一个是指向下一个ListNode对象的指针Next成员。所以说,“指向下一个 ListNode 对象的指针 next 成员” 的意思是,next 指针存储了另一个 ListNode 对象的地址,该对象在链表中紧随当前节点。这样就能够实现链表中节点的连接和遍历。
为什么会需要分别删除头结点和其他节点呢
/**
* 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* cur=head;
while(cur!=NULL)
{
while(head!=NULL&&head->val==val)//问题1:!==是错的,!=才是对的
{
ListNode* temp=head;
head=head->next;
delete temp;
}
ListNode* cur==head;
while(cur->next!=NULL&&cur->val==val)//问题2:这里可能出现不等于val的情况也要考虑进去
//问题3:cur!=null。这里是因为如果链表元素输入NULL的话,在访问空指针的成员在计算机中是犯法的。
{
ListNode* temp=cur->next;
cur->next=cur->next->next;
cur=cur->next; //问题4:这里当cur指针指向的结点的值不等于val的值的时候执行
delete temp;
}
}
}
}
};
/**
* 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); // 设置一个虚拟头结点 //问题1:dummyHead 是一个指向 ListNode 类型的指针,通过 new 关键字创建了一个新的 ListNode 对象,值为0,然后将这个新节点作为虚拟头结点
dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
ListNode* cur = dummyHead;
while (cur->next != NULL) {
if(cur->next->val == val) {
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
} else {
cur = cur->next;
}
}
head = dummyHead->next;//问题2:这里注意如果删除这句话,返回head的时候,head有可能已经被释放了,毕竟我们在循环的时候一直处理的是next这个指针变量,而没有head指针变量赋值。所以我们返回的时候还是原来的head。这个时候我们应该返回。
delete dummyHead;
return head;
}
};
遍历n个结点的时间复杂度是O(n),删除一个节点需要O(1)的时间。所以O(1*n)就是O(n)
问题2:这里注意如果删除这句话,返回head的时候,head有可能已经被释放了,毕竟我们在循环的时候一直处理的是next这个指针变量,而没有head指针变量赋值。所以我们返回的时候还是原来的head。这个时候我们应该返回。
- 问题汇总
- 之前看过一个节点,说头节点是一个很特殊的节点,好像没有值,但是这道题里面好像没有管这个,到时候去了解一些
- 没有赋值的链表的数据都是零嘛还是空,链表的最后一个元素指向哪
- 问题1:!==是错的,!=才是对的
- 问题2:这里可能出现不等于val的情况也要考虑进去
- 问题3:cur!=null。这里是因为如果链表元素输入NULL的话,在访问空指针的成员在计算机中是犯法的。
- 问题4:这里当cur指针指向的结点的值不等于val的值的时候执行
- 问题1:dummyHead 是一个指向 ListNode 类型的指针,通过 new 关键字创建了一个新的 ListNode 对象,值为0,然后将这个新节点作为虚拟头结点
- 时间复杂度
- 第一遍代码复杂度分析
- 虚拟头结点
- 标准代码
- 改过来之后就是上面的标准代码分析
- 第一遍代码
- 删除头结点
- 删除头结点以外的节点
- 关键在于删除头结点的时候,只需要把下一个节点的地址给头结点,然后删除头地址就相当于头地址了,而现在的头结点是最开始的下一个结点
- 而移除其他的结点的方式是一样的
- 查漏补缺问题记录:
- 之前看过一个节点,说头节点是一个很特殊的节点,好像没有值,但是这道题里面好像没有管这个,到时候去了解一些
- 没有赋值的链表的数据都是零嘛还是空,链表的最后一个元素指向哪
- 代码随想录
- 双头结点
- ListNode(int x) : val(x), next(nullptr) {}
- 构造函数,初始化结构
- 我的问题
- 语法问题
- ListNode *next;
- 语法问题
- 标准代码分析
707设计链表
MyLinkedList(){_dummyHead =newLinkedNode(0);// 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点_size =0;}
为什么是给虚拟头结点和结点命名;
class MyLinkedList {
public:
struct LinkNode()
{
int val;
LinkNode *next;
LinkNode(int val):val(val),next(nullptr){}
}
//初始化链表
MyLinkedList() {
this->size=0;
}
//正确答案
//MyLinkedList(){
_dummyHead=new LinkedNode(0);//这里定义的头结点 是一个虚拟头结点
_size=0;
}
//问题1:初始化链表的意思是什么,为什么这样子就可以算是初始化链表
//功能1寻找地址返回值
int get(int index) {
if(index>0&&index<size)
{
LinkNode * hummy= new LinkNode(0);
hummy->next=head;//问题3:head在哪里被定义,这些定义的作用域在哪?
LinkNode *cur=hummy;
while(cur->next!=nullptr&&cur!=nullptr)
{
if(cur->next==index)
{
return cur->next->val;
}
else
{
cur=cur->next;
}
}
}
else
{
return -1;
}
}
//答案2:
Int get(int index){
If(index>(_size-1)||index<0)//这里需要注意index范围是在0-size-1
{
Return -1;
}
LinkedNode *cur=_dummyHead->next;
While(index--)
{
Cur=cur->next;
}
Return cur->val;
}
//错误原因:index是个数,不是地址
//功能2:在链表的最前面插入一个节点,插入完成后,新插入的节点为链表的新的头结点
//答案:
Void addAtHead(int val)
{
LinkedNode * newNode=new LinkedNode(val);
}
//
//功能3:
void addAtHead(int val) {
LinkedNode *newNode=new LinkedNode(val);
newNode->next=_dummyHead->next;
_dummyHead->next=newNode;
_size++;
}
//功能4:在链表最后加一个节点
void addAtTail(int val) {
LinkedNode*newNode=new LinkedNode(val);
While(cur->next!=nullptr)
{
Cur=cur->next;
}
Cur->next=newNode;
_size++;
}
//问题:为什么new可以直接引用构造函数,而不通过结构名
void addAtIndex(int index, int val) {
}
void deleteAtIndex(int index) {
if(index>0&&index<size)
{
LinkNode* hummy=new LinkNode(0);//问题:linkNode是一个虚拟头结点吗,它的里面有一个值0和一个指向空的结点
hummy->next=head;
LinkNode* cur=hummy;
while(cur->next!=nullptr&&cur!=nullptr)
{
LinkNode* temp;
if(cur->next==index)
{
temp=cur->next;
cur->next=cur->next->next;
delete temp;//问题:为什么要给temp赋值为空
}
else
{
cur=cur->next;
}
}
}
}
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
class MyLinkedList {
public:
struct LinkNode()
{
int val;
LinkNode *next;
LinkNode(int val):val(val),next(nullptr){}
}
MyLinkedList() {
size=0;
dummyhead=new ListNode(0);
dummyhead->next=head;
}
//功能1寻找地址返回值
//如果链表的元素小于零或者元素大于size,则返回-1;
//如果链表的元素>=0且<=size-1,则获取链表中下标为indexd的值
//思路:创建一个头结点,通过index循环寻找size=index。当size=index的时候,返回此时对应的值val
int get(int index) {
if(index>=0&&index<size) //问题1:index为什么是从0开始
{
//寻找index对应的地址
LinkNode*dummyhead=new LinkNode(0);
LinkNode* cur=dummyhead;
while(index--)
{
cur=cur->next;
}
return cur->next->val;
else
{
return -1;
}
}
//功能2:将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
//思路
//
void addAtHead(int val) {
LinkNode* NewNode=new LinkNode(val);
dummyhead->next=NewNode;
NewNode->next=dummyhead->next;
size++;
}
//功能3:void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
void addAtTail(int val) {
LinkNode* NewNode=new LinkNode(val);
LinkNode* cur=dummyhead;
while(cur->next!=nullptr)
{
cur=cur->next;
}
cur->next=NewNode;
size++;
}
//功能4:void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
//思路:如果>返回空
// 如果=,插入到末尾
void addAtIndex(int index, int val) {
if(index>size)return ;
if(0<=index&&index<size)
{
LinkNode* NewNode=new LinkNode(val);
LinkNode* cur=dummyhead;
while(index--)
{
cur=cur->next;
}
NewNode->next=cur->next;
cur->next=NewNode;
size++;
}
}
//功能5:void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。
void deleteAtIndex(int index) {
if(index>0&&index<size)
{
LinkNode* NewNode=new LinkNode(val);//问题:linkNode是一个虚拟头结点吗,它的里面有一个值0和一个指向空的结点
LinkNode* cur=dummyhead;
while(index--)
{
cur=cur->next;
}
temp=cur->next;
cur->next=cur->next->next;
delete temp;
size--;
temp=nullptr;
}
}
//功能6:打印链表
void printnode()
{
LinkNode* cur=dummyhead;
while(cur->next!=nullptr)
{
cur=cur->next;
cout<<cur->next->val;
}
}
private:
int size;
LinkNode* dummyhead;//不是int类型int dummyhead;
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
class MyLinkedList {
public:
struct LinkNode{//问题1:没有括号
int val;
LinkNode *next;
LinkNode(int val):val(val),next(nullptr){}
};//问题2:没有分号
MyLinkedList() {
dummyhead=new LinkNode(0);
size=0;
// dummyhead->next=head;//问题3:没有这个头结点吗
}
//功能1寻找地址返回值
//如果链表的元素小于零或者元素大于size,则返回-1;
//如果链表的元素>=0且<=size-1,则获取链表中下标为indexd的值
//思路:创建一个头结点,通过index循环寻找size=index。当size=index的时候,返回此时对应的值val
int get(int index) {
if(index>=0&&index<size) //问题1:index为什么是从0开始
{
//寻找index对应的地址
LinkNode* cur=dummyhead;
while(index--)
{
cur=cur->next;
}
return cur->next->val;
}
else
{
return -1;
}
}
//功能2:将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
//思路
//
void addAtHead(int val) {
LinkNode* NewNode=new LinkNode(val);
NewNode->next=dummyhead->next;
dummyhead->next=NewNode;
size++;
}
//功能3:void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
void addAtTail(int val) {
LinkNode* NewNode=new LinkNode(val);
LinkNode* cur=dummyhead;
while(cur->next !=nullptr)
{
cur=cur->next;
}
cur->next=NewNode;
size++;
}
//功能4:void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
//思路:如果>返回空
// 如果=,插入到末尾
void addAtIndex(int index, int val) {
if(index>size)return ;
if(0<=index&&index<size)
{
LinkNode* NewNode=new LinkNode(val);
LinkNode* cur=dummyhead;
if (index < 0) index = 0;//问题补充:为什么要补充这个,题目又没说
while(index--)
{
cur=cur->next;
}
NewNode->next=cur->next;
cur->next=NewNode;
size++;
}
}
//功能5:void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。
void deleteAtIndex(int index) {
if(index>0&&index<size)
{
LinkNode* cur=dummyhead;
LinkNode* temp;
while(index--)
{
cur=cur->next;
}
temp=cur->next;
cur->next=cur->next->next;
delete temp;
size--;
temp=nullptr;//一定要这样吗
}
else
{
return;}
}
//功能6:打印链表
void printnode()
{
LinkNode* cur=dummyhead;
while(cur->next!=nullptr)
{
cout<<cur->next->val;
cur=cur->next;
}
cout<<endl;
}
private:
int size;
LinkNode* dummyhead;//不是int类型int dummyhead;
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
下面是对您的自定义链表中各个操作的时间复杂度分析:
总体来说,这个自定义链表的操作主要在查找节点位置时会受到链表长度的影响,因此大多数操作的时间复杂度都是 O(n),其中 n 是链表的长度。
来自 <https://chat.openai.com/?model=text-davinci-002-render-sha>
- 问题汇总
- 太多问题了,检索问题的地方就是问题
- get(int index) 函数:
- 时间复杂度:O(n)
- 在最坏情况下,需要遍历整个链表来找到索引为 index 的节点。
- addAtHead(int val) 函数:
- 时间复杂度:O(1)
- 只需创建一个新节点并更新链表头指针,不受链表长度的影响。
- addAtTail(int val) 函数:
- 时间复杂度:O(n)
- 在最坏情况下,需要遍历整个链表来找到链表的尾部,然后添加新节点。
- addAtIndex(int index, int val) 函数:
- 时间复杂度:O(n)
- 在最坏情况下,需要遍历整个链表以找到索引为 index 的位置,然后插入新节点。如果 index 等于链表的长度,那么时间复杂度为 O(1)。
- deleteAtIndex(int index) 函数:
- 时间复杂度:O(n)
- 在最坏情况下,需要遍历整个链表以找到索引为 index 的位置,然后删除节点。
- 时间复杂度分析
- 修改后通过代码
- 代码随想录思路
- 根据思路修改后代码
- void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。
- 思路
- 思路一:头结点+以后的结点
- 思路二:虚拟头结点
- 创造一个虚拟头结点,然后寻找这个虚拟头结点
- 思路
- Int get(int index)获取链表中下标为index的节点的值。如果下标无效,则返回-1。
- 思路:
- 判断index是否有效
- 如果有效,就在链表中使用指针寻找index对应的地址,并返回对应的值
- 方法:虚拟头结点
- 如果无效就返回-1
- 问题2:为什么size的大小从哪里来,会在main函数里面给定吗
- Void addAtHead(int val)将一个值为VAL的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
- 思路:让这个结点的地址给头结点中的next,然后让原来第一个节点中的地址给现在的第一个结点
- 问题4:如何知道传递过来的值为val的地址,它也没有在结点里面呀,怎么知道呢
- Void addAtTail(int val)将一个值为val的节点追加到链表中作为链表的最后一个元素。
- 思路:让链表的最后一个元素指向这个结点,让后这个结点的next地址指向空
- Void addAtIndex(int index,int val)将一个值为val的节点插入到链表中下标为index的节点之前。如果index等于链表的长度,那么该节点会被追加到链表的末尾。如果index比长度更大。
- 思路:
- 判断该index是否比在这个范围里面更大
- 如果在的话就判断index是否等于链表的长度
- 如果不等于,就让val插到index的结点之前
- 如果等于,就让val插到链表末尾
- 如果不在的话就不插入
- 如果在的话就判断index是否等于链表的长度
- 判断该index是否比在这个范围里面更大
- 思路:
- 选择使用单链表或者双链表,设计并实现自己的链表。
- 单链表中的节点:val和next
- 实现MyLinkedList()初始化MyLinkedList对象。
- Mylinkedlist它的对象是什么
- 初始化val和next吗
- 那我们应该要有输入,然后再初始化节点
- 问题1:初始化链表的意思是什么;
- Mylinkedlist它的对象是什么
- 实现MyLinkedList()初始化MyLinkedList对象。
- 单链表中的节点:val和next
-
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) { int i=0; ListNode*one=head; ListNode*cur=head; ListNode*temp=nullptr;//偏差1:没有在后面加上=nullptr //偏差2:从上一个题目中以为链表的长度是给定的,所以没有统计链表的长度 int size=0; while(cur!=nullptr) { size++; cur=cur->next; } int j=size-i-1; while(i<size/2) { cur=head; while(j) { cur=cur->next; j--; } temp=new ListNode(one->val); //temp=one;//偏差3:这里temp是一个指针,但是这个指针初始定义指向空,所以不能直接调用 one->val=cur->val; cur->val=temp->val; i++; one=one->next; //偏差4:充值j j=size-i-1; } return head; } void Nodeprint(ListNode* head) { ListNode* cur=head; while(cur !=nullptr) { cout<<cur->val; cur=cur->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* temp; // 保存cur的下一个节点 ListNode* cur = head; ListNode* pre = NULL; while(cur) { temp = cur->next; // 保存一下 cur的下一个节点,因为接下来要改变cur->next cur->next = pre; // 翻转操作 // 更新pre 和 cur指针 pre = cur; cur = temp; } return pre; } };
- 时间复杂度:o(n)
- 代码
- 时间复杂度
- N^2次
- 代码随想录
- 思路
- 改变指针的方向
- 通过改变指针的方向来改变
- 思路
- 我的思路
- 反向双指针,将首链表元素和尾链表元素交换
- 问题
- 这个链表好像只有一个方向,那么指向尾部元素的指针不能返回
- 问题
- 同向双指针
- 思路:
- 记录第一个元素的值,让一个指针指向左边要交换的元素,让第二个指针遍历到右边要交换的元素,然后让她们两个交换。之后迭代左边指针往右移动,而第二个指针遍历的元素少一个。
- 思路:
- 修改后代码
- 反向双指针,将首链表元素和尾链表元素交换