链表
1.环形链表
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果pos
是 -1,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true
。 否则,返回 false
。
思路:
哈希表:
利用键值对记得每个元素有无记录如果有的话就返回
false
快慢指针:
快、慢指针,从头节点出发
慢指针每次走一步,快指针每次走两步,不断比较它们指向的节点的值
如果节点值相同,说明有环。如果不同,继续循环。
类似 “追及问题”
两个人在环形跑道上赛跑,同一个起点出发,一个跑得快一个跑得慢,在某一时刻,跑得快的必定会追上跑得慢的,只要是跑道是环形的,不是环形就肯定追不上。
- 哈希表
var hasCycle = (head) => {
let map = new Map();
while (head) {
if (map.has(head)) return true;
map.set(head, true); // 存的是节点的地址引用,而不是节点值
head = head.next;
}
return false;
};
- 快慢指针
var hasCycle = (head) => {
let fast = head;
let slow = head;
while (fast) {
if (fast.next == null) return false;
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
2.合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
思路:
递归:
我们直接将以上递归过程建模,同时需要考虑边界情况。
如果
l1
或者l2
一开始就是空链表 ,那么没有任何操作需要合并,所以我们只需要返回非空链表。否则,我们要判断l1
和l2
哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点。如果两个链表有一个为空,递归结束。迭代:
通过设置一个哑结点
首先,我们设定一个哨兵节点
prehead
,这可以在最后让我们比较容易地返回合并后的链表。我们维护一个prev
指针,我们需要做的是调整它的 next 指针。然后,我们重复以下过程,直到l1
或者l2
指向了 null :如果l1
当前节点的值小于等于l2
,我们就把l1
当前的节点接在prev
节点的后面同时将l1
指针往后移一位。否则,我们对l2
做同样的操作。不管我们将哪一个元素接在了后面,我们都需要把prev
向后移一位。在循环终止的时候,
l1
和l2
至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表即可。
- 递归
var mergeTwoLists = function(l1, l2) {
if (l1 === null) {
return l2;
} else if (l2 === null) {
return l1;
} else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
};
- 迭代
var mergeTwoLists = function(l1, l2) {
const prehead = new ListNode(-1);
let prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev.next = l1 === null ? l2 : l1;
return prehead.next;
};
3.移除链表元素
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
思路:因为不知道头结点是否会和val值相等,所以要设置一个哑结点,迭代删除
var removeElements = function(head, val) {
const dummyHead = new ListNode(0);
dummyHead.next = head;
let temp = dummyHead;
while (temp.next !== null) {
if (temp.next.val == val) {
temp.next = temp.next.next;
} else {
temp = temp.next;
}
}
return dummyHead.next;
};
4.反转链表
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
思路:
在遍历链表时,将当前节点的next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。
var reverseList = function(head) {
let prev = null;
let curr = head;
while (curr) {
const next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
};
5.删除排序链表中的重复元素
存在一个按升序排列的链表,给你这个链表的头节点 head
,请你删除所有重复的元素,使每个元素 只出现一次 。
返回同样按升序排列的结果链表。
思路:
var deleteDuplicates = function(head) {
if (!head) {
return head;
}
let cur = head;
while (cur.next) {
if (cur.val === cur.next.val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return head;
};