代码随想录算法训练营Day4 | | 24. 两两交换链表中的节点 ,19.删除链表的倒数第N个节点 ,面试题 02.07. 链表相交,142.环形链表II

24. 两两交换链表中的节点

题目连接:24. 两两交换链表中的节点 - 力扣(Leetcode)

遇到难点

暂无,但是对于递归的方法无法想到

题解

1.迭代法

理解题目意思后,其实就可以简单的理解为几个数交换位置,由于链表的特殊性质,在添加节点的时候是不需要零时创建变量存储的,但是在这题中,明显需要。其次,对于头部节点的区别,最好的方法就是创建一个虚拟节点从而不用额外考虑头部节点

var swapPairs = function(head){
  let dummyHead = new ListNode(0,head)
  let cur = dummyHead
  while(cur.next != null && cur.next.next != null){
    let node1 = cur.next
    let node2 = cur.next.next.next
    cur.next = cur.next.next
    cur.next.next = node1
    cur.next.next.next = node2
    cur = cur.next.next
  }
  return dummyHead.next
}

时间复杂度:O(n)
空间复杂度:O(1)

需要注意的是,在不清晰每一条链如何断开连接的时候,应该一步一步画出图像,思路自然而然就清晰了

2.递归法

递归的终止条件是链表中没有节点,或者链表中只有一个节点,此时无法进行交换。

果链表中至少有两个节点,则在两两交换链表中的节点之后,原始链表的头节点变成新的链表的第二个节点,原始链表的第二个节点变成新的链表的头节点。链表中的其余节点的两两交换可以递归地实现。在对链表中的其余节点递归地两两交换之后,更新节点之间的指针关系,即可完成整个链表的两两交换。

var swapPairs = function(head) {
    if (head === null|| head.next === null) {
        return head;
    }
    const newHead = head.next;
    head.next = swapPairs(newHead.next);
    newHead.next = head;
    return newHead;
}

时间复杂度:O(n)

空间复杂度:O(n)(递归栈的空间)

19.删除链表的倒数第N个节点

题目连接:19. 删除链表的倒数第 N 个结点 - 力扣(Leetcode)

遇到难点

第一眼看,删除第n个节点,这个真的简单,然后再一看发现是倒数第n个节点,没有很好的思路如何找到这个节点

题解

可以换个角度理解这个倒数n,比如以倒数第一个节点为开始,距离其为n的节点;以倒数第二个节点未开始,距离其为n的节点 …

不难发现,只要我们设置两个指针,保证其的距离为n时,当快指针到结尾的时候,慢指针指的地方就是倒数第n个节点

var removeNthFromEnd = function(head, n) {
  let dummyHead = new ListNode(0,head)
  let slow = dummyHead
  let fast = dummyHead
  while(n -- && fast != null){
    fast = fast.next
  }
  fast = fast.next
  while(fast != null){
    slow = slow.next
    fast = fast.next
  }
  slow.next = slow.next.next
  return dummyHead.next
}

时间复杂度:O(n)
空间复杂度:O(1)

这里需要注意的是,由于是要删除链表的某一个节点,因此应该找到他的上一个节点,将next指向被删节点的下一个节点即next.next,因此fast应该比slow的快n+1个节点

02.07. 链表相交

题目连接: 面试题 02.07. 链表相交 - 力扣(Leetcode)

遇到难点

嗯。。。。其实就是完全没得思路,不知道从哪里开始模拟,开始想到用哈希表存,如果一样的话,那是了,但是感觉十分的丑陋(但是确实后来发现思路没啥问题)

题解

1.哈希表
var getIntersectionNode = function(headA, headB) {
    const visited = new Set();
    let temp = headA;
    while (temp !== null) {
        visited.add(temp);
        temp = temp.next;
    }
    temp = headB;
    while (temp !== null) {
        if (visited.has(temp)) {
            return temp;
        }
        temp = temp.next;
    }
    return null;
}

时间复杂度:O(n+m)
空间复杂度:O(m)

2.双指针法

首先观察题目特性,如果有公共交点,那么两个链表的结尾一定是一模一样的;其次如果两个链表一样长,我们可以以此一个节点一个节点的判断,然后比较是否相同,但是问题再两个链表不一样长,但是基于第一条性质,我们发现长链表前面几个节点一定不会是焦点,如果我们把这些节点忽略,不就能使得两个链表一样长了?

话不多说见图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UzaKhyl8-1673790173789)(https://code-thinking.cdn.bcebos.com/pics/%E9%9D%A2%E8%AF%95%E9%A2%9802.07.%E9%93%BE%E8%A1%A8%E7%9B%B8%E4%BA%A4_2.png)]

那么需要做的就是,求出两个的长度,求出插值,给右侧对其,然后一起遍历

var getIntersectionNode = function(headA, headB) {
  let lenA = getLength(headA)
  let lenB = getLength(headB)
  // 保证A的长度大于B
  if(lenA < lenB){
    	[lenA, lenB] = [lenB, lenA];   // 这个分号必须要有,不然会被默认为 [lenA, lenB] = [headB, headA]
    	[headA, headB] = [headB, headA]
  }
  let i = lenA - lenB
  let curA = headA
  let curB = headB
  while(i --){
    curA = curA.next
  }
  while(curA != null){
    if(curA == curB){
      return curA
    }
    curA = curA.next
    curB = curB.next
  }
  return null
}

var getLength = function(head){
  let n = 0
  while(head != null){
    head = head.next
    n ++
  }
  return n
}

时间复杂度:O(n+m)
空间复杂度:O(1)

需要注意的是,对于我这种javascript不带分号的选手而言,需要特别主要,有些时候必须使用;

  • 小括号开头的语句, 前面需要分号
  • 中括号开头的语句, 前面需要分号

其次,对于题目中的判断相交节点应该注意是指针相同,而且不是数值相同

比如说测试用例一中

listA = [4, 1, 8, 4, 5]
listB = [5, 0, 1, 8, 4,5]

判定值为8而不是1,原因就是答案在判定是是否指向同一个节点,对于节点值为1的节点判断出来他们两个同时指向8,因此相交节点为8

142.环形链表II

题目连接: 142. 环形链表 II - 力扣(Leetcode)

遇到难点

思考题目时,单纯从编码解答模拟的角度思考了,对于这样的题目没有运用一定的数学推理去做,从而无法找到关键公式

题解

1. 分析过程

这个题目可以理解成初中(小学?)的跑步追击问题,大概就是小明跑的快,小红跑的慢那什么时候追上。

具体到题目而言,我们需要的是找到一个入口的位置,那么可以分析成两点去解析

  1. 有没有环?
  2. 环的入口在哪?

接下来以此用数学逻辑解决

1.有没有环

​ 这里其实就是刚刚说的追击问题,如果两个人跑步,一快一慢,一条直线上肯定不会相遇,如果是一段直线+一段圈呢?这不就相遇了?

因此,我们可以设置两个指针fast的速度是slow的两倍,如果能有相遇的位置,那就是有环,而且相遇点一定在环内,看个动态图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tsAxRIx2-1673790173790)(https://code-thinking.cdn.bcebos.com/gifs/141.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8.gif)]

2.环的入口在哪

首先如图:

对于slow 的路程为 x + y

对于fast的路程为x + n(y + z) ,其中n为圈数,而且肯定有n >= 1(想想跑步快的追慢的,追上了肯定起码多跑了一圈,除非慢的会时空穿梭(大雾))

所以可以得到等式2 (x + y) = x + n (y + z),由于我们需要求的是x因此我们转化一下可得x = n (y + z) - y

再给变一下 x = (n - 1)(y + z) + z

这表明,快慢指针相遇之后,将剩下的节点走完,则可得到入口结点;因此我们可以给两个index,当两个相等的时候判断为入口即可,具体可看代码

2.具体代码实现
var detectCycle = function(head) {
  let fast = slow =head
  while(fast != null && fast.next != null){
    slow = slow.next
    fast = fast.next.next
    if(slow == fast){
      let index1 = head
      let index2 = fast
      while(index1 != index2){
        index1 = index1.next
        index2 = index2.next
      }
      return index2
    }
  }
  return null
}

时间复杂度:O(n)
空间复杂度:O(1)

今日总结

  1. 学会将题目细致化为几个问题,然后一一解决判断
  2. 多画图多动手
  3. 当涉及到节点的增删的时候,可以添加一个虚拟节点来保证不用特别区别头部节点
  4. 链表题目经常会报空指针异常,因此在判断循环条件的时候,应该格外注意,比如为了不使node.next.next空指针,则应该判断node.next != null
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值