1. 链表理论基础
链表是一种通过 指针 串联在一起的 线性结构,每个节点由两部分组成:
-
数据域:存放数据
-
指针域:存放指向下一节点的指针
链表的入口节点称为链表的头节点,即head
1.1 链表的几种类型
-
单链表:指针域只能指向节点的下一个节点
-
双链表:每个节点有两个指针域,一个指向下一个节点,一个指向上一个节点
-
循环列表:链表首尾相连
1.2 链表的存储方式
数组、向量在内存中都是连续分布的,但是链表在内存中不是连续分布的
1.3 链表的定义
/*链表节点结构体*/ struct ListNode { int val; // 定义val变量值,存储节点值 ListNode *next; // 定义next指针,指向下一个节点,维持节点连接 ListNode(int x) : val(x), next(Null) {} //节点的构造函数,初始化当前节点值为x,指针为空 }
具体解释:
-
在节点
ListNode
定义中,定义为节点为结构变量。 -
节点存储了两个变量:
value
和next
。value
是这个节点的值,next
是指向下一节点的指针,当next
为空指针时,这个节点是链表的最后一个节点。 -
注意
val
只代表当前指针的值,比如p->val
表示p
指针指向的值;而p->next
表示链表下一个节点,也是一个指针 -
构造函数包含两个参数
val()
和next()
,分别用来给节点赋值和指向下一个节点
利用操作符 ->
可以通过结构体指针访问结构体属性
ListNode* p
是指向结构节点的指针,里面只有一个地址
ListNode* p = new ListNode()
是一个结构节点,里面有val和指向下一个节点的结构体指针,而且该节点已经被系统分配内存,在函数体里不会被释放
2. 203移除链表元素
题目:删除链表中等于给定值val的所有节点
示例1:输入:head = [1,2,3,4,5,6], val = 6 输出:[1,2,3,4,5]
示例 2: 输入:head = [], val = 1 输出:[]
示例 3: 输入:head = [7,7,7,7], val = 7 输出:[]
虚拟头节点方法
#include<iostream>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode (int x): val(x), next(NULL) {}
};
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) { //返回一个链表
ListNode* dummyHead = new ListNode(0); //设置一个虚拟头节点
dummyHead->next = head; // 将虚拟头节点指向head
ListNode* cur = dummyHead; //将当前节点cur指向dummyHead
while (cur->next != NULL) {
if (cur->next->val == val) {
ListNode* temp = cur->next;
cur->next = temp->next;
delete temp;
}
else {
cur = cur->next;
}
}
head = dummyHead->next;
delete dummyHead;
return head; //返回头节点
}
};
int main(void) {
Solution solu;
ListNode* head = new ListNode(1);
ListNode* node1 = new ListNode(2);
ListNode* node2 = new ListNode(6);
ListNode* node3 = new ListNode(3);
ListNode* node4 = new ListNode(4);
ListNode* node5 = new ListNode(5);
ListNode* node6 = new ListNode(6);
head->next = node1;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = node5;
node5->next = node6;
ListNode* res = solu.removeElements(head, 1);
while (res != NULL) {
cout << res->val << endl;
res = res->next;
}
system("pause");
return 0;
}
3. 707设计链表
题目:在链表中实现这些功能
-
get(index): 获取链表中第 index 个节点的值。 如果索引无效,则返回-1.
-
addAtHead(val): 在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
-
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
-
addAtIndex(index, val): 在链表中的第 Index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
-
deleteAtIndex(index) : 如果索引 index 有效,则删除链表中的第 index 个节点。
这道题目设计链表的五个接口:
-
获取链表第index个节点的数值
-
在链表的最前面插入一个节点
-
在链表的最后面插入一个节点
-
在链表的第index个节点前面插入一个节点
-
删除链表的第index个节点
#include<iostream>
using namespace std;
class MyLinkedList {
public:
struct LinkedNode {
int val;
LinkedNode* next;
LinkedNode(int val) : val(val), next(nullptr) {}
};
//声明一个私有成员变量
private:
int _size;
LinkedNode* _dummyHead;
// 初始化链表
public:
MyLinkedList() {
_dummyHead = new LinkedNode(0); //定义虚拟头节点
_size = 0; //初始化链表长度为0
}
/*获取到第index个节点数值,如果index是非法数值直接返回 - 1
注意index是从0开始的,第0个节点就是头节点*/
int get(int index) {
if (index > (_size - 1) || index < 0) {
return -1; //index非法
}
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 != NULL) {
cur = cur->next;
}
cur->next = newNode;
_size++;
}
// 在第index个节点前插入一个新节点 如果index=0,那么新插入的节点为链表的新头节点
// 如果index等于链表的长度,则说明是新插入的节点为链表的尾节点
// 如果index大于链表的长度,则返回空
// 如果index小于0,则在头部插入节点
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++;
}
// 删除第index个节点,如果index大于等于链表的长度,直接return,注意index是从0开始的
void deleteAtIndex(int index) {
if (index >= _size || index < 0) {
return;
}
LinkedNode* cur = _dummyHead;
while (index--) {
cur = cur->next;
}
cur->next = cur->next->next;
_size--;
}
// 打印链表
void printLinkedList() {
LinkedNode* cur = _dummyHead;
while (cur->next != nullptr) {
cout << cur->next->val << " ";
cur = cur->next;
}
cout << endl;
}
};
int main(void) {
MyLinkedList* linkedlist = new MyLinkedList();
linkedlist->addAtHead(1);
linkedlist->addAtTail(3);
linkedlist->addAtIndex(1, 2); //链表变为1-> 2 ->3
linkedlist->printLinkedList();
cout << linkedlist->get(1) << endl; //返回2
cout << endl;
linkedlist->deleteAtIndex(1); //返回链表1-> 3
linkedlist->printLinkedList();
cout << linkedlist->get(1) << endl; //返回3
system("pause");
return 0;
}
4. 206翻转链表
题目:翻转一个单链表
示例:输入:1->2->3->4->5->NULL 输出:5->4->3->2->1-NULL
思路:
-
采用两个指针进行翻转操作
-
首先定义一个cur指针,指向头节点,再定义一个pre指针,初始化为NULL
-
开始翻转,将cur->next用temp保存,然后翻转指针指向,cur->next = pre(即NULL)
-
令pre = cur, cur = temp,以此类推直到cur指向null,循环结束,链表翻转完毕。
#include<iostream>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode(int val) : val(val), next(nullptr) {}
};
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* temp; // 保存cur的下一个节点
ListNode* cur = head;
ListNode* pre = NULL; // 反转cur的指针
while (cur) {
temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre; // 返回新的头节点
}
};
int main(void) {
Solution solu;
ListNode* head = new ListNode(1);
ListNode* node1 = new ListNode(2);
ListNode* node2 = new ListNode(3);
ListNode* node3 = new ListNode(4);
ListNode* node4 = new ListNode(5);
head->next = node1;
node1->next = node2;
node2->next = node3;
node3->next = node4;
ListNode* res = solu.reverseList(head); //ListNode* res意思是返回一个链表
while (res != NULL) {
cout << res->val << " ";
res = res->next; //res初始化为head,然后res指向head的下一个节点,即node1,然后res指向node1的下一个节点,即node2,以此类推
}
system("pause");
return 0;
}