算法小白训练营日记,笔记为自用,若有错误感谢指出
--今日任务--:链表理论基础 ,203.移除链表元素, 707.设计链表 ,206.反转链表
链表理论基础
(笔记只记录自己不清楚的一些点)
1.链表的定义:
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
C++可以默认生成构造函数,但构造函数不会初始化任何成员变量。例:
自己定义构造函数初始化节点:
ListNode* head = new ListNode(5);
但使用默认构造函数初始化节点:
ListNode* head = new ListNode();
head->val = 5;
不自己定义构造函数,直接使用默认构造函数的话,在初始化的时候不能直接给变量赋值。
2.数组和链表的特性对比
203.移除链表元素
题目链接:203. 移除链表元素 - 力扣(LeetCode)
删除节点时需知道节点的前一节点,使前一节点指向所删元素的下一节点,释放所删节点即为删除。
用原链表删除节点时,head节点与其他节点删除方法不同,需要单独分出情况讨论。解决方式为采用虚拟头节点,在head节点前设置虚拟头结点dummyhead,cur指针指向dummyhead(原因:结果需返回头结点,不能在dummyhead上进行删改操作,否则无法返回),当节点不为空时,若cur->next->val==val,设节点tmp=cur->next,令cur->next=cur->next->next,释放tmp节点,由此可删除满足条件的节点。若cur指针所指向的值不等于val,则cur变为下一元素:cur=cur->next。
具体代码如下:
/**
* 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;
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;
delete dummyhead;
return head;
}
};
707.设计链表
涉及到了五个链表基本问题,分别是:获取第n个节点值,链表头部插入节点,链表尾部插入节点,在第n个节点前插入节点,删除第n个节点。运用添加虚拟头结点的方式。(n从0开始)
数据结构学的还可以,整体思路很清晰但是书写算法时构造函数及其初始化和链表书写形式不是很熟练,还需多加练习,明天再写一遍此题巩固。
(1)获取第n个节点值,cur指针指向虚拟头结点的下一位,while(index--)时,cur=cur->next;遍历链表找到第n个节点值。
(2)链表头部插入节点,将新节点先指向虚拟头结点的下一位,再将虚拟头结点指向新节点。
(3)链表尾部插入节点,先遍历链表,当节点指向空时,则当前节点为最后一节点,将新节点连接在后面即可。
(4)在第n个节点前插入节点,遍历数组,使cur指针的下一位指向第n个节点,定义新节点newNode,使newNode->next=cur->next,cur->next=newNode进行插入操作。
(5)删除第n个节点,遍历数组,同样使cur指针的下一位指向所要删除的节点,令cur->next=cur->next->next,并释放已删除的节点。
添加节点时,一定新连接新节点与后面节点,再连接前节点。
具体代码如下:
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) {
if(index>(_size-1)||index<0){
return -1;
}
LinkedNode* cur=_dummyhead->next;
while(index--){
cur=cur->next;
}
return cur->val;
}
void addAtHead(int val) {
LinkedNode* newNode=new LinkedNode(val);
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++;
}
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++;
}
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;
tmp=nullptr;
_size--;
}
void printLinkedList(){
LinkedNode* cur=_dummyhead;
while(cur->next!=nullptr){
cout<<cur->next->val<<" ";
cur=cur->next;
}
cout<<endl;
}
private:
int _size;
LinkedNode* _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);
*/
206.反转链表
反转链表,修改每一个指针指向,由前指后变为后指向前,令cur指针指向head,head的前面应为NULL,定义为pre指针。指针方向通过cur->next=pre转换,变换pre和cur指针,定义tmp指针保存cur指针的值,防止变换指针时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* reverseList(ListNode* head) {
ListNode* cur=head;
ListNode* pre=NULL;
ListNode* tmp;
while(cur){
tmp=cur->next;
cur->next=pre;
pre=cur;
cur=tmp;
}
return pre;
}
};
另一种递归算法思想一致,将pre,cur指针变换过程改为函数递归的方式,具体代码如下:
/**
* 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* tmp=cur->next;
cur->next=pre;
return reverse(cur,tmp);
}
ListNode* reverseList(ListNode* head) {
return reverse(NULL,head);
}
};
--今日总结--
链表指针来回变换的思想比较容易理解,已经掌握了查找删除插入等基本的原理,但书写算法水平较差,还需多加练习巩固。