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

65 阅读5分钟

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

链表结点

public class ListNode {

    // 值
    int val;
    // 指针
    // 淡化指针
    // 指的下一个结点
    public ListNode next;

    ListNode() {
    }

    public ListNode(int val) {
        this.val = val;
    }

    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

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

解题思路(交换不明白,看了卡哥)

确实有难度

  1. 需要考虑链表中结点的个数
  2. 需要考虑从哪个位置操作结点
  3. 链表断了的位置,需要记录断掉的结点
  4. 如何链接链表
  5. 考虑从哪个位置操作下一个结点
  6. 如何从该循环结束
代码
public class SwapPairs {
    public ListNode solute(ListNode head) {
        // 虚拟结点
        ListNode dummy = new ListNode(-1, head);
        ListNode cur = dummy;

        // 偶数交换cur后两个不为空 奇数交换cur后一个不为空
        while (cur.next != null && cur.next.next != null) {
            // 提前存 链接会断的结点
            ListNode temp1 = cur.next;
            ListNode temp2 = cur.next.next.next;

            // 开始链接链表操作
            // 头换成2
            cur.next = cur.next.next;
            // 2链上1
            cur.next.next = temp1;
            // 1链上3
            temp1.next = temp2;

            // 将cur 移动到下次操作的位置
            cur = cur.next.next;
        }

        // 返回虚拟结点的next 则为新链表的head
        return dummy.next;
    }
}

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

解题思路一(自己想的笨办法)
  1. 首先获得链表的结点个数sz
  2. 遍历完后,需要将cur重新设置到head
  3. 判断n和sz的大小,如果等于sz=n就是首结点,如果n=1就是尾结点
  4. 排除以上可能性,开始考虑 该结点的上一个 也就是sz-n+1位 cur.next = cur.next.next
  5. 解决后,返回头节点
  6. 忘了还要考虑链表为空的情况
代码
public class RemoveNthFromEnd {

    public static void main(String[] args) {
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(2);
//        ListNode node3 = new ListNode(3);
//        ListNode node4 = new ListNode(4);
//        ListNode node5 = new ListNode(5);
//        ListNode node6 = new ListNode(6);
//        ListNode node7 = new ListNode(7);
        node1.next = node2;
//        node2.next = node3;
//        node3.next = node4;
//        node4.next = node5;
//        node5.next = node6;
//        node6.next = node7;

        System.out.println(removeNthFromEnd(node1, 2));
    }

    static ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(-1, head);
        ListNode cur = head;
        int sz = 0;
        ListNode temp1 = null;
        ListNode temp2 = null;

        if (head == null) return null;

        while (cur != null) {
            sz++;
            cur = cur.next;
        }

        cur = dummy.next;

        if (sz == n) {
            cur = cur.next;
            return cur;
        }

        for (int i = 1; i < sz - n + 1; i++) {
            if (i == sz - n) {
                temp1 = cur;
                temp2 = cur.next.next;
                break;
            }
            cur = cur.next;
        }


        if (temp1 == null) return null;
        temp1.next = temp2;

        return dummy.next;

    }

}
解法思路二 - 运用虚拟结点(录友提供)
  1. 计算链表结点的数量
  2. 使用头节点,可以有效处理掉,不知道头尾结点的后面和前面都是谁

总结 链表题目一定要多使用虚拟结点,结果题目的效果杠杠滴

代码
public class RemoveNthFromEnd1 {
    static ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode prehead = new ListNode(-1, head);
        ListNode cur = head;

        //计算节点的数量
        int size = 0;
        while (cur != null) {
            size++;
            cur = cur.next;
        }

        //找到要删除节点的前一个节点
        cur = prehead;
        for (int i = 0; i < size - n; i++) {
            cur = cur.next;
        }
        cur.next = cur.next.next;//删除目标节点

        return prehead.next;
    }
}
解题思路四(后续补充卡哥)

面试题 02.07. 链表相交

解题思路(卡哥)

双指针 永远的神 要明白什么是链表相交,是你两个同时指向同一个结点了,而不是结点的val相同 理解了之后可以开始写题啦

  1. 求两个链表交点的指针
  2. 设置curA curB 求出 各自的size
  3. 然后让链表尾对齐(或者可以通过反转链表 正对齐)
  4. 然后开始逐个比较节点,当节点不同,同时下一个节点,相同返回
  5. 没有最后return null
解题代码

让gpt帮我看了看,犯了很多小失误,都是复制粘贴的错

gpt也有出错的时候

public class GetIntersectionNode {
    static ListNode getIntersectionNode(ListNode headA, ListNode headB) {

        // 处理cur 本身,不改变链表的结构
        ListNode curA = headA;
        int szA = 0;
        //ListNode curB = headA;
        ListNode curB = headB; // 错误修正1
        int szB = 0;

        while (curA != null) {
            szA++;
            curA = curA.next;
        }

        while (curB != null) {
            //szA++;
            szB++; // 错误修正2
            curB = curB.next;
        }

        curA = new ListNode(-1, headA);
        curB = new ListNode(-1, headB);
//        curA = headA; // 错误修正3
//        curB = headB; // 错误修正4
        // 只能说gpt也有犯错的时候 错误修正3/4 糊涂呀 gpt
        // 边界条件的解决最好用虚拟头节点 好处多多
        // 不用再考虑头结点的处理问题了

        if (szA == szB) {
            while (curA != curB) {
                curA = curA.next;
                curB = curB.next;
                if (curA == curB) {
                    return curA;
                }
            }
        } else if (szA > szB) {
            for (int i = 0; i < szA - szB; i++) {
                curA = curA.next;
            }
            while (curA != curB) {
                curA = curA.next;
                curB = curB.next;
                if (curA == curB) {
                    return curA;
                }
            }
        } else {
            for (int i = 0; i < szB - szA; i++) {
                curB = curB.next;
            }
            while (curA != curB) {
                curA = curA.next;
                curB = curB.next;
                if (curA == curB) {
                    return curA;
                }
            }

        }

        return null;

    }
}

142.环形链表II

解题思路(直接学习卡哥)

理解下题目,应该是 有一个结点被指了两次

怎么找到被指了两次的结点

快慢指针 永远的神 这不就是追击问题

数学真的太美妙了 算法真的太美妙了

代码
public class DetectCycle {
    static ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {// 有环
                ListNode index1 = fast;
                ListNode index2 = head;
                // 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
                while (index1 != index2) {
                    index1 = index1.next;
                    index2 = index2.next;
                }
                return index1;
            }
        }
        return null;
    }
}
gpt总结

这个代码的逻辑是基于“快慢指针”或“双指针”技术的。存在两个指针,一个快指针(fast)一次移动两个节点,一个慢指针(slow)一次移动一个节点。

首先,快指针和慢指针从链表的头部开始移动。如果链表有环,那么快指针和慢指针最终会在环内的某个位置相遇。如果链表没有环,那么快指针会先到达链表的尾部,此时返回 null。

如果快慢指针在环内相遇了,我们现在需要找到环的起点。其中一个思想是,从头部和相遇点分别设定两个指针,每次各走一步,那么当这两个指针再次相遇时,相遇点就是环的起点。

这个想法的依据是数学定理:在快慢指针相遇后,头部节点到环的起点的距离等于当前相遇点到环的起点的距离。

因此,当 index1 和 index2 指针相遇时,该相遇点即为环的起点,返回 index1 或者 index2 即可。 如果 index1 和 index2 指针未能相遇,那么表示链表无环,返回 null。 这种算法的时间复杂度是 O(N),空间复杂度是 O(1),其中 N 是链表的长度。因此,此算法效率极高。