js实现对链表相关算法

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为一组,反转一个链表

以K为一组,反转一个链表Leetcode链接

  • 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;
	}
发布了92 篇原创文章 · 获赞 18 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_34120430/article/details/97500257