链表理论基础
简介
- 链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
- 链表的入口节点称为链表的头结点也就是head。
- 链表的类型:
- 单链表:单链表中的指针域只能指向节点的下一个节点。
- 双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。双链表 既可以向前查询也可以向后查询。
- 循环链表:就是链表首尾相连。
- 链表的存储方式:链表在内存中不像数组那样连续分布,而是通过指针域的指针链接在内存中各个节点,所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
- 链表节点的定义
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)
}
}
图示
203.移除链表元素
题目
删除链表中等于给定值 val 的所有节点。
示例 1: 输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
示例 2: 输入:head = [], val = 1 输出:[]
示例 3: 输入:head = [7,7,7,7], val = 7 输出:[]
解题思路
- 使用迭代法,代码实现上采用新增虚拟头节点的方式统一节点删除过程
- 声明虚拟头结点dummy且指针域指向head,声明cur初始化为dummy
- 当cur值不为空时执行while循环,在循环体中判断cur.next的值是否等于目标值,如相等则代表需移除cur.next节点(将cur.next赋值为cur.next.next)否则只需移动cur即可(cur赋值为cur.next)
- 最后返回链表起始点dummy.next;
代码实现
function removeElements(head: ListNode | null, val: number): ListNode | null {
const dummy = new ListNode(0, head);
let cur = dummy;
while (cur.next) {
if (cur.next.val === val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return dummy.next;
};
707.设计链表
题目
在链表类中实现这些功能:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
解题思路
- 题目要求实现一个链表,可以选择单链表或双链表。链表的基本操作包括插入节点、获取节点值、删除节点等。
- 单链表和双链表各有优缺点。单链表结构简单,但只能从头部开始遍历;双链表可以双向遍历,但结构复杂一些。根据题目要求,我们可以选择单链表来实现。
- 链表的节点至少包含两个属性:
val(节点值)和next(指向下一个节点的引用)。如果选择双链表,还需要一个prev属性。 MyLinkedList类需要实现以下功能:- 构造函数:初始化链表。
get(int index):根据索引获取节点值。addAtHead(int val):在链表头部添加节点。addAtTail(int val):在链表尾部添加节点。addAtIndex(int index, int val):在指定索引位置添加节点。deleteAtIndex(int index):删除指定索引位置的节点。
- 对于每个操作,我们需要考虑其时间复杂度和边界条件。例如,
get和deleteAtIndex操作需要遍历链表,时间复杂度为O(n)。
代码实现
class MyLinkedList {
private head: ListNode | null;
private length: number;
constructor() {
this.head = null;
this.length = 0;
}
get(index: number): number {
let node = this.head;
for (let i = 0; i < index && node !== null; i++) {
node = node.next;
}
return node !== null ? node.val : -1;
}
addAtHead(val: number): void {
const newNode = new ListNode(val);
newNode.next = this.head;
this.head = newNode;
this.length++;
}
addAtTail(val: number): void {
const newNode = new ListNode(val);
if (!this.head) {
this.head = newNode;
} else {
let node = this.head;
while (node.next) {
node = node.next;
}
node.next = newNode;
}
this.length++;
}
addAtIndex(index: number, val: number): void {
const newNode = new ListNode(val);
if (index < 0 || index > this.length) {
return;
}
if (index === 0) {
// 当index为0,即头部添加
newNode.next = this.head;
this.head = newNode;
} else if (index === this.length) {
// 当index等于链表长度,即尾部添加
this.addAtTail(val);
} else {
let prevNode = this.head;
for (let i = 0; i < index - 1; i++) {
prevNode = prevNode.next!;
}
newNode.next = prevNode.next;
prevNode.next = newNode;
this.length++;
}
}
deleteAtIndex(index: number): void {
if (index >= this.length || index < 0) {
return;
}
if (index === 0) {
this.head = this.head.next;
} else {
let node = this.head;
for (let i = 0; i < index - 1; i++) {
node = node.next;
}
node.next = node.next.next;
}
this.length--;
}
}
206.反转链表
题目
题意:反转一个单链表。
示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
解题思路
- 使用迭代法(双指针)解决该题目相对好理解,在原链表上逐个节点反转指针域指向即可,过程中需要定义临时变量用于存储反转指针域之前的指向
- 定义慢指针pre、临时变量temp均初始化为null,定义快指针初始化为链表头节点head
- 当cur节点不为空时执行while循环,循环中先将cur.next保存到temp中,再依次将cur.next指向pre,pre赋值为cur,cur赋值为temp值实现往后移动一位
- 最后返回pre这个新的头结点即可
代码实现
function reverseList(head: ListNode | null): ListNode | null {
let pre = null;
let cur = head;
let temp = null;
while(cur){
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre
};