js实现对链表相关算法
为什么选择js
因为js是函数式编程,实现起来更加方便.而且现在调试工具也很方便.只有有浏览器就可以.
js有很多优秀的特性.尤其是对数组的操作.还有正则表达式的支持等等.
写一般的算法是够了…最主要是方便!
背景
在极客时间上买了一个算法的课程.打算好好恶补一下算法!
在LeetCode上,有关于链表的题目,下面是按难度进行的排序.
// 定义一个 ListNode
function ListNode(val) {
this.val = val;
this.next = null;
}
反转链表
- 以head为轴,依次遍历每一个节点.
- 注意设置2个节点分别记录前后两个
// 反转链表
let reverseList = function(head) {
let pre = null;
let post = null;
while (head !== null) {
post = head.next;
head.next = pre;
pre = head;
head = post;
}
return pre;
};
判断一个链表是否有环
思路1 使用set
- 在set集合中插入数据,如果有重复的就说明有环
// 判断是否有环,有返回入口节点,无返回null
let detectCycle = function(head) {
let set = new Set();
while (head != null) {
if (set.has(head)) {
return head;
} else {
set.add(head)
head = head.next;
}
}
return null;
};
思路2 快慢指针
- 快指针去追慢指针,如果能追上说明有环,追不上说明没有环
- 可以类比跑步,如果是100百米的直线,快的人不能与慢的人相遇,但是要是5千米长跑,就会遇到.
那为什么要 快指针是慢指针速度的两倍,而不是其它更高的倍数呢?
- 首先,一个指针指向另一个是离散的点,而非连续的点
- 要想让它们尽快相遇,那么就要让它们的速度很相近
- 当然,也有存在的例外,可能存在比两倍更先遇到的.
- 可以看一下下面的表,表格中的数据表示在一个以
环大小为4的环
起点的距离- 但是环的大小无法预先知道.所以2倍是比较稳妥的.
速度为1 | 速度为2 | 速度为3 | 速度为4 |
---|---|---|---|
1 | 2 | 3 | 0 |
2 | 4 | 2(相遇) | 0 |
3 | 2 | 0 | |
0 | 0(相遇) | (相遇) |
// 判断是否有环,有返回入口节点,无返回null
var hasCycle = function(head) {
// 如果就1个节点,且next为空
if (head.next === null) {
return false;
}
let fast = head.next.next;
let slow = head.next;
while (fast !== null) {
if (fast === slow) {
return true;
}
slow = slow.next;
if (fast.next === null) {
return false;
}
fast = fast.next.next;
}
return false;
};
环形节点入环口
- 环形节点入环口,用set的话,只要遇到重复的返回就是
- 如果使用快慢指针的话,很麻烦,需要遍历两次
- 先遍历得到相遇第一次的位置
- 然后在遍历一次得到入环口
// 判断是否有环,有返回入口节点,无返回null
var detectCycle = function(head) {
var set = new Set();
while (head != null) {
if (set.has(head)) {
return head;
} else {
set.add(head)
head = head.next;
}
}
return null;
};
我想了很久如何使用快慢指针来求得入环口
然后,思考了一些问题
假设 入环前的距离为 M
和 环的大小 K
通过思考可以得到
第一次相遇时走的次数为
M + K-(M%K)
其实这是一个简单的追逐问题,只不过用代码实现有些麻烦
- 先不去考虑环之外的事情,只考虑在环中,两个人追逐
- 两个是单方向跑的.所以当快的那个在环中的时候比慢的那个前面一点,那么快的要追很久才能与慢的相遇
△x = △v * t
联想到,时针与分针每天相遇多少次,可以使用同样的方法计算; 22次
再回到上面的公式
K - (M%K)
其实就是为了求出,当慢指针到进入环的时候,快指针在环中的位置- 又因为两者之间的速度差为1 ,所以两个指针在环中走的距离就是
K - (M%K)
- 再加上M,是慢指针在直线走的距离
以K为一组,反转一个链表
- js 尽量使用let
- 代码略复杂,具体看注释
- 通过将要翻转的链表进行分组,左边分过的,中间要分的,右边未分的
- 其中要注意反转第一组时,要用一个对象记录结果的第一个node
- 最后一组要判断是否满足k个,要够k个再反转
- 反转时要主要保留前后两个节点,记得将要反转的节点组前后先都为空
// 反转链表
let reverseList = function(headTemp) {
let head = headTemp;
let pre = null;
let post = null;
while (head !== null) {
post = head.next;
head.next = pre;
pre = head;
head = post;
}
return pre;
};
let getEndNode = function(head, k) {
for (let i = 0; i < k - 1; i++) {
if (head == null) {
return false;
}
// 这里的head,可能返回null,当i=k-1,但是head.next == null
head = head.next;
}
return head;
}
// K 个元素为一组交换
// 需要3个节点记录,并且以head为轴,进行处理
let reverseKGroup = function(head, k) {
let first = null;
let pre = null;
while (head != null) {
let start = head;
let end = getEndNode(head, k);
// end 为false 或则 null,说明剩下的不足k
if (end === false || end === null) {
return first;
} else {
head = end.next;
}
end.next = null;
reverseList(start)
start.next = head;
// 第一次反转时,pre是null
if (pre != null) {
pre.next = end;
} else {
// 取得第一组的反转后的第一个
first = end;
}
// pre指向start,作为下一次分组的开始
pre = start;
}
return first;
}