书接上回# 链表算法题分析 - js篇,上一章是链表的定义,和一些简单的链表算法题。
因为苦逼的js没有内置的LinkList。。。所以本章我们来自己动手设计一个链表,这也是LeetCode的707题,需要实现下列方法:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
示例:
MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1,2); //链表变为1-> 2-> 3
linkedList.get(1); //返回2
linkedList.deleteAtIndex(1); //现在链表是1-> 3
linkedList.get(1); //返回3
思考
要满足这些方法,实例函数中需要提供些什么?
因为我们get, addAtIndex, deleteAtIndex函数要判断索引是否有效,我们如果没有链表长度,每次都要循环一遍算长度,十分消耗时间,所以我们定义一个链表的长度this.size。
因为我们要实现addAtHead函数,在头部增加节点,所以我们定义一个头部指针this.head。
因为我们要实现addAtTail函数,在尾部增加节点,如果每次都用指针遍历指向尾部,也是浪费时间的,所以我们增加一个this.tail指针,指向链表尾部。
代码实现
var MyLinkedList = function () {
this.size = 0;
this.head = null;
this.tail = null;
};
/**
* @param {number} index
* @return {number}
*/
MyLinkedList.prototype.get = function (index) {
if (index >= this.size) return -1;
let ans = this.head;
while (index) {
ans = ans.next;
index--;
}
return ans.val;
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtHead = function (val) {
const node = new ListNode(val, this.head);
if (!this.size) {
this.tail = node;
}
this.head = node;
this.size += 1;
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtTail = function (val) {
const node = new ListNode(val, null);
if (!this.size) {
this.head = node;
this.tail = node;
} else {
this.tail.next = node;
this.tail = this.tail.next;
}
this.size += 1;
};
/**
* @param {number} index
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtIndex = function (index, val) {
if (index > this.size) {
return
} else if (index === this.size) {
this.addAtTail(val)
} else if (index <= 0) {
this.addAtHead(val)
} else {
index -= 1;
let current = this.head;
let idx = 0;
while (idx < index) {
current = current.next;
idx++;
}
const next = current.next;
current.next = new ListNode(val, next);
this.size += 1;
}
};
/**
* @param {number} index
* @return {void}
*/
MyLinkedList.prototype.deleteAtIndex = function (index) {
if (index >= this.size) return;
if (index === 0) {
this.head = this.head.next;
this.size -= 1;
return;
}
let current = this.head;
while (index > 1) {
current = current.next;
index--;
}
if (current.next === this.tail) {
this.tail = current;
}
current.next = current.next.next;
this.size -= 1;
};
function ListNode(val, next) {
this.val = val;
this.next = next || null;
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* var obj = new MyLinkedList()
* var param_1 = obj.get(index)
* obj.addAtHead(val)
* obj.addAtTail(val)
* obj.addAtIndex(index,val)
* obj.deleteAtIndex(index)
*/
哨兵节点和双链表
上面的代码有哪些容易出错或优化的点??
- addAtHead、addAtTail需要对长度为空进行判断,初始化头尾节点
- deleteAtIndex中
if (current.next === this.tail) { this.tail = current; }这段代码,需要对尾节点进行移动 - 查询节点时,若需要操作的节点在尾部,我们也需要从头开始遍历链表
那么如何解决呢??
- 哨兵节点,模拟一个虚拟的伪头和伪尾节点,这样头尾的操作时间复杂度都是O(1)
- 双链表,判断index是靠近头部还是尾部节点,操作插入,删除,查询的时间复杂度是O(min(index, size-index))
var MyLinkedList = function () {
this.size = 0;
const dump_head = new ListNode(-1);
const dump_tail = new ListNode(-1);
dump_head.next = dump_tail;
dump_tail.prev = dump_head;
this.head = dump_head
this.tail = dump_tail;
};
/**
* @param {number} index
* @return {number}
*/
MyLinkedList.prototype.get = function (index) {
if (index >= this.size) {
return -1;
}
if ((index + 1) * 2 > this.size) {
// 靠近尾部,从尾部查找
let idx = this.size - index;
let current = this.tail;
while (idx) {
current = current.prev;
idx--;
}
return current.val;
} else {
let idx = index + 1;
let current = this.head;
while (idx) {
current = current.next;
idx--;
}
return current.val;
}
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtHead = function (val) {
const next = this.head.next;
const node = new ListNode(val, next, this.head);
next.prev = node;
this.head.next = node;
this.size++;
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtTail = function (val) {
const prev = this.tail.prev;
const node = new ListNode(val, this.tail, prev);
this.tail.prev = node;
prev.next = node;
this.size++;
};
/**
* @param {number} index
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtIndex = function (index, val) {
if (index > this.size) {
return
} else if (index === this.size) {
this.addAtTail(val)
} else if (index <= 0) {
this.addAtHead(val)
} else {
let idx = 0, current = null;
if ((index + 1) * 2 > this.size) {
idx = this.size - index;
current = this.tail;
while (idx) {
current = current.prev;
idx--;
}
} else {
idx = index + 1;
current = this.head;
while (idx) {
current = current.next;
idx--;
}
}
const prev = current.prev;
const node = new ListNode(val, current, prev);
prev.next = node;
current.prev = node;
this.size++;
}
};
/**
* @param {number} index
* @return {void}
*/
MyLinkedList.prototype.deleteAtIndex = function (index) {
if (index >= 0 && index < this.size) {
let idx = 0, current = null;
if ((index + 1) * 2 > this.size) {
idx = this.size - index;
current = this.tail;
while (idx) {
current = current.prev;
idx--;
}
} else {
idx = index + 1;
current = this.head;
while (idx) {
current = current.next;
idx--;
}
}
const prev = current.prev;
const next = current.next;
prev.next = next;
next.prev = prev;
this.size--;
}
};
function ListNode(val, next, prev) {
this.val = val || 0;
this.next = next || null;
this.prev = prev || null;
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* var obj = new MyLinkedList()
* var param_1 = obj.get(index)
* obj.addAtHead(val)
* obj.addAtTail(val)
* obj.addAtIndex(index,val)
* obj.deleteAtIndex(index)
*/
本文的代码已收录Github,仓库中包括之前的文章链接和代码收录,后续代码也会陆续更新,欢迎大家不吝赐教。