前言
链表:链表主要是考基础操作多一点,基本的对链表进行增删查改,所以我们必须要熟悉链表的底层数据结构,题目二的设计链表就是最好的题目。
tips:关于链表需要遍历的问题,尽量使用伪头结点去处理,因为遍历操作一般是通过当前节点去改下一个节点,头结点会处于尴尬的地方,需要我们单独处理,为了简化操作,我们可以定义一个伪头,然后统一逻辑处理。
题目链接
一、移除元素
思路1:迭代的思路,加入头结点,for循环遍历,如果nxt 是目标值就delete,最后返回伪头的下一个元素。
思路2:递归的思路,不断返回新的头结点去,跳过目标节点,同时删除原节点。
//直接迭代,加入头结点,能大幅度降低代码逻辑
ListNode* removeElements1(ListNode* head, int val) {
ListNode dummy(0,head);
auto cur=&dummy;
while(cur->next){
auto nxt=cur->next;
if(nxt->val==val) cur->next=nxt->next,delete nxt;
else cur=nxt;
}
return dummy.next;
}
//递归的方法
ListNode* removeElements(ListNode* head, int val) {
if(head==nullptr)return nullptr;
if(head->val==val){
ListNode* newhead=removeElements(head->next,val);
delete head;
return newhead;
}else{
head->next=removeElements(head->next,val);
return head;
}
}
二、设计链表
思路1:设计一个单链表,首先我们需要一个节点的结构体,并显示定义节点的构造函数,链表初始化头结点,接着定义size即可
- get需要位置是否有效,然后去循环迭代得到下标索引index,去得到具体值。
- 头插入,伪头后面,直接构造一个新节点,新节点的下一个赋值给伪头的下一个,伪头等于新节点,然后size++
- 尾插入,构造新节点,循环遍历索引找空,直接插入。
- 索引index位置插入,结合一下get操作的思路,先对index合法性校验,然后--迭代,新节点的下一个赋值给伪头的下一个,伪头等于新节点,然后size++。就是头插入的操作
- 删除索引位置,一样的操作但是需要删除中间我们不想要的节点。
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--;
}
private:
int _size;
LinkedNode* _dummyhead;
};
三、反转链表
思路1:主要是迭代的思路,我个人总结了一个口诀,初始化、保存、翻转、更新更新,这样对于我们做这道题目能更好的理解翻转操作。
思路2:主要是递归的思路,他比迭代主要少了两行代码,在整体思路上面,我们可以看见主要是通过传递参数的不同去完成更新操作。
ListNode* reverseList1(ListNode* head) {
//迭代的方式,初始化操作,记住口诀,保存翻转更新。
ListNode* pre=nullptr,*cur=head,*nxt=nullptr;
while(cur){
nxt=cur->next;//保存
cur->next=pre;//翻转
pre=cur;//更新
cur=nxt;
}
return pre;
}
//递归的方式
ListNode* reverse(ListNode* pre,ListNode* cur){
if(cur==nullptr)return pre;
//翻转
ListNode* nxt=cur->next;
cur->next=pre;
return reverse(cur,nxt);//少了两步更新的操作,通过传参来解决
}
ListNode* reverseList(ListNode* head) {
return reverse(nullptr,head);
}
总结
总结一下我们常见的链表操作,主要熟悉增删查改,我们在写链表题目的时候最好画一个图,先保存什么,在修改什么,容易混乱。
增加:插入操作的时候,先保存下一个的指向,在挑战上一个的指向
删除:定义一个指针去释放下一个结点。