赞
踩
1.当你需要创造一条新链表的时候,可以使用虚拟头结点简化边界情况的处理。使用虚拟头节点时,注意题目要求,返回时一般要head.next。跟双指针同时使用时,返回新链表的一定是head.next,而不是p.next!!!因为这个会动的指针最后已经不知道知道哪里去了,犯了一百遍的错误。
2.递归的思想相对迭代思想。不要跳进递归,而是利用明确的定义来实现算法逻辑。递归操作链表并不高效。和迭代解法相比,虽然时间复杂度都是 O(N),但是迭代解法的空间复杂度是 O(1),而递归解法需要堆栈,空间复杂度是 O(N)。
3.链表是一种兼具递归和迭代性质的数据结构,认真思考一下可以发现这个问题具有递归性质。
4.双指针。对于单链表来说,大部分技巧都属于快慢指针。
1.合并两个有序链表
简单
思路:
输入两个有序链表,把他俩合并成一个新的有序链表。双指针分别指向两个链表,依次比较两个指针指向的值的大小,值比较小的连接到新链表上,同时将该指针往后移一位。循环往复,直到一个或两个链表遍历完毕。并将未遍历完的链表的剩余部分直接拼在新链表上。
/** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode} list1 * @param {ListNode} list2 * @return {ListNode} */ var mergeTwoLists = function(list1, list2) { let ret = new ListNode(0,null); let p1= list1, p2 = list2, cur = ret; while(p1&&p2){ if(p1.val<p2.val){ cur.next = p1; p1 = p1.next; } else{ cur.next = p2; p2 = p2.next; } cur = cur.next; } if(p1==null){ cur.next = p2; } if(p2==null){ cur.next = p1; } return ret.next; };
2.单链表的分解
中等
思路:
将这个链表一分为二,一个链表存小于x的值,一个链表存大于等于x的值,最后再把两个链表拼接。
注意:
1.由于直接在原链表上操作,将原链表指针消除,否则连得太乱循环会报错。
2.老问题 返回的时候注意返回的指针指向的是哪一个。
/** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode} head * @param {number} x * @return {ListNode} */ var partition = function(head, x) { let head1 = new ListNode(0,null),head2 = new ListNode(0,null); let p1 = head1, p2=head2, p = head; while(p){ //小值放在p1 大值放在p2 if(p.val<x){ p1.next = p; p1 = p1.next; } else{ p2.next = p; p2 = p2.next; } //由于直接在原链表上操作,将原链表指针消除,否则连得太乱循环会报错 let temp = p.next; p.next = null; p = temp; } p1.next = head2.next; //添加虚拟头指针时返回时不要忘了next //这里第一次写成return p1.next,然而p1已经遍历到第一个链表最后了... return head1.next; };
3.合并k个有序链表
困难
思路:
1.两两归并排序。将k个链表以队列的形式弹出前两个归并,生成的新链表归并入数组末尾,继续弹出前两个排序,直到数组只有一个元素为止。
/** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode[]} lists * @return {ListNode} */ var mergeTwoLists = function(listA,listB){ let ret = new ListNode(0,null); let p1= listA, p2 = listB, cur = ret; while(p1&&p2){ if(p1.val<p2.val){ cur.next = p1; p1 = p1.next; } else{ cur.next = p2; p2 = p2.next; } cur = cur.next; } if(p1==null){ cur.next = p2; } if(p2==null){ cur.next = p1; } return ret.next; } var mergeKLists = function(lists) { if(lists.length == 0)return null; while(lists.length > 1){ lists.push(mergeTwoLists(lists.shift(),lists.shift())) } return lists[0] };
2.优先级队列(最小堆)
参考:合并k个有序链表、labuladong
/** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode[]} lists * @return {ListNode} */ var mergeKLists = function(lists) { // 两种思路:优先级队列(二叉堆) 和 JS-API排序 // 首先实现一个优先级队列的数据结构(见下文 class: primaryQueue) // 声明一个优先级队列 pq = new primaryQueue() // 构造一个新的链表头节点(第一个节点设置为0,避免空节点等情况) let result = new ListNode(0) let p = result // 插入所有list的头节点 for(list of lists){ if(list) pq.insert(list) } // 将目前队列中的节点进行比较、将最小的追加在原链表后 while(pq.getSize()>1){ const temp = pq.pop() p.next = temp p = p.next // temp 表示 本轮比较中最小的头节点 // temp.next 表示这个链表中的下一个节点 if(temp.next) pq.insert(temp.next) } return result.next }; // 实现优先级队列 class primaryQueue{ // 定义一个“堆”,存放所有的元素 constructor(){ this.heap = [] this.heap[0] = 0 } // 交换任意两个节点 swap(index1,index2){ // 存一个别人的写法,有待研究 // [this.heap[i1], this.heap[i2]] = [this.heap[i2], this.heap[i1]]; let temp = this.heap[index1] this.heap[index1] = this.heap[index2] this.heap[index2] = temp } // 返回父节点索引 getParentIndex(node){ return Math.floor(node/2) } // 返回左节点索引 getLeftIndex(node){ return node*2 } // 返回右节点索引 getRightIndex(node){ return node*2 + 1 } // 上浮 shiftUp(node){ // 在堆顶之前都while循环着 while(node>1){ let parentNode = this.getParentIndex(node) if(this.heap[parentNode].val > this.heap[node].val){ this.swap(parentNode,node) // 交换值 // 更新传入的node的索引(随着交换操作上移了) node = this.getParentIndex(node) } else break //堆结构已经得到保证,不需要循环到堆顶了,直接跳出循环 } } // 下沉 shiftDown(node){ // 在堆底之前都循环着 while(this.heap[this.getLeftIndex(node)]){ let tempIndex = this.getLeftIndex(node) let rightIndex = this.getRightIndex(node) if(this.heap[rightIndex] && this.heap[tempIndex].val > this.heap[rightIndex].val){ tempIndex = rightIndex } if(this.heap[node].val<this.heap[tempIndex].val) break // 如果这个节点比两个子节点都大,退出循环 this.swap(tempIndex,node) node = tempIndex } } // 插入节点 insert(val){ // 把节点放到堆底,执行上浮操作,让所有节点处于正确位置 this.heap.push(val) this.shiftUp(this.heap.length-1) } // 删除最小的节点 pop(){ // 将原来最小的节点(堆顶)和堆底的节点交换 const top = this.heap[1] this.swap(1,this.heap.length-1) this.heap.length-- // 删除交换后的堆底节点,即原来的堆顶节点 this.shiftDown(1) // 对交换到堆定的节点进行下沉操作,保证堆的结构正确 return top } // 返回最大的节点 getMin(){ return this.heap[1] } // 获取当前堆的大小 getSize(){ return this.heap.length } }
4.删除单链表的倒数第 k 个节点
中等
思路:
双指针法。只用遍历一次链表即可。定义fast指针和slow指针,初始值为虚拟头结点。fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作)。fast和slow同时移动,直到fast指向末尾。删除slow指向的下一个节点。
/** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode} head * @param {number} n * @return {ListNode} */ var removeNthFromEnd = function(head, n) { let ret = new ListNode(0,head); let slow = fast = ret; while(n--)fast=fast.next; while(fast.next!=null){ slow=slow.next; fast =fast.next; } slow.next =slow.next.next; return ret.next; };
5.单链表的中点
简单
**思路:**让两个指针 slow 和 fast 分别指向链表头结点 head。每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点。
/** * 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 {ListNode} */ var middleNode = function(head) { let fast = slow =head; while(fast&&fast.next){ slow = slow.next; fast = fast.next.next; } return slow; };
6.环形链表II
中等
思路:
参考代码随想录环形链表II、labuladong
解决方案也是用快慢指针,每当慢指针 slow 前进一步,快指针 fast 就前进两步。如果 fast 最终遇到空指针,说明链表中没有环;如果 fast 最终和 slow 相遇,那肯定是 fast 超过了 slow 一圈,说明链表中含有环。
假设快慢指针相遇时,慢指针 slow 走了 k 步,那么快指针 fast 一定走了 2k 步。fast 一定比 slow 多走了 k 步,这多走的 k 步其实就是 fast 指针在环里转圈圈,所以 k 的值就是环长度的「整数倍」。假设相遇点距环的起点的距离为 m,那么结合上图的 slow 指针,环的起点距头结点 head 的距离为 k - m,也就是说如果从 head 前进 k - m 步就能到达环起点。巧的是,如果从相遇点继续前进 k - m 步,也恰好到达环起点。因为结合上图的 fast 指针,从相遇点开始走k步可以转回到相遇点,那走 k - m 步肯定就走到环起点了。所以,只要我们把快慢指针中的任一个重新指向 head,然后两个指针同速前进,k - m 步后一定会相遇,相遇之处就是环的起点了。
/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } */ /** * @param {ListNode} head * @return {ListNode} */ var detectCycle = function(head) { let slow = fast = head; if(!fast||!fast.next)return null; while(fast&&fast.next){ slow = slow.next; fast = fast.next.next; if(slow==fast){ slow = head; while(slow!=fast){ slow =slow.next; fast = fast.next; } return slow; } } return null; };
7.两个链表是否相交
简单
思路:
交点不是数值相等,而是指针相等。
1.如果用两个指针 p1 和 p2 分别在两条链表上前进,并不能同时走到公共节点,也就无法得到相交节点 c1。解决这个问题的关键是,通过某些方式,让 p1 和 p2 能够同时到达相交节点 c1。所以,我们可以让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起。如果这样进行拼接,就可以让 p1 和 p2 同时进入公共部分,也就是同时到达相交节点 c1。
2.curA指向链表A的头结点,curB指向链表B的头结点。求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到和curB 末尾对齐的位置, 此时就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。否则循环退出返回空指针。
//思路1 /** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } */ /** * @param {ListNode} headA * @param {ListNode} headB * @return {ListNode} */ var getIntersectionNode = function(headA, headB) { let p1 = headA, p2 = headB; while(p1!=p2){ p1=p1.next; p2=p2.next; if(p1==null&&p2==null)return null; if(p1==null){ p1 = headB; } if(p2== null){ p2 = headA; } } return p1; }; //思路2 /** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } */ /** * @param {ListNode} headA * @param {ListNode} headB * @return {ListNode} */ var getLen = function(head){ let cur = head, len = 0; while(cur){ len++; cur=cur.next; } return len; } var getIntersectionNode = function(headA, headB) { let curA = headA, curB = headB; let lenA = getLen(headA), lenB = getLen(headB); if(lenA < lenB){ //es6特性 交换 [curA, curB] = [curB, curA]; [lenA, lenB] = [lenB, lenA]; } let gap = lenA - lenB; while(gap--){ curA=curA.next; } while(curA&&curA!=curB){ curA = curA.next; curB = curB.next; } return curA; };
8.反转链表
简单
思路:
1.首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。
2.递归的简洁使用。参考labuladong
/** * 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 {ListNode} */ var reverseList = function(head) { return reverse(null, head); }; var reverse = function(pre,head){ if(!head) return pre; let temp = head.next; head.next = pre; return reverse(head, temp); } //递归2 var reverseList = function(head) { if (head == null || head.next == null) return head; // 递归终止条件 let last = reverseList(head.next); // 递归反转后续节点 head.next.next = head; // 将当前节点设置为后续节点的后续节点 head.next = null; // 将当前节点的后续节点设置为空 return last; // 返回反转后的链表 }
9.反转链表前 N 个节点
思路:
base case 变为 n == 1,反转一个元素,就是它本身,同时要记录后驱节点。刚才直接把 head.next 设置为 null,因为整个链表反转后原来的 head 变成了整个链表的最后一个节点。但现在 head 节点在递归反转之后不一定是最后一个节点了,所以要记录后驱 follower(第 n + 1 个节点),反转之后将 head 连接上。
let follower = null;
var reverseListTopN = function(head,n){
if(n==1){
follower = head.next;
return head;
}
let last = reverseListTopN(head.next,n-1);
head.next.next = head;
head.next = follower;
return last;
}
10.反转链表一部分
中等
思路:
1.递归。如果 m == 1,就相当于反转链表开头的 n 个元素。当 m != 1,如果把 head 的索引视为 1,那么是想从第 m 个元素开始反转;如果把 head.next 的索引视为 1 ,那么相对于 head.next,反转的区间应该是从第 m - 1 个元素开始的,反转区间为[m-1,n-1]。
2.迭代。
/** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode} head * @param {number} left * @param {number} right * @return {ListNode} */ let follower = null; var reverseTopN = function(head,n){ if(n==1){ follower = head.next; return head; } let last = reverseTopN(head.next, n-1); head.next.next = head; head.next = follower; return last; } var reverseBetween = function(head, left, right) { if(left == 1){ return reverseTopN(head, right); } head.next = reverseBetween(head.next, left-1, right-1); return head; }; //迭代 /** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode} head * @param {number} left * @param {number} right * @return {ListNode} */ var reverseBetween = function(head, left, right) { let ret = new ListNode(0,head); let pre = ret, cur = head; let prev= ret; for(let i = 1; i<left+1; i++){ pre = pre.next; cur = cur.next; if(i==left-1)prev=pre; } // console.log(pre); // console.log(cur); // console.log(prev); let last = pre; let n = right-left; // console.log(last); while(n--){ let temp = cur.next; cur.next = pre; pre = cur; cur = temp; } prev.next = pre; // console.log(last); last.next = cur; // console.log(pre); // console.log(cur); return ret.next; };
10. K 个一组翻转链表
困难
思路:
递归。先反转以 head 开头的 k 个元素。将第 k + 1 个元素作为 head 递归调用 reverseKGroup 函数。将上述两个过程的结果连接起来。如果最后的元素不足 k 个,就保持不变。这就是 base case。「反转以 a 为头结点的链表」其实就是「反转 a 到 null 之间的结点」,「反转 a 到 b 之间的结点」只要更改函数签名,并把上面的代码中 null 改成 b 即可。注意 reverse 函数是反转区间 [a, b)。
/** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode} head * @param {number} k * @return {ListNode} */ var reverseKGroup = function(head, k) { if(head==null)return null; var reverse = function(head,tail){ let pre = null, cur = head; // while 终止的条件改为tail while(cur!=tail&&cur){ let temp = cur.next; cur.next = pre; pre = cur; cur = temp; } return pre; } let a = head, b = head; //左闭右开 区间 [a, b) 包含 k 个待反转元素 for(let i = 0; i<k; i++){ // 不足 k 个,不需要反转,base case if (b==null) return a; b=b.next; } let newHead = reverse(a,b); // 递归反转后续链表并连接起来 a.next = reverseKGroup(b,k); return newHead; };
11.回文链表
简单
思路:
回文串就是正着读和反着读都一样的字符串。
1.原始链表反转存入一条新的链表,然后比较这两条链表是否相同。
2.利用二叉树后序遍历。
3.翻转后半部分链表,并进行回文串的判断。
定义slow,fast两个指针,slow每次走一步,fast每次走两步。当fast走到尾结点或者null时,slow到达中点。
如果fast指针没有指向null,说明链表长度为奇数,slow还要再前进一步。
从slow开始反转后面的链表,现在就可以开始比较回文串了。
如果不想改变原有链表结构,只需:
p.next = reverse(q);
第三种思路解法如下:
/** * 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 {boolean} */ var isPalindrome = function(head) { if(!head&&head.next == null) return true; var reverse = function(head){ let pre = null, cur = head, temp = null; while(cur){ temp = cur.next; cur.next = pre; pre = cur; cur = temp; } return pre; } let slow = fast = head; while(fast&&fast.next){ slow = slow.next; fast = fast.next.next; } let p = slow; //如果是奇数串 找到中点后slow还要往后走一步 if(fast!=null){ slow = slow.next; } let left = head; let q = reverse(slow); let right = q; while(right){ if(left.val !== right.val){ return false; } left = left.next; right = right.next; } //如果不想破坏原链表的结构 可加这一句 p.next = reverse(q); return true; };
12.两两交换链表中的节点
中等
思路:
双指针。使用虚拟头节点处理。
/** * 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 {ListNode} */ var swapPairs = function(head) { let ret = new ListNode(0,head), temp=ret; while(temp.next&&temp.next.next){ let pre = temp.next; let cur = temp.next.next; pre.next = cur.next; cur.next = pre; temp.next = cur; temp = pre; } return ret.next; };
13.设计链表
中等
class LinkNode{ constructor(val,next){ this.val=val; this.next=next; } } var MyLinkedList = function() { this._size = 0; this._head = null; this._tail = null; }; MyLinkedList.prototype.getNode = function(index){ if(index<0 || index>=this._size) return null; if(index==0) return this._head; let cur = new LinkNode(0,this._head); for(let i = 0; i<=index;i++){ cur = cur.next; } return cur; } /** * @param {number} index * @return {number} */ MyLinkedList.prototype.get = function(index) { if(!this.getNode(index)) return -1; let node = this.getNode(index); return node.val; }; /** * @param {number} val * @return {void} */ MyLinkedList.prototype.addAtHead = function(val) { let node = new LinkNode(val,this._head); this._head = node; if(!this._tail){ this._tail = node; } this._size++; return; }; /** * @param {number} val * @return {void} */ MyLinkedList.prototype.addAtTail = function(val) { let node = new LinkNode(val,null); if(this._tail){ this._tail.next = node; this._tail = node; this._size++; return; } this._tail = node; this._head = node; this._size++; return; }; /** * @param {number} index * @param {number} val * @return {void} */ MyLinkedList.prototype.addAtIndex = function(index, val) { if(index>this._size) return; if(index<=0){ this.addAtHead(val); return; } if(index==this._size){ this.addAtTail(val); return; } let node = new LinkNode(val, null); let last = this.getNode(index-1); node.next = last.next; last.next = node; this._size++; return; }; /** * @param {number} index * @return {void} */ MyLinkedList.prototype.deleteAtIndex = function(index) { if(index>=this._size || index<0) return; if(index==0){ this._head = this._head.next; if(this._size==1){ this._tail=null; } this._size--; return; } let last = this.getNode(index-1) last.next = last.next.next; if(index==this._size-1){ this._tail = last; } this._size--; return; }; /** * Your MyLinkedList object will be instantiated and called as such: * var obj = new MyLinkedList() * var param_1 = obj.get(index) * obj.addAtHead(val) * obj.addAtTail(val) * obj.addAtIndex(index,val) * obj.deleteAtIndex(index) */
14.移除链表元素
简单
/** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode} head * @param {number} val * @return {ListNode} */ 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; };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。