今天晚上刷的是链表的题目,加油!!!
今日任务:
- 链表的理论基础
- 移除链表元素
- 设计链表(较为综合)
- 翻转链表
- 两两交换链表中的节点
文章目录
一、链表的理论基础
1.1 什么是链表?
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,即数据域和指针域(存放指向一个节点的指针),最后一个节点的指针域指向null(空指针)。链表的入口节点称为链表的头节点也就是head。
1.2 链表的类型
1.2.1 单链表
上面说的就是单链表的问题。
1.2.2 双链表
双链表中有两个指针域(即prev 和 next),一个指向下一个节点,一个指向上一个节点。双链表既可以向前查询,也可以向后查询。
1.2.3 循环链表
循环链表,顾名思义,就是链表首尾相连。循环链表可以解决 约瑟夫环的问题。
1.3 链表的存储方式
链表在内存中不是连续分布的,它是通过指针域的指针链接在内存中的各个节点。它在内存中不是连续分布的,而是散乱分布在内存中的某个地址上,分配机制取决于操作系统的内存管理。
1.4 代码中如何实现链表?
1.4.1 链表的定义
单链表的定义方式如下:
struct ListNode{
int val; // 节点上存储的元素
ListNode *next; // 指向下一个函数的指针
ListNode(int x): val(x), next(NULL){} // 节点的构造函数
}
1.4.2 链表和数组的对比
- 数组的查询很快,但是删除或者插入一个元素很慢;
- 链表的查询较慢,但是删除或者插入一个元素容易。
- 数组在定义的时候,长度就是固定的,如果想要改动数组的长度,那么就需要重新定义一个新的数组。
- 链表的长度可以是不固定的,可以动态地增删,适合数据量不固定地,频繁增删,较少查询地场景。
对于解决链表的题目,我们都是采用虚拟头节点的方式来实现,因为这样可以完成删除头节点和其他节点的代码的统一。
二、203.移除链表元素
Leetcode题目:【203.移除链表元素】
这个题目是链表中最简单的题(考察对链表的操作),但是需要注意下面几点:
(1)节点的结构体定义如何去写(模板一定要记得牢牢的);
(2)虚拟头节点的初始化操作如何实现;
(3)如何将节点通过delete删除;
/**
* 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* dummyNode = new ListNode(0);
dummyNode -> next = head;
ListNode* cur = dummyNode;
while(cur->next!=NULL){
if(cur -> next -> val == val){
ListNode* temp = cur -> next;
cur -> next = cur -> next -> next;
delete temp;
}else{
cur = cur -> next;
}
}
head = dummyNode -> next;
delete dummyNode;
return head;
}
};
三、707.设计链表(最综合的一道题目)
Leetcode题目:【707.设计链表】
通过这个题目,我们其实可以对链表的操作有比较深入的了解,比较难的点在于:
(1)如何去定义节点的结构体,最后的结构体定义是有“;”的。
(2)指针随索引偏移的时候,到底偏移多少单位(跟题目中要求索引是从0开始还是1开始有关系),这个需要自己尝试;
(3)全局变量还定义个一个size的问题,用于统计偏移量;
(4)在调试的时候,每操作一步需要最好打印完整个链表中的数据,以方便调试。
class MyLinkedList {
public:
struct ListNode{
int val;
ListNode *next;
ListNode(int x): val(x), next(NULL){}
};
// 构造函数初始化
MyLinkedList(){
dummyNode = new ListNode(0);
cur = dummyNode;
cout << "new object is already built" << endl;
}
// 获取下标节点的值
int get(int index){
if(size == 0 || index >= size){
return -1;
}else{
cur = dummyNode;
for(int i = 0; i<=index; i++){
cur = cur -> next;
}
return cur -> val;
}
//printList();
}
// 添加头节点
void addAtHead(int val){
ListNode *head = new ListNode(val);
ListNode *temp = dummyNode -> next;
dummyNode -> next = head;
head -> next = temp;
size++;
//printList();
}
// 添加尾节点
void addAtTail(int val){
ListNode *tail = new ListNode(val);
cur = dummyNode;
while(cur -> next != NULL){
cur = cur -> next;
}
cur -> next = tail;
size++;
//printList();
}
// 插入中间索引的某一个值
void addAtIndex(int index, int val){
if(index>size) return;
ListNode *midNode = new ListNode(val);
cur = dummyNode;
for(int i = 0; i<index; i++){
cur = cur -> next;
}
ListNode *temp = cur -> next;
cur -> next = midNode;
cur -> next -> next = temp;
size++;
//printList();
}
// 删除某一个索引值
void deleteAtIndex(int index){
// 这句话没加
if(index >= size ||index<0){return ;}
cur = dummyNode;
for(int i = 0; i<index; i++){
cur = cur -> next;
}
ListNode *tmp = cur -> next;
cur->next = cur -> next -> next;
delete tmp;
tmp = NULL;
size--;
//printList();
}
// 遍历链表元素
void printList(){
cur = dummyNode;
while(cur->next != NULL){
cout << cur -> next -> val << " ";
cur = cur -> next;
}
cout << endl;
}
private:
ListNode * dummyNode = NULL;
ListNode* cur = NULL;
int size = 0;
};
/**
* 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.反转链表
Leetcode题目:【206.反转链表】
很有意思的一道题,当时就是一位大佬去谷歌面试不会写反转链表,然后就被谷歌拒绝了。
这个方法很巧妙,采用双指针,一个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* reverseList(ListNode* head) {
ListNode *pre = NULL, *cur;
cur = head;
while(cur != NULL){
ListNode* tmp = cur -> next;
cur -> next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
};
五、24.两两交换链表中的节点
Leetcode题目:【24. 两两交换链表中的节点】
这个题目也基本上是链表的操作题,一定要画图,不画图做不出来的,
需要注意的是:
(1)while的临界条件:因为需要保证cur后面有两个节点,才不会指向空指针,因此需要满足本节点的下一个节点和下下节点均不为空;
(2)cur最终要移到1的位置,后面指向要交换的节点,不要搞混了。
/**
* 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* swapPairs(ListNode* head) {
ListNode* dummyNode = new ListNode(0);
ListNode* cur = dummyNode;
dummyNode -> next = head;
// 因为要交换指针后面的两个节点,所以需要保证下一个节点和下下节点均不为空才可以
while(cur -> next!= NULL && cur -> next -> next != NULL){
ListNode *temp = cur -> next;
ListNode *temp1 = cur -> next -> next -> next;
cur -> next = cur -> next -> next; // 步骤一
cur -> next -> next = temp; // 步骤二
cur -> next -> next -> next = temp1; // 步骤三
cur = cur -> next -> next;
}
return dummyNode -> next;
}
};