链表
单链表:
定义链表节点:
class LinkNode {
constructor(val, next) {
this.val = val;
this.next = next;
}
}
定义链表:
var MyLinkedList = function() {
this._size = 0;
this._head = null;
};
双向链表:
203.移除链表元素
第一想法
单向链表无法通过下标找到元素,需要从头节点开始一个一个依次访问
如果当前节点的data == val
则上一个节点的next 指向 当前节点的next
思路
这里实际上需要考虑两种情况,第一种是删除的节点是头节点,第二种是删除的节点非头节点
可以单独分开来考虑,也可以使用一个虚拟头节点,这样就可以保证所有会被删除的节点都不是头节点
最后返回的时候去掉虚拟头节点就行
这里用第二种方法比较方便,JS代码如下:
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var removeElements = function(head, val) {
let cur = new ListNode(0,head);
let ret = cur;
while(ret.next){
if(ret.next.val == val){
ret.next = ret.next.next;
continue;
}
ret = ret.next;
}
return cur.next;
};
这里要注意continue,即如果该节点需要删除,则删除该节点后需要跳出循环,不能执行ret = ret.next这一步
这道题给的输入输出很适合用第二种方法
总结
这一题一开始有点不理解,算法和输入输出的关系,也就是对于JS中的链表有点没搞清楚
然后发现JS中,链表是用对象实现的,即链表的一般形式:
const list = {
head: {
value: 6
next: {
value: 10
next: {
value: 12
next: {
value: 3
next: null
}
}
}
}
}
};
707.设计链表
第一想法
JS实现链表不是很熟,第一想法还是懵逼
先看了一下题解
这一题写了整整4个小时,好难
卡哥的答案的JS部分看的好难受,最后干脆自己写了
思路
一开始自己直接上手写,写了写去全是错误,有太多的边界情况需要考虑,写起来越写越乱
看了卡哥的视频后发现,使用虚拟头结点可以使问题变得简单一些
这样就不需要把对头节点的操作单独考虑,也不需要单独考虑index = 0或者链表为空这些特殊情况
-
初始化链表和节点
var MyLinkedList = function() { this._size = 0; this._head = null; }; function ListNode(val, next) { this.val = val; this.next = next; };
-
获取index对应元素的值
MyLinkedList.prototype.get = function(index) { // 注意index的有效范围 if(index < 0 || index >= this._size){ return -1; } // 这里建立一个虚拟头结点 let dummyhead = new ListNode(0,this._head); let cur = dummyhead.next; while(index-- > 0){ cur = cur.next; } return cur.val; }; -
头部插入节点
这里必须注意插入节点的顺序
应该建立新节点和后一个节点的连接,再建立新节点和前一个节点的连接
MyLinkedList.prototype.addAtHead = function(val) { const node = new ListNode(val, this._head); this._head = node; this._size++; };
-
尾部插入节点
当前的指针必须指向链表中最后一个元素
创建虚拟头指针进行移动,移动到链表的最后一个元素上
MyLinkedList.prototype.addAtTail = function(val) { let cur = new ListNode(0, this._head); const node = new ListNode(val,null) this._size++; if(this._head){ while(cur.next !== null){ cur = cur.next; } // 注意这里,如果cur.next 是null,那么无法赋值 cur.next = node; return; } this._head = node; };这里有一个大坑,也是让我反复写不出来的原因
如果此时链表是空的,cur.next 就是 null
在JS语法中,null是一个独立的类型,这里给它赋值一个对象会出问题
所以必须把链表是空的这种情况单独拿出来分析
-
第n个节点前插入节点
将指针移动到第n个节点的上一个节点
注意边界上的考虑,一般考虑边界时可以举一个极端的例子
这里的边界情况,实际上如果index = 链表长度时,并不需要单独考虑
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; } let cur = new ListNode(0, this._head); const node = new ListNode(val); while(index-- > 0){ cur = cur.next; } node.next = cur.next; cur.next = node; this._size++; };这里和上面同理
对于cur.next = null 的情况,都单独写出来讨论
-
删除节点
这里第n个节点必须是cur.next,即此时指针指向的应该是待删除节点的前一个节点
MyLinkedList.prototype.deleteAtIndex = function(index) { if(index < 0 || index >= this._size){ return; } let cur = new ListNode(0, this._head); if(index == 0){ this._head = this._head.next; this._size--; return; } while(index-- > 0){ cur = cur.next; } cur.next = cur.next.next; this._size--; };这里如果index = 0,则cur的改变对原链表并没有影响
可是如果index 不为0,则cur发生移动,改变cur的指向对原数组又有影响了
因此需要把index为0的情况单独讨论
总结
哪怕对着卡哥的视频一步步写,还是执行出问题,写了整整两天
自己又不会用vscode调试,又卡住了
这题卡的实在是太难受了,又不会debug,代码看着完全没有问题,就是跑不成功
最后发现问题,貌似是JS中无法把一个对象赋值给null,导致之前的分类完全错误,还是必须去分情况考虑
这道题需要好好研究研究
206.反转链表
第一想法
定义一个新链表,新链表的next = 旧链表的prev(用双向链表了)
思路
1.双指针解法
这里使用两个指针,cur指向头节点,pre指向其上一个节点
令cur.next = pre即可
为了使cur和pre可以方便移动,这里引入一个第三方变量temp
代码如下:
var reverseList = function(head) {
let cur = head;
let pre = null;
let temp = null;
while(cur != null){
// 注意这里的相互赋值
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
};
循环的结束条件是cur指向null,即此时pre是头结点
将pre返回即可
2.递归解法
递归写法是基于双指针写法的原理的
代码如下:
var reverseList = function(head) {
return reverse(head,null);
};
var reverse = function(cur,pre){
if(cur == null){
return pre;
}
let temp = cur.next;
cur.next = pre;
return reverse(temp,cur);
}
总结
这部分不难,主要是这个双指针的思想需要记住
还有就是这个赋值的操作,搞清楚赋值的先后顺序,这个比较容易搞混
递归感觉自己还不是特别熟,需要再多练练