1.什么是链表?
- 链表是多个元素组成的列表
- 元素存储不连续,用next指针连接到一起
- JS中没有链表,但是可以用Object模拟链表
2.常用操作
- 新增节点 append
- 删除节点 remove
- 插入节点 insert
- 获取索引 indexOf
- 链表转字符串 toString
- 获取链表长度 size
- 判断链表是否为空 isEmpty
3.定义链表类
class Node { //节点类
//构造函数
constructor(val) {
this.val = val;
this.next = null;
}
}
class LinkedList { // 链表类
//构造函数
constructor() {
this.head = null;
this.length = 0;
}
//新增节点
append(val) {
let node = new Node(val);
let current; //暂存当前位置
if(this.head === null) { // 如果头结点为空,当前节点作为头结点
this.head = node;
} else {
current = this.head;
while(current.next) { //遍历找到链表尾部
current = current.next;
}
current.next = node; //在链表尾部加入新节点
}
this.length++; //更新链表长度
}
//删除节点,并获得删除节点的值
removeAt(index) {
if(index > -1 && index < this.length) { //预防下标越界
var current = this.head;//暂存当前位置
var previous; //暂存当前位置的前一个
var position = 0;
if(index === 0) { //要删除的是第一个位置,就得改变头指针指向
this.head = current.next;
} else {
while(position++ < index) { //遍历直到current处于index位置
previous = current;
current = current.next; //此时current处于index处,previous在index-1处
}
previous.next = current.next; //改变链表结构,跳过index处
}
this.length--; //更新链表长度
return current.val; //返回index处的值
} else {
return null; //下标越界返回空
}
}
//插入节点
insert(index,val) {
if(index > -1 && index <= this.length) {
let node = new Node(val);
let current = this.head;
let previous;
let position = 0;
if(index === 0) {
node.next = current;
this.head = node;
} else {
while(position++ < index) {
previous = current;
current = current.next;
}
node.next = current;
previous.next = node;
}
length++;
return true; //插入成功
} else {
return false; //插入失败
}
}
//获取索引
indexOf(val) {
let current = this.head;
let position = 0;
while(current) {
if(current.val === val) {
return position;
}
position++;
current = current.next;
}
return -1; //未找到索引
}
//将链表转换成字符串
toString() {
let current = this.head;
let string = '';
while(current) {
string += current.val + ((current.next ? ',': ''));
current = current.next;
}
return string;
}
//链表长度
size() {
return this.length;
}
//判断链表是否为空
isEmpty() {
return this.length === 0;
}
}
4.LeetCode
接下来使用链表这个数组结构来刷LeetCode有关链表的题目,巩固提升对链表的了解。
题号237.删除链表中的节点(简单)
题目要求:
解题思路:
题目直接给了当前节点,只需用下一位节点覆盖掉当前节点,就可以删除元素
编写代码:
var deleteNode = function(node) {
node.val = node.next.val;
node.next = node.next.next;
};
复杂度分析
- 事件复杂度O(1)
- 空间复杂度O(1)
题号206.反转链表(简单)
题目要求:
解题思路:
使用双指针遍历,在遍历链表时,将当前节点的指针改为指向前一个节点,需要存储后一个节点指针,最后返回新的头引用。
编写代码:
var reverseList = function(head) {
let p1 = head;
let p2 = null;
while(p1) {
const tmp = p1.next; //保存p1下一个节点
p1.next = p2; //p1下一个改为p2
p2 = p1; //p2指向p1当前位置
p1 = tmp;
}
return p2;
};
复杂度分析:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
题号2.两数相加(中等)
题目要求:
解题思路:
- 思路: 链表问题,遍历l1,l2,赋值给l3
- 边界条件
* l1 或 l2 哪个先结束,结束的用0替补,不影响相加
* 相加后是否产生进位
* 判断最高位是否产生进位,在循环外单独实现
编写代码:
var addTwoNumbers = function(l1, l2) {
let l3 = new ListNode();
let p1 = l1; //不要直接操作原链表
let p2 = l2;
let p3 = l3;
let carry = 0; //进位判断
while(p1 || p2) {
const val1 = p1 ? p1.val : 0;
const val2 = p2 ? p2.val : 0;
const val = Math.floor((val1 + val2 + carry) % 10);
carry = Math.floor((val1 + val2 + carry) / 10);
p3.next = new ListNode(val);
if(p1) p1 = p1.next;
if(p2) p2 = p2.next;
p3 = p3.next;
}
if(carry) { //判断最后一位是否有进位
p3.next = new ListNode(carry);
}
return l3.next;
}
复杂度分析:
- 事件复杂度: O(max(m,n)),m,n分别为两个链表的长度
- 空间复杂度: O(1),返回值不计入空间复杂度