关于链表的若干问题


节选自:https://leetcode.cn/studyplan/top-100-liked/

链表反转

初始化 precur 分别指向 null 和头节点。

image-20231113183500736

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur = head, pre = null;
        while(cur != null) {
            ListNode tmp = cur.next; // 暂存后继节点 cur.next
            cur.next = pre;          // 修改 next 引用指向
            pre = cur;               // pre 暂存 cur
            cur = tmp;               // cur 访问下一节点
        }
        return pre;
    }
}

相交链表

  1. 指针 pA 指向 A 链表,指针 pB 指向 B 链表,依次往后遍历

  2. 如果 pA 到了末尾,则 pA = headB 继续遍历

  3. 如果 pB 到了末尾,则 pB = headA 继续遍历
  4. 比较长的链表指针指向较短链表head时,长度差就消除了
  5. 如此,只需要将最短链表遍历两次即可找到位置

有相交时A:a+c,B:b+c,且满足等式 a+c+b+c=b+c+a+c,因此他们走了 a+b+c 之后在 c 处相遇;

无相交时A:a+c1,B:b+c2c1c2 仅仅结点数量相同,因此 a+c1+b+c2=b+c2+a+c1,且a+c1+b=b+c2+a;但是接下来一个走c1一个走c2,距离 NULL 距离相同但是并非相交。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) return null;
        ListNode pA = headA, pB = headB;
        while (pA != pB) {
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }
        return pA;
    }
}

回文链表

可以直接用以下步骤完成本题:

  1. 复制链表值到数组列表中。
  2. 使用双指针法判断是否为回文。

但可以使用快慢指针+反转链表的方法实现空间复杂度 $O(1)$ :

  1. 找到前半部分链表的尾节点。
  2. 反转后半部分链表。
  3. 判断是否回文。
  4. 恢复链表。
  5. 返回结果。
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head == null)    return true;

        // 找到前半部分的尾结点
        ListNode fhalf = endOfFirstHalf(head);
        ListNode sstart = reverseList(fhalf.next);

        // 判断是否回文
        ListNode p1 = head;
        ListNode p2 = sstart;
        boolean result = true;
        while(result && p2 != null)
        {
            if(p1.val != p2.val)    result = false;
            p1 = p1.next; p2 = p2.next;
        }

        // 还原链表
        fhalf.next = reverseList(sstart);
        return result;
    }

    public ListNode reverseList(ListNode head)
    {
        ListNode pre = null;    ListNode cur = head;
        while(cur != null)
        {
            ListNode tmp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = tmp;
        }

        return pre;
    }

    public ListNode endOfFirstHalf(ListNode head)
    {
        ListNode fast = head;
        ListNode slow = head;
        while(fast.next != null && fast.next.next != null)
        {
            fast = fast.next.next;
            slow = slow.next;
        }

        return slow;
    }
}

合并两个有序链表

递归思想

  • 终止条件:当两个链表都为空时,表示我们对链表已合并完成。

  • 如何递归:我们判断 l1l2 头结点哪个更小,然后较小结点的 next 指针指向其余结点的合并结果。(调用递归)

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

image-20231115225201165

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        }
        else if (l2 == null) {
            return l1;
        }
        else if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        }
        else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

两数相加

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head = new ListNode(0);
        ListNode cur = head;
        int carry = 0;
        while(l1 != null || l2 != null)
        {
            int x = l1 == null ? 0 : l1.val;
            int y = l2 == null ? 0 : l2.val;
            int sum = x + y + carry;

            carry = sum / 10;
            sum = sum % 10;
            cur.next = new ListNode(sum);

            cur = cur.next;
            if(l1 != null)  l1 = l1.next;
            if(l2 != null)  l2 = l2.next;
        }

        if(carry == 1)  cur.next = new ListNode(carry);
        return head.next;
    }
}

两两交换链表中的节点

递归确定的有以下三个条件:

  • 返回值:交换完成的子链表
  • 调用单元:设需要交换的两个点为 head 和 next,head 连接后面交换完成的子链表,next 连接 head,完成交换
  • 终止条件:head 为空指针或者 next 为空指针,也就是当前无节点或者只有一个节点,无法进行交换
class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        ListNode next = head.next;
        head.next = swapPairs(next.next);
        next.next = head;
        return next;
    }
}

排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

class ListNode {
    int val;
    ListNode next;
    
    // 链表节点类的构造函数
    ListNode(int x) {
        val = x;
    }
}

public class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        
        // 使用快慢指针找到链表中点
        ListNode slow = head, fast = head, pre = null;
        while (fast != null && fast.next != null) {
            pre = slow;         // pre指向slow的前一个节点,用于将链表从中点处断开
            slow = slow.next;   // 慢指针每次前进一步
            fast = fast.next.next;  // 快指针每次前进两步
        }
        pre.next = null;  // 将链表从中点处断开
        
        ListNode l1 = sortList(head);  // 对前半部分链表排序
        ListNode l2 = sortList(slow);   // 对后半部分链表排序
        
        return merge(l1, l2);  // 合并两个有序链表
    }
    
    // 合并两个有序链表的方法
    private ListNode merge(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);  // 创建一个虚拟头节点作为合并后链表的起始节点
        ListNode cur = dummy;  // cur指向当前合并后链表的最后一个节点
        
        // 比较l1和l2中的元素,并依次将较小的元素加入到合并后链表中
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                cur.next = l1;
                l1 = l1.next;
            } else {
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        
        // 当其中一个链表为空时,直接将另一个链表连接到合并后链表的末尾
        if (l1 != null) {
            cur.next = l1;
        }
        if (l2 != null) {
            cur.next = l2;
        }
        
        return dummy.next;  // 返回合并后链表的头节点(注意:虚拟头节点的下一个节点才是真正的头节点)
    }
}

文章作者: ShiQuLiZhi
版权声明: 本博客所有文章除特别声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ShiQuLiZhi !
评论
  目录