本文总结leetcode上关于链表的一些高频题,方便以后深入理解学习。
概念
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
题目
难度:简单
代码展示:
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
let p1 = head;
let p2 = null;
while (p1) {
const next = p1.next;
p1.next = p2;
p2 = p1;
p1 = next;
}
return p2;
}
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(1)
代码展示:
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var mergeTwoLists = function(l1, l2) {
const l3 = new ListNode(0);
let p1 = l1;
let p2 = l2;
let p3 = l3;
while (p1 || p2) {
if (!p1) { p3.next = p2; break; }
if (!p2) { p3.next = p1; break; }
const r = p1.val > p2.val;
if (r) {
p3.next = new ListNode(p2.val);
p2 = p2.next;
} else {
p3.next = new ListNode(p1.val);
p1 = p1.next;
}
}
p3 = p3.next;
return l3.next;
}
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(1)
代码展示:
/**
* @param {ListNode} head
* @param {number} k
* @return {ListNode}
*/
var getKthFromEnd = function(head, k) {
let p1 = head;
let p2 = head;
while (k > 0) {
p2 = p2.next;
k--;
}
while (p1 && p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(1)
代码展示:
var hasCycle = function(head) {
let p1 = head;
let p2 = head;
while (p1 && p2 && p2.next) {
p1 = p1.next;
p2 = p2.next.next;
if (p1 === p2) return true;
}
return false;
}
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(1)
代码展示:
/**
* @param {ListNode} head
* @return {ListNode}
*/
var deleteDuplicates = function(head) {
let p = head;
while (p && p.next) {
if (p.val === p.next.val) {
p.next = p.next.next;
} else {
p = p.next;
}
}
return head;
};
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(1)
代码展示:
/**
* @param {ListNode} head
* @return {boolean}
*/
var isPalindrome = function(head) {
const vals = [];
let p1 = head;
while (p1) {
vals.push(p1.val);
p1 = p1.next;
}
console.log(vals);
for (let i = 0, j = vals.length - 1; i < vals.length, j >= 0; i++, j--) {
if (vals[i] !== vals[j]) {
return false;
}
}
return true;
}
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(n)
代码展示:
/**
* @param {ListNode} head
* @return {number[]}
*/
var reversePrint = function(head) {
let p1 = head;
const res = [];
while (p1) {
res.push(p1.val);
p1 = p1.next;
}
return res.reverse();
}
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(n)
代码展示:
/**
* @param {ListNode} headA
* @param {ListNode} headB
* @return {ListNode}
*/
var getIntersectionNode = function(headA, headB) {
if (!headA || !headB) return false;
let p1 = headA;
let p2 = headB;
while (p1 !== p2) {
p1 = p1 ? p1.next : headB;
p2 = p2 ? p2.next : headA;
}
return p1;
};
复杂度分析:
- 时间复杂度:O(m+n):m为A链表节点数,n为B链表节点数。
- 空间复杂度:O(1)
代码展示:
/**
* @param {ListNode} headA
* @param {ListNode} headB
* @return {ListNode}
*/
var getIntersectionNode = function(headA, headB) {
let p1 = headA;
let p2 = headB;
while (p1 != p2) {
p1 = p1 ? p1.next : headB;
p2 = p2 ? p2.next : headA;
}
return p1;
}
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(n)
代码展示:
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var removeElements = function(head, val) {
if (!head) return null;
while (head.val === val) {
head = head.next;
if (!head) return head;
}
let p1 = head;
let p2 = p1.next;
while (p2) {
if (p2.val === val) {
p1.next = p2.next;
p2 = p1.next;
} else {
p1 = p1.next;
p2 = p2.next;
}
}
return head;
};
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(1)
代码展示:
/**
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var deleteNode = function(head, val) {
if (head.val === val) return head.next;
let p1 = head;
let p2 = p1.next;
while (p2) {
if (p2.val === val) {
p1.next = p2.next;
}
p1 = p1.next;
p2 = p2.next;
}
return head;
}
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(1)
代码展示:
/**
* @param {ListNode} node
* @return {void} Do not return anything, modify node in-place instead.
*/
var deleteNode = function(node) {
node.val = node.next.val;
node.next = node.next.next;
};
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(1)
难度:中等
代码展示:
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function(l1, l2) {
const l3 = new ListNode(0);
let p1 = l1;
let p2 = l2;
let p3 = l3;
let addOne = 0;
while(p1 || p2) {
let v1 = p1 ? p1.val : 0;
let v2 = p2 ? p2.val : 0;
const sum = v1 + v2 + addOne;
addOne = ~~(sum / 10);
p3.next = new ListNode(sum % 10);
if (p1) p1 = p1.next;
if (p2) p2 = p2.next;
p3 = p3.next;
}
if (addOne) p3.next = new ListNode(addOne);
return l3.next;
}
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(1)
代码展示:
/**
* @param {ListNode} head
* @param {number} left
* @param {number} right
* @return {ListNode}
*/
var reverseBetween = function(head, left, right) {
let p1 = new ListNode(-1);
p1.next = head;
let p2 = p1;
for (let i = 0; i < left - 1; i++) {
p2 = p2.next;
}
let cur = p2.next;
for (let i = 0; i < right - left; i++) {
const next = cur.next;
cur.next = next.next;
next.next = p2.next;
p2.next = next;
}
return p1.next;
}
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(1)
代码展示:
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {void} Do not return anything, modify head in-place instead.
*/
var reorderList = function(head) {
if (!head) return head;
const temp = [];
while (head) {
temp.push(head);
head = head.next;
}
let i = 0; j = temp.length - 1;
while (i < j) {
temp[i].next = temp[j];
i++;
if (i === j) break;
temp[j].next = temp[i];
j--;
}
temp[i].next = null;
return temp[0];
};
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(1)
代码展示:
/**
* @param {ListNode} head
* @return {ListNode}
*/
var deleteDuplicates = function(head) {
if (!head) return head;
const l1 = new ListNode(0, head);
let p1 = l1;
while (p1.next && p1.next.next) {
if (p1.next.val === p1.next.next.val) {
const v = p1.next.val;
while (p1.next && p1.next.val === v) {
p1.next = p1.next.next;
}
} else {
p1 = p1.next;
}
}
return l1.next;
}
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(1)
代码展示:
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
if (!head) return head;
const l1 = new ListNode(0, head);
let p1 = l1;
let p2 = p1;
while (p2) {
p2 = p2.next;
n--;
}
while (p1 && p2) {
if (!p2.next) {
p1.next = p1.next ? p1.next.next : null;
}
p1 = p1.next;
p2 = p2.next;
}
return l1.next;
};
复杂度分析:
- 时间复杂度:O(n):n为链表节点数。
- 空间复杂度:O(1)
思路:
-
利用归并排序,先将链表对半拆分,一直拆分到单个链表节点
-
然后通过merge方法将每个独立链表边排序边合并,最终输出一个排好序的链表
代码展示
/**
* @param {ListNode} head
* @return {ListNode}
*/
var sortList = function (head) {
if (!head || !head.next) return head;
let slow = head, fast = head;
let preSlow = null;
while (fast && fast.next) {
preSlow = slow; // preSlow可以保存前一半链表
slow = slow.next; // slow的速度是fast的一半
fast = fast.next.next; // 当fast为null时,slow刚好可以获取到后一半链表
}
preSlow.next = null; // 此时preSlow.next = null,将链表截断,只剩前半条链表
const l = sortList(head); // 此时输入的head就是前一半链表,并且递归一直拆分成独立元素
const r = sortList(slow); // slow为后一半链表,递归拆分成独立元素
return merge(l, r); // 拆分到最小单元后,进行从短到长链表的合并
}
var merge = function (l1, l2) {
const dummy = new ListNode(-1);
let p0 = dummy;
let p1 = l1;
let p2 = l2;
while (p1 && p2) {
if (p1.val < p2.val) {
p0.next = p1;
p1 = p1.next;
} else {
p0.next = p2;
p2 = p2.next;
}
p0 = p0.next;
}
if (p1) p0.next = p1;
if (p2) p0.next = p2;
return dummy.next;
}
复杂度分析:
-
时间复杂度:O(nlogn)
-
空间复杂度:O(logn)
难度:困难
总结:
链表数据结构对于我来说有点不太好操作,需要多多练习,加深理解。