目录
数组的缺点
链表的优势
链表到底是什么?
链表结构的封装
链表常见操作
append方法
链表的遍历方法(traverse)
insert方法
// 插入方法: abc
insert(value: T, position: number): boolean {
// 1.边界判断--
if (position < 0 || position > this.size) return false;
// 2.根据value创建新的值
const newNode = new Node(value);
// 3.进行value节点的插入
// 3.1当插入的是头部
if (position === 0) {
newNode.next = this.head;
this.head = newNode;
}
// 3.2插入其他位置
else {
let current = this.head;
let previous: Node<T> | null = null;
let index = 0;
while (index++ < position && current) {
previous = current;
current = current.next;
}
newNode.next = current;
previous!.next = newNode;
}
// 4.链表长度+1
this.size++;
return true;
}
removeAt方法
get方法
遍历结点的操作重构
update方法
indexOf方法
remove方法以及其他方法
707. 设计链表 -字节、腾讯等公司面试题
// 1.创建Node节点类
class Node<T> {
value: T;
next: Node<T> | null = null;
constructor(value: T) {
this.value = value;
}
}
// 2.创建LinkedList的类
class LinkedList<T> {
private head: Node<T> | null = null;
private size: number = 0;
get length() {
return this.size;
}
// 封装私有方法
// 根据position获取到当前的节点(不是节点的value, 而是获取节点)
private getNode(position: number): Node<T> | null {
let index = 0;
let current = this.head;
while (index++ < position && current) {
current = current.next;
}
return current;
}
// 追加节点
append(value: T) {
// 1.根据value创建一个新节点
const newNode = new Node(value);
// 2.判断this.head是否为null
if (!this.head) {
this.head = newNode;
} else {
let current = this.head;
while (current.next) {
current = current.next;
}
// current肯定是指向最后一个节点的
current.next = newNode;
}
// 3.size++
this.size++;
}
// 遍历链表的方法
traverse() {
const values: T[] = [];
let current = this.head;
while (current) {
values.push(current.value);
current = current.next;
}
console.log(values.join("->"));
}
// 插入方法:
insert(value: T, position: number): boolean {
// 1.越界的判断
if (position < 0 || position > this.size) return false;
// 2.根据value创建新的节点
const newNode = new Node(value);
// 3.判断是否需要插入头部
if (position === 0) {
newNode.next = this.head;
this.head = newNode;
} else {
const previous = this.getNode(position - 1);
newNode.next = previous!.next;
previous!.next = newNode;
}
this.size++;
return true;
}
// 删除方法: 根据位置删除
removeAt(position: number): T | null {
// 1.越界的判断
if (position < 0 || position >= this.size) return null;
// 2.判断是否是删除第一个节点
let current = this.head;
if (position === 0) {
this.head = current?.next ?? null;
} else {
// 重构成如下代码
const previous = this.getNode(position - 1);
// 找到需要的节点
previous!.next = previous?.next?.next ?? null;
}
this.size--;
return current?.value ?? null;
}
// 获取方法:
get(position: number): T | null {
// 越界问题
if (position < 0 || position >= this.size) return null;
// 2.查找元素, 并且范围元素
return this.getNode(position)?.value ?? null;
}
// 更新方法:
update(value: T, position: number): boolean {
if (position < 0 || position >= this.size) return false;
// 获取对应位置的节点, 直接更新即可
const currentNode = this.getNode(position);
currentNode!.value = value;
return true;
}
// 根据值, 获取对应位置的索引
indexOf(value: T): number {
// 从第一个节点开始, 向后遍历
let current = this.head;
let index = 0;
while (current) {
if (current.value === value) {
return index;
}
current = current.next;
index++;
}
return -1;
}
// 删除方法: 根据value删除节点
remove(value: T): T | null {
const index = this.indexOf(value);
return this.removeAt(index);
}
// 判读单链表是否为空的方法
isEmpty() {
return this.size === 0;
}
}
const linkedList = new LinkedList<string>();
console.log("------------ 测试append ------------");
linkedList.append("aaa");
linkedList.append("bbb");
linkedList.append("ccc");
linkedList.append("ddd");
linkedList.traverse();
console.log("------------ 测试insert ------------");
linkedList.insert("abc", 0);
linkedList.traverse();
linkedList.insert("cba", 2);
linkedList.insert("nba", 6);
linkedList.traverse();
// 测试删除节点
console.log("------------ 测试removeAt ------------");
linkedList.removeAt(0);
linkedList.removeAt(0);
linkedList.traverse();
console.log(linkedList.removeAt(2));
linkedList.traverse();
console.log(linkedList.removeAt(3));
linkedList.traverse();
console.log("------------ 测试get ------------");
console.log(linkedList.get(0));
console.log(linkedList.get(1));
console.log(linkedList.get(2));
console.log("------------ 测试update ------------");
linkedList.update("why", 1);
linkedList.update("kobe", 2);
console.log("------------ 测试indexOf ------------");
console.log(linkedList.indexOf("cba"));
console.log(linkedList.indexOf("why"));
console.log(linkedList.indexOf("kobe"));
console.log(linkedList.indexOf("james"));
console.log("------------ 测试remove ------------");
linkedList.remove("why");
linkedList.remove("cba");
linkedList.remove("kobe");
linkedList.traverse();
console.log(linkedList.isEmpty());
export {};
237. 删除链表中的节点 – 字节、阿里等公司面试题
class ListNode {
val: number;
next: ListNode | null;
constructor(val?: number, next?: ListNode | null) {
this.val = val === undefined ? 0 : val;
this.next = next === undefined ? null : next;
}
}
export default ListNode;
// 偷梁换柱 不修改获取的node节点,不用删除,将值改变,在将指针执行next.next,对应指向的认知要清晰
function deleteNode(node: ListNode | null): void {
node!.val = node!.next!.val;
node!.next = node!.next!.next;
}
206. 反转链表 – 字节、谷歌等面试题
import ListNode from "./面试题_ListNode";
function reverseList(head: ListNode | null): ListNode | null {
// 什么情况下链表不需要处理?
// 1.head本身为null的情况
if (head === null) return null;
// 2.只有head一个节点
if (head.next === null) return head;
// 数组模拟栈结构
const stack: ListNode[] = [];
let current: ListNode | null = head;
while (current) {
stack.push(current);
current = current.next;
}
// 以此从栈结构中取出元素, 放到一个新的链表中
const newHead: ListNode = stack.pop()!;
let newHeadCurrent = newHead;
while (stack.length) {
const node = stack.pop()!;
newHeadCurrent.next = node;
newHeadCurrent = newHeadCurrent.next;
}
// 注意: 获取到最后一个节点时, 一定要将节点的next置为null,不然最后一个next会还是执行前一个对象节点,形成循环引用,形成递归
newHeadCurrent.next = null;
return newHead;
}
// 模拟数据进行测试
const node1 = new ListNode(1);
node1.next = new ListNode(2);
node1.next.next = new ListNode(3);
// 通过for循环模拟数据进行测试
// const node1 = new ListNode(1);
// let current1 = node1;
// for (let i = 0; i < 5; i++) {
// current1.next = new ListNode(i);
// current1 = current1.next;
// }
const newHead = reverseList(node1);
let current = newHead;
while (current) {
console.log(current.val);
current = current.next;
}
export {};
206. 反转链表(非递归)
import ListNode from "./面试题_ListNode";
function reverseList(head: ListNode | null): ListNode | null {
// 1.判断节点为null, 或者只要一个节点, 那么直接返回即可
if (head === null || head.next === null) return head;
// 2.反转链表结构
let newHead: ListNode | null = null;
while (head) {
// 链表 --变量是指向地址的,赋予的是地址中存在的值,变量可以再次发送改变,之前的指向变量已经赋予过值的不会改变
// eg newHead = null (newHead值为null) head.next = newHead (head.next值为null) newHead = head (newHead的位为整个head节点 ,而head.next值依然为null)
// 1.让current节点指向下一个节点(head.next)
// 目前:保留着下一个节点的引用,可以获取并且不会销毁
const current: ListNode | null = head.next;
// 2.改变head.next当前指向的节点,指向newHead
// 对于第一个节点来说,指向的newHead就是指向null
head.next = newHead;
// 让newHead指向head节点
// 目的是下一次遍历时,第二步操作,可以让下一个节点指向前一个节点
newHead = head;
// 让head移向下一个节点,指向current
head = current;
}
return newHead;
}
// 模拟数据进行测试
const node1 = new ListNode(1);
node1.next = new ListNode(2);
node1.next.next = new ListNode(3);
const newHead = reverseList(node1);
let current = newHead;
while (current) {
console.log(current.val);
current = current.next;
}
export {};
14_面试题三-反转链表(递归)
import ListNode from "./面试题_ListNode";
let count = 1;
function reverseList(head: ListNode | null): ListNode | null {
// 如果使用的是递归, 那么递归必须有结束条件
if (head === null || head.next === null) return head;
const newHead = reverseList(head?.next ?? null);
// 完成想要做的操作是在这个位置
// 第一次来到这里的时候, 是倒数第二个节点 head是指向倒是第二个
// head.next.next是递归后链表中最后一个节点的next 指向head递归到的节点
head.next.next = head;
head.next = null;
return newHead;
}
// 模拟数据进行测试
const node1 = new ListNode(1);
node1.next = new ListNode(2);
node1.next.next = new ListNode(3);
const newHead = reverseList(node1);
let current = newHead;
while (current) {
console.log(current.val);
current = current.next;
}
export {};