链表
常用的链表结构
- 单链表
- 双向链表 空间换时间
- 循环链表
链表的删除/插入是O(1)级别的,但是随机访问需要O(n)的复杂度 - 双向循环链表
技巧
- 理解指针或引用的含义
将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。
在编写链表代码的时候,我们经常会有这样的代码:p->next=q。这行代码是说,p 结点中的 next 指针存储了 q 结点的内存地址。
还有一个更复杂的,也是我们写链表代码经常会用到的:p->next=p->next->next。这行代码表示,p 结点的 next 指针存储了 p 结点的下下一个结点的内存地址。
- 警惕指针丢失和内存泄漏
注意不要把指针弄丢,比如插入是时候
在删除的时候要注意,释放内存
- 利用哨兵简化实现难度
在处理删除时,head节点比较难处理,所以添加一个哨兵,然后处理完,返回哨兵的next
- 重点处理边界条件
经常用来检查链表代码是否正确的边界条件有这样几个:
- 如果链表为空时,代码是否能正常工作?
- 如果链表只包含一个结点时,代码是否能正常工作?
- 如果链表只包含两个结点时,代码是否能正常工作?
- 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?
- 举例画图,辅助思考,多建指针
写在纸上,把指针赋值和调整之后的结果也画出来,对照写,就比较简单了
练习题
共25个已完成13个,全是中等
个常见的链表操作:
- 1.单链表反转 206
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
var prev = null;
var cur = head;
var next = null;
while (cur !== null) {
next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev;
}
// 链表的递归实现
var reverseList = function(H) {
if (H === null || H.next === null)
//链表为空直接返回,而H->next为空是递归基
return H
var newHead = reverseList(H.next) //一直循环到链尾
H.next.next = H //翻转链表的指向
H.next = null //记得赋值NULL,防止链表错乱
return newHead //新链表头永远指向的是原链表的链尾
}
- 2.链表中环的检测 141
使用set来存储
var hasCycle = function(head) {
const set = new Set();
if (head == null) {
return false;
}
while(head.next !== null) {
if(set.has(head)) {
return true;
} else {
set.add(head);
}
head = head.next;
}
return false;
};
使用快慢指针
var hasCycle = function(head) {
if (head == null || head.next == null) {
return false;
}
let slow = head;
let fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
};
- 3.两个有序链表合并 21
var mergeTwoLists = function(l1, l2) {
let preHead = new ListNode();
let cur = preHead;
while (l1 && l2){
if(l1.val <= l2.val){
cur.next = new ListNode(l1.val);
l1 = l1.next;
}
else {
cur.next = new ListNode(l2.val);
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 || l2
return preHead.next;
};
- 4.删除链表倒数第n个节点 19
var swapPairs = function(head) {
var dummyHead = new ListNode(0);
dummyHead.next = head;
p = dummyHead;
while(p.next && p.next.next) {
var node1 = p.next;
var node2 = p.next.next;
var next = node2.next;
node2.next = node1;
node1.next = next;
p.next = node2;
p = node1
}
if (dummyHead.next === null) {
return []
}else {
return dummyHead.next;
}
};
- 5.求链表的中间节点 876
空间换时间 O(n) 空间复杂度:O(N)
var middleNode = function(head) {
const arr = [];
while(head !== null) {
arr.push(head);
head = head.next;
}
const middle = Math.floor(arr.length / 2);
return arr[middle];
};
快慢指针
var middleNode = function(head) {
slow = fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
};
- 6.两两交换链表中的节点 24 需要补充图
var swapPairs = function(head) {
var dummyHead = new ListNode(0);
dummyHead.next = head;
p = dummyHead;
while(p.next && p.next.next) {
var node1 = p.next;
var node2 = p.next.next;
var next = node2.next;
node2.next = node1;
node1.next = next;
p.next = node2;
p = node1
}
if (dummyHead.next === null) {
return []
}else {
return dummyHead.next;
}
};
- 7.删除链表中的节点 237
把下一个的值赋给这个node,然后删除下一个节点即可
var deleteNode = function(node) {
node.val = node.next.val;
node.next = node.next.next;
};
- 8.删除排序链表中的重复元素 83
因为是有序的,所以直接前一个跟后一个对比删除就行了时间复杂度O(N)
如何是无序的使用set也可以做到O(N), 就是空间换时间
var deleteDuplicates = function(head) {
if (head == null) {
return [];
}
let cur = head;
while(cur && cur.next){
if (cur.val === cur.next.val) {
cur.next = cur.next.next
} else {
cur = cur.next
}
}
return head;
};
- 9.移除链表元素 203
var removeElements = function(head, val) {
while (head !== null && head.val === val) {
head = head.next;
}
if (head === null) {
return []
}
let cur = head;
while(cur.next !== null) {
if (cur.next.val === val) {
cur.next = cur.next.next
} else {
cur = cur.next;
}
}
return head;
};
<!--递归-->
if (head == null){
return null;
}
head.next = removeElements(head.next,val);
return head.val == val ? head.next:head;
// 使用头部守位 然后返回 dummnyHead.next
var removeElements = function(head, val) {
let dummnyHead = new ListNode(0);
dummnyHead.next = head;
let cur = dummnyHead;
while(cur.next !== null) {
if (cur.next.val === val) {
cur.next = cur.next.next
} else {
cur = cur.next;
}
}
if (dummnyHead.next === null){
return []
} else {
return dummnyHead.next
}
};
- 10.回文链表 234
快慢针找到中间,然后把后面的翻转,然后对比
var reverseList = function(head) {
var pre = null;
var cur = head;
var next = null;
while(cur !== null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
var isPalindrome = function(head) {
if(head === null || head.next === null) {
return true;
}
fast = slow = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
}
if(fast) slow = slow.next;
let list2 = reverseList(slow);
while(list2 !== null) {
if (head.val !== list2.val) {
return false;
}
head = head.next;
list2 = list2.next;
}
return true;
};
- 11.旋转链表 61
- 12.相交链表 160
先变量两个表,然后把长度磨平,然后在对比节点是否相等
var getIntersectionNode = function(headA, headB) {
if(headA === null || headB === null) {
return null;
}
var listA = headA;
var listB = headB;
var lenA = 0;
var lenB = 0;
while(listA !== null) {
listA = listA.next;
lenA++;
}
while(listB !== null) {
listB = listB.next;
lenB++;
}
if (lenA > lenB) {
var diff = lenA - lenB;
while(diff>0) {
headA = headA.next;
diff--;
}
}
if (lenB > lenA) {
var diff = lenB - lenA;
while(diff>0) {
headB = headB.next;
diff--;
}
}
while(headB!=null) {
if(headA==headB)
return headA;
headA=headA.next;
headB=headB.next;
}
return null;
};
- 13.排序链表 148 https://www.youtube.com/watch?v=M1TwY0nsTZA
- 14.分隔链表 86
- 15.链表随机节点 382 https://www.youtube.com/watch?v=oCKJXWpgFm4
public class Solution {
ListNode head;
Random random;
public Solution(ListNode h) {
head = h;
random = new Random();
}
public int getRandom() {
ListNode c = head;
int r = c.val;
for(int i=1;c.next != null;i++){
c = c.next;
if(random.nextInt(i + 1) == i) r = c.val;
}
return r;
}
}
- 16.链表组件 817 https://www.youtube.com/watch?v=y1_lFSkQNDc
- 17.最长数对链 646
- 18.重排链表 143
- 19.分隔链表 725 https://www.youtube.com/watch?v=fk8JTWhM-4U
var len = 0, curr = root;
while (curr) {
len++;
curr = curr.next;
}
var quo = Math.floor(len / k);
var rem = len % k;
var results = [root];
curr = root;
var last = curr;
while (k > 1) {
for (var i = 0; i < quo; i++) {
last = curr;
curr = curr.next;
}
if (rem > 0) {
last = curr;
curr = curr.next;
rem--;
}
if (last) {
last.next = null;
}
results.push(curr);
k--;
}
return results.map(item => item === null ? []: item);
先遍历一遍,拿到长度,然后len / k 就是要分成几个,然后len % k 就是有一个是多1的
[1,2,3,4,5,6,7,8,9,10]
===>
[4,3,3]
- 20.删除排序链表中的重复元素 83
var deleteDuplicates = function(head) {
if (head == null) {
return [];
}
let cur = head;
while(cur && cur.next){
if (cur.val === cur.next.val) {
cur.next = cur.next.next
} else {
cur = cur.next
}
}
return head;
};
- 21.奇偶链表 328
- 22.两数相加 2
var addTwoNumbers = function(l1, l2) {
if (!l1) return l2
if (!l2) return l1
let p1 = l1
let p2 = l2
let val, carry
const ret = new ListNode()
let cur = ret
while (p1 || p2) {
let current = add(p1, p2, carry)
cur.val = current.val
carry = current.carry
if (p1) p1 = p1.next
if (p2) p2 = p2.next
if (p1 || p2) {
cur.next = new ListNode()
cur = cur.next
}
}
if (carry) {
cur.next = new ListNode(carry)
}
return ret
};
function add(p1, p2, _carry = 0) {
let val = ((p1 && p1.val) || 0) + ((p2 && p2.val) || 0) + _carry
let carry = (val >= 10) ? 1 : 0
if (carry) { val -= 10 }
return { val, carry }
}
- 23.删除排序链表中的重复元素 II82
- 24.k个一组翻转链表 25