[Day4]代码随想录

53 阅读5分钟

今日内容:24.两两交换链表中的节点,19.删除链表的倒数第N个节点,面试题02.07.链表相交,142.环形链表||,总结
代码随想录链接:代码随想录 (programmercarl.com)

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

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

普通解法

  • 添加一个虚拟头节点方便第一次交换。
        ListNode dummy =  null; 
        dummy.next = head;//创建虚拟头节点
  • 前提是存在两个非空的节点可以交换。
while (cur.next != null && cur.next.next != null)
  • 在保证链表不断的情况下修改各个节点的next,以实现交换。

    • 先保存原来的点位
    temp = cur.next.next.next;
    firstNode = cur.next;
    secondNode = cur.next.nxet;
    
    • 再更改指针
    cur.next = secondNode;
    secondnode.next = firstnode;
    firstnode.next = temp;
    cur = firsrnode;//移动至下一轮
    

总代码如下:

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummy =  new ListNode(-1); 
        dummy.next = head;//创建虚拟头节点
        ListNode cur = dummy;
        ListNode temp ;
        ListNode firstnode;
        ListNode secondnode;
        while (cur.next != null && cur.next.next != null){
            temp = cur.next.next.next;
            firstnode = cur.next;
            secondnode = cur.next.next;
            cur.next = secondnode;
            secondnode.next = firstnode;
            firstnode.next = temp;
            cur = firstnode;
        }
        return dummy.next;
    }
}

递归解法

用递归函数去返回每一组节点的头节点,代码如下:

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        ListNode next = head.next;
        ListNode newNode = swapPairs(next.next);
        next.next = head;
        head.next = newNode;
        return next;
    }
}

注意:(head == null || head.next == null)中的两个判定条件的顺序不能改!

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

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

最粗暴的就是计算出size,然后找到那个节点,代码如下:

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode ();
        dummy.next = head;
        ListNode cur = dummy;
        int size = 0;
        while(cur.next != null){
            cur = cur.next;
            size++;
        }
        cur = dummy;
        for(int i = 0; i < size - n; i++) {
            cur = cur.next;
        }
        cur.next = cur.next.next;
        return dummy.next;
    }
}

这里创建了一个虚拟头节点dummy,比较直观的模拟了这样一个过程
时间复杂度O(n),空间复杂度O(1)

快慢指针

然后思考了一种双指针写法,一个指针在前,另一个指针在后,两个指针中间相隔n+1个距离,最开始快指针先跑,跑一步然后n--,当n减到0后,两个指针一起跑,直到快指针跑到头,这个时候让左指针删掉下一个节点即可。

代码如下:

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode ();
        dummy.next = head;
        ListNode left = dummy;
        ListNode right = new ListNode();
        right = dummy;
        while(right.next != null ){
            right = right.next;
            n--;
            if(n < 0 ){
                left = left.next;
            }
        }
        left.next = left.next.next;
        return dummy.next;
    }
}

时间复杂度O(n),空间复杂度O(1) 这次竟然一遍过了啊啊啊啊,激动的嘞,虚拟头节点确实好用,临界条件的判断舒服了好多。 这题应该算比较简单,能够自己写出两个方法并跑通。

面试题 02.07.链表相交

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

因为这题是找交点,所以如果有交点,那么这两个列表在尾部会有一段是相同的,那么只要先把这两段“对齐”,就可以找到交点。
如何对齐呢?我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置,如图:

面试题02.07.链表相交_2

代码如下:

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA = headA;
        ListNode curB = headB;
        int lenA = 0, lenB = 0;
        while(curA != null){
            curA = curA.next;
            lenA++;
        } 
        while(curB != null){
            curB = curB.next;
            lenB++;
        } 
        curA = headA;
        curB = headB;
       if (lenB > lenA) {
            int tmpLen = lenA;
            lenA = lenB;
            lenB = tmpLen;
            ListNode tmpNode = curA;
            curA = curB;
            curB = tmpNode;
        }
        int n = lenA - lenB;
        while(n-- > 0){
            curA = curA.next;
        }
        while (curA != null) {
            if (curA == curB) {
                return curA;
            }
            curA = curA.next;
            curB = curB.next;
        }
        return null;
    }
}
  1. 求两个链表的长度
  2. 找到长的链表设置为A链表,短的为B链表
  3. “对齐”:将长链表指针移动到和锻炼表一样的位置
  4. 一起移动两个指针,找到节点相同的点然后返回该点,若没有则返回null

就是中间这一段选出长的列表作为A链表,短的链表作为B链表,有没有更好的写法。

环形链表 ||

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

看到了快慢指针法,让有环的话就会在环中相遇,尝试写一下代码:

卡在了数学,不过看懂了! 代码如下:

public class Solution {
    public 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;    
    }
}

让slow碰到fast能证明有环,通过数学公式x = (n-1)*(z+y)+z ,如图所示:

可以得到结论:从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点


结束,今天的学习有全靠自己撸出来的代码,也有全靠答案的,最后一个环形链表真的精彩绝伦,拍案叫好。爽!