前言
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
不难发现,链表也是用于存储一系列数据的,这一点与数组十分相似,他们之间有什么区别?
在存储方式上: 数组存储地址为连续的地址,而链表内存分布是不均匀的
链表是通过指针域的指针链接在内存中各个节点。
所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
如图所示:
除此之位,需要了解链表的定义,js代码如下:
class ListNode{
consturctor(val,next){
this.val = val
this.next = next
}
}
203.移除链表元素
题目链接:203. 移除链表元素 - 力扣(Leetcode)
遇到问题
由于之前有学过链表,所以基本的链表遍历还是能做,但是和数组一样没有注意细节: 当头元素是需要被删除的元素时的区分与判断
题解
根据上述问题,可以得出两种解答方法
1. 头部区分法(在原有的链表上更改)
var removeElements = function(head, val){
while(head != null && head.val == val){
head = head.next
}
let cur = head
while(cur != null && cur.next != null){
if(cur.next.val == val){
cur.next = cur.next.next
}else{
cur = cur.next
}
}
return head
}
时间复杂度:O(n)
空间复杂度:O(1)
这里大体上的链表循环思路并不是很难,只要一个一个去遍历即可,但是要特别注意:
- Q: 结束链表的条件是什么?
A: 在删除头部节点的时候,首先需要保证head.val == val
,但是head
一定存在嘛?因此必须判断当前链表是存在内容的,即head != null
,不然会报错null 不存在 next 属性
; 同样在遍历后续节点的时候,必须保证当前指针不为空,即cur != null
,其次因为需要建立到下下个节点,因此需要保证cur.next != null
- 在删除后续节点的时候,应该注意需提前判断一位,即应该判断的是
cur.next
,这样才调整所指节点;因此,在调整完指针后,不应该给当前节点指向下一位,因为需要对新指向的下一位节点进行判断
2. 新增虚拟头部链接
因为需要对头部节点进行额外的判断,如果没有头部节点就不需要,因此我们可以创建一个人新的节点成为头部节点
var removeElements = function(head, val) {
let newNode = new ListNode(0,head)
let cur = newNode
while(cur != null && cur.next != null ){
if(cur.next.val == val){
cur.next = cur.next.next
}else{
cur = cur.next
}
}
return newNode.next
}
时间复杂度:O(n)
空间复杂度:O(1)
遍历思想与头部区分法一致,但是需要注意到的是返回节点是newNode.next
3.递归法
var removeElements = function(head, val) {
if(head == null){
return head
}
head.next = removeElements(head.next,val)
return head.val == val ? head.next : head
}
时间复杂度:O(n)
空间复杂度:O(1)
707.设计链表
题目链接: 707. 设计链表 - 力扣(Leetcode)
遇到问题
注意题解的要求理解(暂时自己的测试用例未完全过完,等周末再来巩固)
题解
class LinkNode {
constructor(val, next) {
this.val = val;
this.next = next;
}
}
/**
* Initialize your data structure here.
* 单链表 储存头尾节点 和 节点数量
*/
var MyLinkedList = function() {
this._size = 0;
this._tail = null;
this._head = null;
};
/**
* Get the value of the index-th node in the linked list. If the index is invalid, return -1.
* @param {number} index
* @return {number}
*/
MyLinkedList.prototype.getNode = function(index) {
if(index < 0 || index >= this._size) return null;
// 创建虚拟头节点
let cur = new LinkNode(0, this._head);
// 0 -> head
while(index-- >= 0) {
cur = cur.next;
}
return cur;
};
MyLinkedList.prototype.get = function(index) {
if(index < 0 || index >= this._size) return -1;
// 获取当前节点
return this.getNode(index).val;
};
/**
* Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtHead = function(val) {
const node = new LinkNode(val, this._head);
this._head = node;
this._size++;
if(!this._tail) {
this._tail = node;
}
};
/**
* Append a node of value val to the last element of the linked list.
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtTail = function(val) {
const node = new LinkNode(val, null);
this._size++;
if(this._tail) {
this._tail.next = node;
this._tail = node;
return;
}
this._tail = node;
this._head = node;
};
/**
* Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted.
* @param {number} index
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtIndex = function(index, val) {
if(index > this._size) return;
if(index <= 0) {
this.addAtHead(val);
return;
}
if(index === this._size) {
this.addAtTail(val);
return;
}
// 获取目标节点的上一个的节点
const node = this.getNode(index - 1);
node.next = new LinkNode(val, node.next);
this._size++;
};
/**
* Delete the index-th node in the linked list, if the index is valid.
* @param {number} index
* @return {void}
*/
MyLinkedList.prototype.deleteAtIndex = function(index) {
if(index < 0 || index >= this._size) return;
if(index === 0) {
this._head = this._head.next;
// 如果删除的这个节点同时是尾节点,要处理尾节点
if(index === this._size - 1){
this._tail = this._head
}
this._size--;
return;
}
// 获取目标节点的上一个的节点
const node = this.getNode(index - 1);
node.next = node.next.next;
// 处理尾节点
if(index === this._size - 1) {
this._tail = node;
}
this._size--;
};
// MyLinkedList.prototype.out = function() {
// let cur = this._head;
// const res = [];
// while(cur) {
// res.push(cur.val);
// cur = cur.next;
// }
// };
/**
* 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)
*/
206.反转链表
题目链接 :206. 反转链表 - 力扣(Leetcode)
遇到问题
第二次刷这个题目了,能很快 反映出迭代的方法,但是忽略了递归法
题解
1.迭代法
var reverseList = function(head) {
let pre = null, cur = head
while(cur){
const next = cur.next
cur.next = pre
pre = cur
cur = next
}
return pre
}
时间复杂度:O(n)
空间复杂度:O(1)
2.递归法
var reverseList = function(head) {
if (head == null || head.next == null) {
return head;
}
let ans = reverseList(head.next)
head.next.next = head
head.next = null
return ans
}
时间复杂度:O(n)
空间复杂度:O(n)