203移除链表元素
方法一:递归
var removeElements = function(head, val) {
if (head === null) {
return head;
}
head.next = removeElements(head.next, val);
return head.val === val ? head.next : head;
};
var removeElements = function (head, val) {
if (head == null) {
return head;
}
head.next = removeElements(head.next, val);
// 只需要考虑当前节点是否等于val值,如果当前节点等于val值,则返回当前节点的下一个节点,否则返回当前节点
return head.val == val ? head.next : head;
};
方法二:迭代
链表常规解题法,创建dummy指针,通过判断当前指针的下一节点的值是否为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;
};
var removeElements = function (head, val) {
let dummyNode = new ListNode(-1);
dummyNode.next = head;
let p = dummyNode;
while (p.next != null) {
if (p.next.val == val) {
// 如果遍历到的节点的值等于val值,则将当前节点的next指针指向当前指向的节点的下一个节点
p.next = p.next.next;
} else {
p = p.next;
}
}
return dummyNode.next;
};
var removeElements = function(head, val) {
let newList = new ListNode(0,head)
let cur = newList
while(cur.next){
if(cur.next.val == val){
cur.next = cur.next.next
}else{
cur = cur.next
}
}
return newList.next
};
var removeElements = function(head, val) {
const ret = new ListNode(0, head);
let cur = ret;
while(cur.next) {
if(cur.next.val === val) {
cur.next = cur.next.next;
continue;
}
cur = cur.next;
}
return ret.next;
};
如果不用虚拟头结点:
var removeElements = function(head, val) {
// 先跳过所有头结点
while(head !== null && head.val === val) {
head = head.next
}
if(head === null) {
return head
}
pre = head
// 之后正常遍历即可~
while(pre !== null && pre.next !== null) {
if(pre.next.val === val) {
pre.next = pre.next.next
}
else {
pre = pre.next
}
}
return head
};
83删除排序链表中的重复元素
不需要使用虚拟头结点
var deleteDuplicates = function(head) {
let cur = head
while(cur !== null && cur.next !== null) {
if(cur.val === cur.next.val) {
// 当前值与下一个值相同则 通过修改链表结点指向跳过这个值
cur.next = cur.next.next
}
else {
// 当前值与下一个值不相同则 结点继续移动 检查接下来的结点
cur = cur.next
}
}
return head
};
206&剑指offer24反转链表
方法一:迭代
时间复杂度:O(n),其中n是链表的长度,需要遍历链表一次
空间复杂度:O(1)
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;
};
加一个为空的特殊情况,执行时间会少很多。
var reverseList = function(head) {
if (head === null || head.next === null) return head;
let prev = null; // 定义一个前驱节点
let cur = head; // 定义一个当前节点
while (cur) {
let next = cur.next; // 定义一个后继节点
// 执行交换操作
cur.next = prev;
prev = cur;
cur = next;
}
return prev;
};
方法二:递归
时间复杂度:O(n),其中n是链表的长度,需要遍历链表一次
空间复杂度:O(n)
var reverseList = function(head) {
if (head == null || head.next == null) {
return head;
}
const newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
};
var reverseList = function(head) {
if (head == null || head.next == null) return head
const p = reverseList(head.next)
head.next.next = head
head.next = null
return p
};
方法三:ES6语法
var reverseList = function(head) {
/*
类比思考 反转数组思路 双端指针实现
while (l < r) swap(a, l++, r--)
数组位置的交换是这样 [a[i], a[j]] = [a[j], a[i]]
但是 单链表只能依次通过 next 访问 不能通过索引访问
链表的交换需要扩展一个指针 即next
cur 当前项
prev 上一项
cur.next 当前指针指向
[ cur.next, prev, cur ] = [prev, cur, cur.next]
上面这段ES6语法表示
当前cur 的指针next 指向prev上一项 并且 交换迭代prev 和 next
*/
let [p, c] = [null, head]
while (c) [c.next, p, c] = [p, c, c.next]
return p
};
141环形链表
方法一:快慢指针
「Floyd 判圈算法」(又称龟兔赛跑算法):我们可以根据上述思路来解决本题。具体地,我们定义两个指针,一快一满。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置head,而快指针在位置head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。
观察下面的代码,我们使用的是 while 循环,循环条件先于循环体。由于循环条件一定是判断快慢指针是否重合,如果我们将两个指针初始都置于 head,那么 while 循环就不会执行。因此,我们可以假想一个在 head 之前的虚拟节点,慢指针从虚拟节点移动一步到达 head,快指针从虚拟节点移动两步到达 head.next,这样我们就可以使用 while 循环了。
时间复杂度:O(n)
空间复杂度:O(1)
var hasCycle = function(head) {
if(head==null||head.next==null){return false;}
let fast=head.next;
let slow=head;
while(fast!=slow){
if(fast==null||fast.next==null){return false;}
slow=slow.next;
fast=fast.next.next;
}
return true;
};
var hasCycle = function(head) {
// 快慢指针初始化指向 head
let slow = head;
let fast = head;
// 快指针走到末尾时停止
while (fast && fast.next) {
// 慢指针走一步,快指针走两步
slow = slow.next;
fast = fast.next.next;
// 快慢指针相遇,说明含有环
if (slow == fast) {
return true;
}
}
// 不包含环
return false;
};
方法二:哈希表
时间复杂度:O(n)
空间复杂度:O(n)
var hasCycle = (head) => {
let map = new Map();
while (head) {
if (map.has(head)) return true;//如果当前节点在map中存在就说明有环
map.set(head, true);//否则就加入map
head = head.next;//迭代节点
}
return false;//循环完成发现没有重复节点,说明没环
};
方法三:巧用JS
var hasCycle = function(head) {
try {
JSON.stringify(head)
return false
} catch {
return true
}
};
142环形链表II
方法一:快慢指针
时间复杂度:O(n)
空间复杂度:O(1)
var detectCycle = function(head) {
if (head === null) {
return null;
}
let slow = head, fast = head;
while (fast !== null) {
slow = slow.next;
if (fast.next !== null) {
fast = fast.next.next;
} else {
return null;
}
if (fast === slow) {
let ptr = head;
while (ptr !== slow) {
ptr = ptr.next;
slow = slow.next;
}
return ptr;
}
}
return null;
};
var detectCycle = function(head) {
if(head==null||head.next==null){return null;}
let fast=head;
let slow=head;
while(fast&&fast.next){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
let ptr=head;
while(ptr!=slow){
ptr=ptr.next;
slow=slow.next;
}
return ptr;
}
}
return null;
};
var detectCycle = function(head) {
// 快慢指针初始化指向 head
let slow = head;
let fast = head;
// 快指针走到末尾时停止
while (fast && fast.next) {
// 慢指针走一步,快指针走两步
slow = slow.next;
fast = fast.next.next;
// 快慢指针相遇,说明含有环
if (slow == fast) {
// 任一一节点指向头节点
fast = head;
// 同步向前进
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
// 返回入口节点
return fast;
}
}
// 不包含环
return null;
};
方法二:哈希表
时间复杂度:O(n)
空间复杂度:O(n)
var detectCycle = function(head) {
const visited = new Set();
while (head !== null) {
if (visited.has(head)) {
return head;
}
visited.add(head);
head = head.next;
}
return null;
};
剑指offer22链表中倒数第k个节点
方法一:顺序查找
时间复杂度:O(n)
空间复杂度:O(1)
var getKthFromEnd = function(head, k) {
let node = head, n = 0;
while (node) {
node = node.next;
n++;
}
node = head;
for (let i = 0; i < n - k; i++) {
node = node.next;
}
return node;
};
方法二:快慢指针
时间复杂度:O(n)
空间复杂度:O(1)
应该考虑的特殊情况有:
head为空指针;
k大于链表的长度;
输入的参数k为0;
var getKthFromEnd = function(head, k) {
let fast=head;
let slow=head;
for(let i=0; i<k; i++){
fast=fast.next;
}
while(fast){
slow=slow.next;
fast=fast.next;
}
return slow;
};
var getKthFromEnd = function(head, k) {
let fast = head, slow = head;
while (fast && k > 0) {
[fast, k] = [fast.next, k - 1];
}
while (fast) {
[fast, slow] = [fast.next, slow.next];
}
return slow;
};