
一、概念
1. 数组

2. 链表
链表和数组一样,可以用于存储一系列的元素,但是 链表和数组的实现机制完全不同

链表类似于火车:有一个火车头,火车头会连接一个节点,节点上有乘客(类似于数据),并且这个节点会连接下一个节点,以此类推

3. 链表常见操作
append(element):向链表尾部添加一个新的项
insert(position,element):向链表的特定位置插入一个新的项
get(position) :获取对应位置的元素
indexOf(element):返回元素在链表中的索引。如果链表中没有该元素则返回-1
update(position,element) :修改某个位置的元素
removeAt(position):从链表的特定位置移除一项
remove(element):从链表中移除一项
isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false
size():返回链表包含的元素个数。与数组的length属性类似
二、代码实现
1. 封装链表结构
/**
* 封装节点类
*/
class Node<T> {
value: T;
next: Node<T> | null = null;
constructor(value: T) {
this.value = value;
}
}
/**
* 封装链表类
*/
class LickedList<T> {
private head: Node<T> | null = null;
private size: number = 0;
get length() {
return this.size;
}
}
2. 封装链表方法
append - 追加方法
向链表尾部追加数据可能有两种情况:
链表本身为空,新添加的数据时唯一的节点
链表不为空,需要向其他节点后面追加节点

append(value: T) {
// 1. 创建一个新的节点
const newNode = new Node(value);
// 2. 判断链表是否为空
if (!this.head) {
this.head = newNode;
} else {
// 3. 创建一个临时指针
let current = this.head;
while (current.next) {
// 4. 指针往后挪移一次
current = current.next;
}
// 5. 拼接到最后一个元素的next上
current.next = newNode;
}
this.size++;
}
traverse - 遍历方法

traverse(): string {
// 1. 定义结果数组
const res: T[] = [];
// 2. 定义临时指向头部初始指针
let current = this.head;
// 3. 当前节点存在时
while (current) {
// 4. 保存当前节点值
res.push(current.value);
// 5. 指针往后挪移一次
current = current.next;
}
// 5. 返回拼接好的数据
return res.join(' -> ');
}
inset - 插入方法

insert(position: number, value: T): boolean {
// 1. 边界处理 :
if (position < 0 || position > this.size) return false;
// 2. 创建新节点
const newNode = new Node(value);
// 3. 头部位置插入 => 比较特殊
if (position === 0) {
// 注意 : 顺序不能出错
newNode.next = this.head;
this.head = newNode;
} else {
// 4. 创建临时指针
let current = this.head!;
// 5. 其他位置插入 => 让临时指针指向需更改元素的前一个位置
for (let i = 0; i < position - 1; i++) {
current = current.next!;
}
// 6. 保存原先指向
const tempNode = current.next;
// 7. 当前节点指向新节点
current.next = newNode;
// 8. 新节点指向原先的指向
newNode.next = tempNode;
}
this.size++;
return true;
}
removeAt - 删除方法

removeAt(position) :根据下标删除某个位置的元素
removeAt(position: number): Node<T> | null {
// 0. 边界处理 :
if (position < 0 || position >= this.size) return null;
/**
* 采用双指针
*/
// 1. 指向当前节点
let current = this.head;
// 2. 指向前一个节点
let privious: Node<T> | null = null;
// 3. 删除头节点
if (position === 0) {
this.head = this.head!.next;
} else {
// 4. 删除其他位置节点
// 5. 循环后,current指向指定位置的节点,privious指向前一个位置
for (let i = 0; i < position; i++) {
privious = current;
current = current!.next;
}
// 6. 让前一个位置的next指向指定位置的next => 跳过当前节点
privious!.next = current?.next ?? null;
}
this.size--;
return current;
}
get - 获取方法
get(position) :获取对应位置的元素的值
get(position: number): T | null {
// 0. 边界值
if (position < 0 || position >= this.size) return null;
// 1. 指向头节点
let current = this.head;
for (let i = 0; i < position; i++) {
current = current!.next;
}
// 2. 返回查找到的节点
return current!.value;
}
updata - 修改方法
update(position,element) :修改某个位置的元素的值
update(position: number, value: T): boolean {
// 0. 边界值
if (position < 0 || position >= this.size) return false;
let current = this.head;
// 1. 查找到指定节点
for (let i = 0; i < position; i++) {
current = current!.next;
}
// 2. 修改数据
current!.value = value;
return true;
}
indexOf - 查找索引
indexOf(position) :根据元素获取它在链表中的位置
indexOf(value: T): number {
// 1. 指向头节点
let current = this.head;
// 2. 初始下标
let index = -1;
// 3. 当节点存在时
while (current) {
// 4. 下标值加一
index++;
// 5. 判断是否查询到该元素
if (current.value === value) {
// 6. 查询到直接返回
return index;
}
// 6. 没查询到,重新循环
current = current.next;
}
// 7. 没有找到,返回-1
return -1;
}
remove - 删除方法
remove(value) :获取元素的值删除元素
remove(value: T): Node<T> | null {
// 1. 查找到对应位置
const index = this.indexOf(value);
// 2. 删除元素
return this.removeAt(index);
}
3. 封装与重构
/**
* 封装节点类
*/
class Node<T> {
value: T;
next: Node<T> | null = null;
constructor(value: T) {
this.value = value;
}
}
/**
* 封装链表类
*/
class LickedList<T> {
head: Node<T> | null = null;
private size: number = 0;
get length() {
return this.size;
}
// 封装私有方法,根据位置获取索引
private getNode(position: number): Node<T> | null {
let current = this.head;
for (let i = 0; i < position; i++) {
current = current!.next;
}
// 2. 返回查找到的节点
return current;
}
// 追加
append(value: T): void {
// 1. 创建一个新的节点
const newNode = new Node(value);
// 2. 判断链表是否为空
if (!this.head) {
this.head = newNode;
} else {
// 3. 创建一个临时指针
let current = this.head;
while (current.next) {
current = current.next;
}
// 4. 拼接到最后一个元素的next上
current.next = newNode;
}
this.size++;
}
// 遍历
traverse(): string {
// 1. 定义结果数组
const res: T[] = [];
// 2. 定义临时指向头部初始指针
let current = this.head;
// 3. 当前节点存在时
while (current) {
// 4. 保存当前节点值
res.push(current.value);
// 5. 指针往后挪移一次
current = current.next;
}
// 5. 返回拼接好的数据
return res.join(' -> ');
}
// 插入
insert(position: number, value: T): boolean {
// 1. 边界处理 :
if (position < 0 || position > this.size) return false;
// 2. 创建新节点
const newNode = new Node(value);
// 3. 头部位置插入 => 比较特殊
if (position === 0) {
// 注意 : 顺序不能出错
newNode.next = this.head;
this.head = newNode;
} else {
// 4. 其他位置插入
// 5. 获取指定位置前一个节点
const previous = this.getNode(position - 1)!;
// 6. 新节点指向指定位置节点
newNode.next = previous.next;
// 前一个节点指向新节点
previous.next = newNode;
}
this.size++;
return true;
}
// 根据下标删除元素
removeAt(position: number): Node<T> | null {
// 0. 边界处理 :
if (position < 0 || position >= this.size) return null;
/**
* 采用双指针
*/
// 1. 指向当前节点
let current = this.head;
// 2. 删除头节点
if (position === 0) {
this.head = this.head!.next;
} else {
// 3. 删除其他位置节点
// 4. 获取指定位置前一个节点
const previous = this.getNode(position - 1)!;
// 6. 让前一个位置的next指向指定位置的next => 跳过当前节点
previous!.next = previous?.next?.next ?? null;
}
this.size--;
return current;
}
// 根据索引获取节点的值
get(position: number): T | null {
// 0. 边界值
if (position < 0 || position >= this.size) return null;
// 1. 获取当前节点
const current = this.getNode(position)!;
// 2. 返回查找到的节点
return current.value;
}
// 根据索引更新节点的值
update(position: number, value: T): boolean {
if (position < 0 || position >= this.size) return false;
// 1. 找到节点
let current = this.getNode(position);
// 修改值
current!.value = value;
return true;
}
// 根据值查找节点的索引
indexOf(value: T): number {
// 1. 指向头节点
let current = this.head;
// 2. 初始下标
let index = -1;
// 3. 当节点存在时
while (current) {
// 4. 下标值加一
index++;
// 5. 判断是否查询到该元素
if (current.value === value) {
// 6. 查询到直接返回
return index;
}
// 6. 没查询到,重新循环
current = current.next;
}
// 7. 没有找到,返回-1
return -1;
}
// 根据节点的值删除节点
remove(value: T): Node<T> | null {
// 1. 查找到对应位置
const index = this.indexOf(value);
// 2. 删除元素
return this.removeAt(index);
}
// 查看链表是否为空
isEmpty(): boolean {
return this.size === 0;
}
}
三、面试题
面试题 - 设计链表

class Node {
value: number;
next: Node | null = null;
constructor(value: number) {
this.value = value;
}
}
class MyLinkedList {
private head: Node | null = null;
private size: number = 0;
get(index: number): number {
let current = this.head;
let currentIndex = -1;
while (current) {
currentIndex++;
if (currentIndex === index) {
return current.value;
}
current = current.next;
}
return -1;
}
addAtHead(val: number): void {
const newNode = new Node(val);
let current = this.head;
if (!current) {
this.head = newNode;
} else {
newNode.next = this.head;
this.head = newNode;
}
this.size++;
}
addAtTail(val: number): void {
const newNode = new Node(val);
let current = this.head;
if (!current) {
this.head = newNode;
} else {
while (current.next) {
current = current!.next;
}
current.next = newNode;
}
this.size++;
}
addAtIndex(index: number, val: number): void {
if (index < 0 || index > this.size) return;
const newNode = new Node(val);
let current = this.head;
if (index === 0) {
newNode.next = current;
this.head = newNode;
} else {
// 找到前一个元素
for (let i = 0; i < index - 1; i++) {
current = current!.next;
}
newNode.next = current!.next;
current!.next = newNode;
}
this.size++;
}
deleteAtIndex(index: number): void {
if (index < 0 || index >= this.size) return;
let current = this.head;
if (index === 0) {
this.head = this.head!.next;
} else {
// 找到前一个元素
for (let i = 0; i < index - 1; i++) {
current = current!.next;
}
current!.next = current?.next?.next ?? null;
}
this.size--;
}
traverse() {
let res: number[] = [];
let current = this.head;
while (current) {
res.push(current.value);
current = current.next;
}
return res.join(' -> ');
}
}
export {}
面试题 - 删除链表中的节点


function deleteNode(node: ListNode | null): void {
// 1. 把传过来的节点的值,改成下一个节点的值
node!.val = node!.next!.val;
// 2. 把当前节点的next,指向下下个节点
node!.next = node!.next!.next;
// 3. 没错!!!实际上是把下一个节点删了
};