题目引用
- 移除链表元素
- 设计链表
- 翻转链表
链表介绍
链表与数组不同的是,它是以指针串联在一起的分布在内存随机位置上的,而不是像指针一样占用整块的连续空间。因此也不支持通过指针++读取。所以在题目里面总是比较抽象,需要通过画图来帮助解题。一般出现在算法里面的都会是单链表,结构形如
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) {}
1.移除链表元素
来看一下题目~
题目要求我们删除链表中 ==val
的元素,因此我们需要遍历一遍数组将 ==val
的节点删掉,怎么删呢?
首先定义一个cur
指针,当cur指针所在节点的下一个节点的值 ==val
时,我们使用一个tmp
指针指向它,然后 cur
指针指向的节点将 next
指向 tmp
的下一个节点,完成删除。
最后附上代码:
ListNode* removeElements(ListNode* head, int val) {
while(head!=NULL&&head->val==val){
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;
}
这里值得注意的是头结点位置的值,如果头结点的值 ==val
的话,需要我们直接从头结点删除,并把头结点指针移动到下一个位置。
设计链表
这里我们就选择难度较大的单链表来实现吧
首先需要自己定义一个链表节点,这个在文章开头就有,直接copy~
然后在类内初始化链表长度_size
和一个虚拟头指针dummyhead
。
首先是构造函数,在构造函数内给_size
和dummyhead
赋值和开辟空间
MyLinkedList() {
_size=0;
dummyHead=new ListNode(0);
}
然后是get
函数,返回index
位置的值,这里需要判断index
位置是否越界,接着遍历链表找到index
位置的节点并返回val
。
int get(int index) {
if(index<0||index>(_size-1))
return -1;
ListNode* cur=dummyHead->next;
while(index--){
cur=cur->next;
}
return cur->_val;
}
头插函数,这里new
一个新节点,将新节点的next
指针指向dummyhead
的下一个节点,dummyhead
的next
指向新节点,别忘了++_size
。
void addAtHead(int val) {
ListNode* newNode=new ListNode(val);
newNode->next=dummyHead->next;
dummyHead->next=newNode;
_size++;
}
尾插,一路循环到链表的尾节点,将尾节点的next
指针指向新节点
void addAtTail(int val) {
ListNode* newNode=new ListNode(val);
ListNode*cur=dummyHead;
while(cur->next!=nullptr){
cur=cur->next;
}
cur->next=newNode;
_size++;
}
接下来的插入删除指定位置函数都比较简单,就直接上完整代码吧
class MyLinkedList {
public:
struct ListNode{
struct ListNode* next;
int _val;
ListNode(int val):_val(val),next(nullptr){}
};
MyLinkedList() {
_size=0;
dummyHead=new ListNode(0);
}
int get(int index) {
if(index<0||index>(_size-1))
return -1;
ListNode* cur=dummyHead->next;
while(index--){
cur=cur->next;
}
return cur->_val;
}
void addAtHead(int val) {
ListNode* newNode=new ListNode(val);
newNode->next=dummyHead->next;
dummyHead->next=newNode;
_size++;
}
void addAtTail(int val) {
ListNode* newNode=new ListNode(val);
ListNode*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;
ListNode* newNode=new ListNode(val);
ListNode* cur=dummyHead;
while(index--){
cur=cur->next;
}
newNode->next=cur->next;
cur->next=newNode;
_size++;
}
void deleteAtIndex(int index) {
if(index<0||(index>=_size))
return;
ListNode* cur=dummyHead;
while(index--){
cur=cur->next;
}
ListNode* tmp=cur->next;
cur->next=cur->next->next;
delete tmp;
_size--;
}
private:
int _size;
ListNode* dummyHead;
};
反转链表
我们来看一下题目,让我们将链表翻转之后返回头节点的指针。
这里有两种办法:一种从前往后遍历数组,一边将当前节点next
指针指向下一个,一边向前遍历,最后返回当前节点的指针。
另一种通过递归直接来到倒数第二个节点位置,将尾节点的next
的指针指向自己,再返回上一层调用。
我们两种都讲
第一种
我们定义cur
指针指向head
,pre
指针用于指向cur
的前一个节点,再定义一个tmp
用于记录cur
指针的下一个位置
只要cur
指针指向的节点存在,就使tmp
指向cur
指针的下一个节点,并把cur
的next
指针指向pre
所在位置,再将pre
移动到cur
位置cur
移动到tmp
位置,三个指针一次循环同时向前移动一次直到循环结束。
这里就附上代码供大家理解一下:
ListNode* reverseList(ListNode* head) {
ListNode* pre=NULL;
ListNode* cur=head;
ListNode* tmp;
while(cur){
tmp=cur->next;
cur->next=pre;
pre=cur;
cur=tmp;
}
return pre;
}
第二种写法:
我们利用递归找到倒数第二个节点,利用倒数第二个节点将尾节点的next
指针指向自己,然后返回上一层递归,不断调用直到所有节点的next
指针都被翻转,返回接收的头指针。来看一下图。
附上代码:
ListNode* reverseList(ListNode* head) {
// 边缘条件判断
if(head == NULL) return NULL;
if (head->next == NULL) return head;
// 递归调用,翻转第二个节点开始往后的链表
ListNode *last = reverseList(head->next);
// 翻转头节点与第二个节点的指向
head->next->next = head;
// 此时的 head 节点为尾节点,next 需要指向 NULL
head->next = NULL;
return last;
}
总结
在遇到链表问题时,初学者切忌空想,一定要多画图,多思考,当你做出来一道时,就离做出一百道不远啦~