数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
双链表
单链表中的指针域只能指向节点的下一个节点。
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表 既可以向前查询也可以向后查询。
问题:约瑟夫环是什么?循环列表为什么能解决约瑟夫环的问题?
链表的代码:
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
删除链表节点,对于c++需要释放内存,其他语言例如Java、Python,就有自己的内存回收机制,就不用自己手动释放了。
/**
* 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* virt=new ListNode(0,head); //要加new
virt->val=0;
virt->next=head;
ListNode* tmp;
tmp=virt;
while(tmp->next!=NULL){
if(tmp->next->val==val){
ListNode* tmp1= tmp->next;
// tmp->next =tmp1->next;
// tmp1->next=NULL;
tmp->next=tmp->next->next;
delete tmp1;
}
else{
tmp=tmp->next;
}
}
head=virt->next;
delete virt;
return head; //必须从头节点返回才行,直接选取head节点开始返回不行
}
};
参考了题解才改对的,就是主要是本来没有 head=virt->next; delete virt; 就直接返回head,可能因为有前他指针指向head,不能直接返回。要->next得到虚拟节点的下一个。
另外,是用ListNode* virt=new ListNode(0,head); ,也就是用new什么来声明一个指针。
delete tmp1;就是指针节点。
707.设计链表
链表操作的两种方式:
- 直接使用原来的链表来进行操作。
- 设置一个虚拟头结点在进行操作。
这道题需要自己定义链表节点结构体
MyLinkedList()是初始化函数
class MyLinkedList {
public:
struct linkedNode{
int val;
linkedNode* next;
linkedNode(int val):val(val),next(nullptr){}
};
MyLinkedList() {
// int num=0;
// linkedNode * head=new linkedNode(0);
_num=0;
_head=new linkedNode(0);
}
int get(int index) {
if(_num<=index) return -1;
linkedNode * tmp=_head;int tmpN=-1;
while(tmp->next!=NULL){
tmp=tmp->next;
tmpN++;
if(tmpN==index){
return tmp->val;
}
}
return -1;
}
void addAtHead(int val) {
// linkedNode * tmp=head;
linkedNode * newT=new linkedNode(val);
newT->next=_head->next;
_head->next=newT;
_num++;
}
void addAtTail(int val) {
linkedNode * newT=new linkedNode(val);
linkedNode * tmp=_head;//int tmpN=-1;
while(tmp->next!=NULL){
tmp=tmp->next;
}
_num++;
tmp->next=newT;
}
void addAtIndex(int index, int val) {
if(index>_num) return ;
linkedNode * newT=new linkedNode(val);
linkedNode * tmp=_head;int tmpN=-1;
while(tmp->next!=NULL){
tmpN++;
if(tmpN==index){
newT->next=tmp->next;
_num++;
tmp->next=newT;
return;
// return tmp->val;
}
tmp=tmp->next;
}
if((tmpN+1)==index){
tmp->next=newT;
_num++;
}
return ;
// tmp->next=newT;
}
void deleteAtIndex(int index) {
linkedNode * tmp=_head;int tmpN=-1;
while(tmp->next!=NULL){
tmpN++;
if(tmpN==index){
linkedNode * tmpN=tmp->next;
tmp->next=tmp->next->next;
delete tmpN;
_num--;
return;
// return tmp->val;
}
tmp=tmp->next;
}
}
private:
int _num;
linkedNode * _head;
};
/**
* 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);
*/
这道题需要自己再定义一个linkedNode结构体,一开始我不知道linkednode是不是应该放在mylinkedlist下面,mylinkedlist是初始化函数,所以是放在public下。而初始化函数是赋值初值的,在private:下面可以声明函数的私有成员变量,而不是在初始化函数声明。看了眼题解的写法才写出来,主要是忘记了c++的语法。
要注意index小于0时特判不合法。下面代码是复制过来的,为了提示自己tmp最好赋值个nullptr。
void deleteAtIndex(int index) {
if (index >= _size || index < 0) {
return;
}
LinkedNode* cur = _dummyHead;
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--;
}
/**
* 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) {
if(head==nullptr) return nullptr;
ListNode * tmp =head;
ListNode * tmpN;
tmpN= tmp->next;
ListNode* node;
while(tmpN!=NULL){
node=tmpN;
tmpN=tmpN->next;
node->next=tmp;
tmp=node;
// tmp=tmp->next;
}
head->next=NULL;
return tmp;
}
};
一遍ac,注意特判空列表的情况。比如有三个节点,第二个节点反过来要指向第一个,那原本遍历数组需要的第二个节点指向的下一个也要用其他指针指向保留。其实套用在具体例子下就比较好写了。相当于一个三个数的滑窗,每次窗口内要处理前两个数为后一个数指向前一个数。
其实就是双指针法,题解的方法是:
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;
}
};