- 分类刷算法题
- 之前陆陆续续刷了一些,但是感觉都没有真正掌握起来吧,做的题杂乱无章,所以这次决定好好整理一下好好过一遍
- 今天要刷的是链表
一、 链表的合并
- 剑指 Offer 25. 合并两个排序的链表 - 力扣(LeetCode)
- 主要运用的就是’穿针引线’的思路,比较两个,那个比较小就给那个串起来
- 难度:简单
- 思路:
- 初始化dummy伪结点,cur指向dummy结点
- 进入while循环:当l1为空或者l2为空时跳出循环
- 当l1.val<l2.val时,cur后继结点指向l1,l1向前走一步
- 当l1.val<l2.val时,cur后继结点指向l2,l2向前走一步
- cur向前走一步
- 合并剩余的部分
var mergeTwoLists = function(l1, l2) {
let dummy = new ListNode;
let cur = dummy;
//两个都节点都存在时,就一直穿梭引线
while(l1 && l2) {
//当l1的值比较小的时候,cur的next指向l1,反之指向l2
if(l1.val< l2.val) {
cur.next = l1
l1 = l1.next;
}else{
cur.next = l2;
l2 = l2.next;
}
//串起一个结点后,‘针’也要向前走一步
cur = cur.next;
}
//最后剩下的就给它直接一整串串起来
cur.next = l1 === null? l2:l1;
return dummy.next
};
二、链表的删除
- 83. 删除排序链表中的重复元素 - 力扣(LeetCode)
- 这个很常见也很基础
- 难度: 简单
- 思路:
- cur指向head
- 进入while循环:当cur指向结点为空或cur的后继结点为空时跳出循环
- 如果cur指向结点值等于其后继结点的值,则让他的后继结点指向其后继结点的后继结点
- 否则cur向前走一步
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
};
- 82. 删除排序链表中的重复元素 II - 力扣(LeetCode)
- 难度:中等
- 这道题就是上一道题的衍生了,这道题需要把重复的全部删除,那么就可能出现头结点也要删除遇到这种情况我们就要加他加上dummy伪头结点,才可以next指向真实头结点对他进行删除
- 思路:
- 初始化dummy结点:让他的后继节点指向head,同时cur指向dummy
- 进入while循环:cur的后继节点或cur的后继节点的后继节点为空时跳出循环
- 当cur的后继节点值等于其后继节点的后继节点值时,遍历其后继节点,对该值节点进行删除
- 否则的话cur向前走一步
var deleteDuplicates = function(head) {
//dummy伪头结点
let dummy = new ListNode();
dummy.next = head;
let cur = dummy;
while(cur.next && cur.next.next) {
if(cur.next.val == cur.next.next.val) {
let val = cur.next.val
while(cur.next && cur.next.val == val) {
cur.next = cur.next.next;
}
}else{
cur = cur.next;
}
}
return dummy.next
};
- 剑指 Offer 18. 删除链表的节点 - 力扣(LeetCode)
- 难度:简单
- 如上一道题所述,看到删除节点有可能删除第一个节点我们可能就要设置伪头结点啦;
- 同时这道题说的是删除一个节点,也就是说如果头结点是需要删除的节点话,我们可以采用另一种方法,遇到删除节点为头结点时直接返回head.next就行
- 以上两种思路跟上述题都差不多就不赘述啦
//设置伪头结点
var deleteNode = function(head, val) {
let dummy = new ListNode();
dummy.next = head;
let cur = dummy;
while(cur && cur.next) {
if(cur.next.val == val) {
cur.next = cur.next.next;
return dummy.next
}else{
cur = cur.next;
}
}
};
//遇到头结点需要删除,直接返回head.next
var deleteNode = function(head, val) {
if(head.val == val) return head.next;
let cur = head;
while(cur && cur.next) {
if(cur.next.val == val) {
cur.next = cur.next.next;
return head;
}else{
cur = cur.next;
}
}
};
- 剑指 Offer II 021. 删除链表的倒数第 n 个结点 - 力扣(LeetCode)
- 难度: 中等
- 这道题我们采取新的思路-快慢指针
- 遇到涉及相对复杂的链表操作,比如反转、指定位置的删除等等经常需要涉及到反复遍历,解决这类题,我们用到的是双指针中的“快慢指针”
- 快慢指针指的是两个一前一后的指针,两个指针往同一个方向走,只是一个快一个慢
- 涉及链表操作、尤其是涉及结点删除的题目,我都建议大家写代码的时候直接把
dummy
给用起来,建立好的编程习惯,这个在后面也不会再赘述啦 - 还有一道很类似的题,做完这道题也可以试一试喔:剑指 Offer 22. 链表中倒数第k个节点 - 力扣(LeetCode)
- 思路:
- 设置伪头结点
- 初始化快指针fast, 慢指针low 都指向dummy
- 先让快指针走K步(目的:当快指针走到最后的结点时,慢结点刚好指向倒数第K个结点的前继结点)
- 让快慢指针同频向前走直到fast走到最后的结点
- 删除慢指针的后继节点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tyzcqBC6-1662475101414)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/73192dec21274eefb8283e248d23fc82~tplv-k3u1fbpfcp-watermark.image?)]
var removeNthFromEnd = function(head, n) {
let dummy = new ListNode();
dummy.next = head;
let fast = dummy;
let slow = dummy;
while(n!=0) {
fast = fast.next;
n--
}
while(fast.next) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next
};
做完上述的题还可以试试以下的题哦
203. 移除链表元素 - 力扣(LeetCode): 这个题跟上述剑指offer18的题很像
三、链表反转
- 剑指 Offer II 024. 反转链表 - 力扣(LeetCode)
- 难度: 简单
- 上述题目我们用到了双指针法的快慢指针,那我们这道题用一下多指针法
- pre,cur,next依次指向连续结点
- pre为cur的前继结点,用于反转链表
- cur为当前结点,通过cur遍历
- next指向cur的后继节点,防止反转后丢失后继链表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gZmI3ySr-1662475101415)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/65bcc6d793ee4b32a24c128bfc0d0e73~tplv-k3u1fbpfcp-watermark.image?)]
var reverseList = function(head) {
let pre = null;
let cur = head;
while(cur) {
let next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre
};
- 92. 反转链表 II - 力扣(LeetCode)
- 难度:中等
- 这道题跟上一道题很像,不一样的是上一道题是全部反转,这道题属于局部反转
- 采用先反转再拼接,所以记得要把left的前一个节点和right的后一个节点记录下来
- 思路:
- 初始化伪头结点dummy
- 初始化leftNode,rightNode,通过for循环让其分别指向left的前继结点和right节点
- 切断
- 局部反转(直接采用上一道题的代码)
- 拼接
- 这道题直接做可能会容易乱,画个图就好啦~
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sRl62BF6-1662475101416)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/472124e362ce418299c01ba59da63a22~tplv-k3u1fbpfcp-watermark.image?)]
var reverseBetween = function(head, left, right) {
let dummy = new ListNode();
dummy.next = head;
let leftNode = dummy;
let rightNode = dummy;
//走到left的前继结点
for(let i =0 ;i<left-1; i++) {
leftNode = leftNode.next;
}
//走到right结点
for(let i=0; i<right; i++) {
rightNode = rightNode.next;
}
//记录下list1和list2
let list1 = leftNode.next;
let list2 = rightNode.next;
//切断
leftNode.next = null;
rightNode.next = null;
//反转中间链表
let list3 = reverseList(list1);
//拼接
leftNode.next = list3;
list1.next = list2;
//返回
return dummy.next
};
var reverseList = function(head) {
let pre = null;
let cur = head;
while(cur) {
let next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre
};
四、环形链表
- 141. 环形链表 - 力扣(LeetCode)
- 难度: 简单
- 遇到环形的还可以多加一个判断:如果只有两个结点的话,是无法构成环形的
- 这道题的方法太多啦而且都是很常见的方法
- 可以利用哈希表,哈希表存储已经经过的结点,如果经过的节点一样的就说明环形了
- 也可以使用上面说过的双指针里面的快慢指针:如果链表是环形的,那么快指针一定会追上慢指针
- 也可以使用最简单的标记方法:我经过就把它的flag变为true,只有我经过的节点flag为true,那么一定成环了
var hasCycle = function(head) {
//使用快慢指针
//如果只有两个节点也构不成环形链表
if(head == null || head.next == null || head.next.next == null) return false
let n1 = head;
let nn = head.next;
while(n1 != nn){
//下一个节点能够为空说明不环形
if(nn.next == null || nn.next.next == null) {
return false;
}
//一个每次走一步一个每次走两步,形成快慢
n1 = n1.next;
nn = nn.next.next;
}
return true
};
var hasCycle = function(head) {
//使用flag
//如果只有两个节点也构不成环形链表
if(head == null || head.next == null || head.next.next == null) return false
while(head) {
if(head.flag) {
return true
}else{
head.flag = true;
head = head.next;
}
}
return false
};
- 142. 环形链表 II - 力扣(LeetCode)
- 难度:中等
- 这道题跟上面基本一样啦,改一下返回值就好啦
var detectCycle = function(head) {
if(head == null || head.next == null || head.next.next == null) return null
while(head) {
if(head.flag) {
return head
}else{
head.flag = true;
head = head.next;
}
}
return null
};
五、链表的相交
- 160. 相交链表 - 力扣(LeetCode)
- 难度: 简单
- 这个题的思路就是如果我相交的话,那么两个指针分别在两个链表走一遍,那么一定会有相交的结点
- 若两链表 有 公共尾部 (即 c > 0c>0 ) :指针 A , B 同时指向第一个公共节点node
- 若两链表 无 公共尾部 (即 c = 0c=0 ) :指针 A , B 同时指向 null
var getIntersectionNode = function(headA, headB) {
//如果有一个链表为空的话那么一定不会相交
if(headA === null || headB === null) return null
let pA = headA;
let pB = headB;
//这里没有相交是不会死循环哦,因为会同时指向null,也是相等;所以退出循环不一定是没有相交
while(pA != pB) {
//如果pA指向的为空,则切换到headB ,否则就指向其后继节点
pA = pA === null? headB: pA.next;
//如果pB指向的为空,则切换到headA ,否则就指向其后继节点
pB = pB === null? headA: pB.next;
}
return pA
};
六、其他链表题
- 876. 链表的中间结点 - 力扣(LeetCode)
- 难度:简单
- 当快指针走到最后的时候,慢指针刚好在中间结点
var middleNode = function(head) {
//使用快慢指针
let fast = head;
let slow = head;
while(fast && fast.next) {
fast = fast.next.next;
slow = slow.next;
}
return slow
};
以上代码是自己做题的总结,可能不够完美哈哈哈,建议还是多看看LeetCode的题解喔