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

155 阅读7分钟

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

题目
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

思路

  1. 首先创建一个虚拟头节点dummyNode值为0
  2. 声明一个指针cur指向真实头节点,即dummyNode.next
  3. 声明一个临时指针tmp
  4. 声明一个指针prev指向真实头节点,记录cur移动过程中的前一个节点
  5. 判断当前链表长度如果小于2,则返回整个链表
  6. 声明判断条件为cur的值的循环
    1. 首先判断如果cur.next为null则跳出循环,用于匹配奇数链表的最后一个值
    2. 将cur.next赋值给临时指针tmp
    3. 将cur.next指向tmp.next
    4. 将tmp.next指向cur
    5. 如果虚拟头节点的next是cur,说明此时是第一次交换,要将虚拟头节点额外处理,指向目前tmp的节点
    6. 如果prev.next此时指向cur,说明不是第一次交换,每次交换后cur.next上一次指向的节点此次交换后位置更换,所以要将prev.next指向tmp,cur赋值给prev
    7. 最后将cur向后移一位
  7. 返回虚拟头节点的next,即变换后的链表。

代码

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var swapPairs = function(head) {
    let dummyNode = new ListNode(0,head)
    let cur = dummyNode.next
    if (!cur||!cur.next) return head
    let tmp,prev = cur
    while (cur) {
        if (!cur.next) break
        tmp = cur.next
        cur.next = tmp.next
        tmp.next = cur
        if (dummyNode.next===cur) {
            dummyNode.next = tmp
        }
        if (prev.next === cur) {
            prev.next =tmp
            prev = cur
        }
        cur = cur.next
    }
    return dummyNode.next
};

优化:

看了代码随想录发现自己思路没问题,但是代码逻辑写的非常复杂冗余,理解了优化后的代码后自己再写一遍。

代码

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var swapPairs = function(head) {
    let dummyNode = new ListNode(0,head)
    let cur = dummyNode
    while (cur.next&&cur.next.next) {
        let tmp = cur.next.next,pre = cur.next
        pre.next = tmp.next
        tmp.next = pre
        cur.next = tmp
        cur = pre
    }
    return dummyNode.next
};

总结:

这道题第一次提交是我独立写出来的,虽然逻辑复杂,但是功能实现,重要的是进一步简化逻辑的思想。

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

题目
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

思路

  1. 声明一个虚拟头节点
  2. 声明一个指针cur指向虚拟头节点
  3. 进入判断条件为cur.next不为null的循环
    1. 设置一个索引index保存n的大小
    2. 设置一个临时指针tmp指向cur.next
    3. 进入判断条件为index大于0的循环,每次判断后index--,目的是判断当前的cur.next是否是我们要删除的值
      1. 将tmp指向tmp.next
      2. 判断如果此时tmp为null,并且index为0,此时这cur.next是我们要删除的值,直接将其移除,返回操作后的链表
  4. cur指向cur.next

代码

/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    let dummyNode = new ListNode(0,head) 
    let cur = dummyNode
    while (cur.next) {
        let index = n
        let tmp = cur.next
        while (index-- >0) {
            tmp = tmp.next
            if (!tmp&&!index) {
                cur.next = cur.next.next
                return dummyNode.next
            }
        }
        cur = cur.next
    }
};

优化:

看了代码随想录的双指针解法我觉得自己就是傻呗..其实我自己的思想从某种程度上来说也是一种双指针?ok学习了思路以后再重新写一遍。

代码

/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    let slow,fast
    let dummyNode = new ListNode(0,head)
    slow = fast = dummyNode
    while (n-- >=0){
        fast = fast.next
    }
    while (true) {
        if (!fast) {
            slow.next = slow.next.next
            return dummyNode.next
        }
        slow = slow.next
        fast = fast.next
    }
};

总结:

虽然自己思考这个解法逻辑,思考了很久很久,但是这次我思考出来的逻辑并不太复杂,总归也是自己做出来了,但是看到优化后的双指针解法以后一下就明白了,而且这个思路非常清晰,希望自己未来也可以看到题目就有非常清晰的思路。

面试题02.07. 链表相交

题目
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。 图示两个链表在节点 c1 开始相交: ![[Pasted image 20230610170855.png]] 题目数据 保证 整个链式结构中不存在环。 注意,函数返回结果后,链表必须 保持其原始结构 。

思路
设置两个循环,进行比较,时间复杂度On2

  1. 判断如果A或者B链表为null,直接返回null
  2. 声明一个指针curA指向链表A
  3. 进入判断条件为curA的循环
    1. 声明一个指针curB指向链表B
    2. 进入判断条件为curB的循环
      1. 判断如果curA与curB在目前这个节点相交,则返回任意一个链表的当前节点
      2. curB指向curB.next
    3. curA指向curA.next
  4. 最后返回null

代码

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
    if (!headA||!headB) return null
    let curA = headA
    
    while (curA) {
        let curB = headB
        while (curB) {
            if (curB===curA) {
                return curA
            }
            curB = curB.next
        }
        curA = curA.next
    }
    return null
};

优化:

看了代码随想录的思路,绝了,吗的我怎么想不到,我还一直在想时间复杂度On怎么用双指针的方式,思路是把较短的链表与较长的链表末尾对齐,然后就可以从短链表的开头进行同步比较。

代码

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
    if (!headA||!headB) return null
    let curA = headA, curB = headB,lenA = 0,lenB = 0
    while (curA.next) {
        lenA++
        curA = curA.next
    }
    curA = headA
    while (curB.next) {
        lenB++
        curB = curB.next
    }
    curB = headB
    if (lenA >= lenB) {
        let dev = lenA-lenB
        return getRes(curA,curB,dev)
    }else {
        let dev = lenB-lenA
        return getRes(curB,curA,dev)
    }
    return null
};

const getRes = (curA,curB,dev) =>{
        while (dev-- >0) {
            curA = curA.next
        }
        while (true) {
            if (curA === curB) {
                return curA
            }
            curA = curA.next
            curB = curB.next
        }
}

总结:

自己的编程思维还需要多培养。

142.环形链表II

题目
给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

思路
想不明白。

代码
写不出来。

优化:

看了代码随想录的思路:

  1. 设置两个快慢指针,快指针每次走两步,慢指针每次走一步,设置一个循环,如果两个指针相遇,说明一定存在环形链表。
  2. 如果存在环形链表,接下来的任务就是找到环形链表的入口,这里涉及到数学问题。
    1. 设链表开头到环形入口的距离x,慢指针在环形内走过的距离为y,慢指针距离下次走到环形入口的距离为z,那么慢指针走过的路程为x+y,快指针走过的路程为x+y+n(y+z)
    2. 快指针的速度是慢指针的两倍,时间相等,所以(x+y)*2 = x+y+n(y+z)
    3. 化简:x=(n-1)(y+z) + z
    4. 无论n为几,最后相遇点一定是环形链表的入口

代码

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function(head) {
    let slow,fast
    slow = fast = head
    while (fast&&fast.next) {
        slow = slow.next
        fast = fast.next.next
        if (slow === fast) {
            let indexS = slow,indexF = head
            while (indexF!==indexS) {
                indexF = indexF.next
                indexS = indexS.next
            }
            return indexF
        }
    }
    return null
};

总结:

看解析之前感觉很难,两个关键点——判断是否存在环形,环形入口在哪完全想不明白,看完解析以后又觉得就这?希望自己不仅学习这个题解之后也能学到这种解题思路。

Day4总结

今天做的题是在昨天初步认识链表的基础上去完成的,在昨天对链表一窍不通的情况下去看题解做题后,今天对链表已经有了认识,前三题都是在自己没看题解的情况下做出来,无论是否是最优解,这都是进步的表现,但是缺少的还是数据结构的思想,希望自己再接再厉。