链表 -- LeetCode 刷题记录

214 阅读3分钟

参考:CS-Notes

1. 相交链表

(1)双指针

/**
 * 设 c 为相交的部分
 * headA = a + c,headB = b + c
 * a + c + b = b + c + a
 * 所以访问 a 之后继续访问 b,另一条链表访问 b 之后继续访问 a
 * 这样它们就能同时到达交点了
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode l1, l2;

        l1 = headA;
        l2 = headB;

        while (l1 != l2) {
            l1 = (l1 != null) ? l1. next : headB;
            l2 = (l2 != null) ? l2.next : headA;
        }

        return l1;
    }
}

(2)通过链表长度判断

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }

        // 1 遍历两个链表找到每个链表的最后一个节点
        // 同时计算其链表长度
        ListNode tempA = headA;
        ListNode tempB = headB;
        int countA = 1;
        int countB = 1;

        while (tempA.next != null) {
            countA++;
            tempA = tempA.next;
        }
        while (tempB.next != null) {
            countB++;
            tempB = tempB.next;
        }

        // 2 判断两个链表的最后一个节点是否相等
        // 不相等说明两个链表必不可能相交
        if (tempA != tempB) {
            return null;
        }

        // 3 同时回到链表头部,两个链表长度大的先走
        // 走的步数为两个链表长度的差值
        tempA = headA;
        tempB = headB;
        if (countA > countB) {
            int count = countA - countB;
            while (count-- != 0) {
                tempA = tempA.next;
            }
        }
        if (countB > countA) {
            int count = countB - countA;
            while (count-- != 0) {
                tempB = tempB.next;
            }
        }

        // 第一个相交节点就是环的入口
        while (tempA != tempB) {
            tempA = tempA.next;
            tempB = tempB.next;
        }

        return tempA;
    }
}

2. 反转链表

  • 递归法
class Solution {
    public ListNode reverseList(ListNode head) {
        // 结束条件
        if (head == null || head.next == null) {
            return head;
        }

        // 思想就是看成两个节点,后一个节点的 next 指向前一个节点
        // 前一个节点的 next 指向 null,完成链表反转
        ListNode next = head.next;
        ListNode newHead = reverseList(next);
        next.next = head;
        head.next = null;

        return newHead;
    }
}
  • 头插法
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head;
        // 修改指向的节点
        ListNode temp = head;
        // 保存 temp 下一个节点
        ListNode next = null;
        // 新链表的头节点
        ListNode newHead = null;

        // 思想就是先保存下一个节点
        // 再把当前节点的 next 指向新链表
        // 然后再把新链表的 head 指向当前节点
        // 之后再后移一个节点
        while (temp != null) {
            // 保存下一个节点,避免指向丢失
            next = temp.next;
            temp.next = newHead;
            newHead = temp;
            temp = next;
        }

        return newHead;
    }
}

3. 合并两个有序链表

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 考虑特殊情况
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }

        // 如果 l1 的第一个节点的值小于 l2 第一个节点的值
        // 就把 l1 的第一个节点加入到新链表中,同时后移一个节点
        if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

4. 删除排序链表中的重复元素

(1)暴力

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

        ListNode newHead = head;
        ListNode temp = newHead;
        head = head.next;

        while (head != null) {
            if (temp.val != head.val) {
                temp.next = head;
                temp = temp.next;
            }
            head = head.next;
        }

        temp.next = null;

        return newHead;
    }
}

(2)递归

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        // 终止条件
        if (head == null || head.next == null) {
            return head;
        }

        head.next = deleteDuplicates(head.next);
        
        return ((head.val) == (head.next.val)) ? head.next : head;
    }
}

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

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 快慢指针
        ListNode fast = head;
        ListNode slow = head;

        while (n-- != 0) {
            fast = fast.next;
        }

        // 说明要删除第一个节点
        if (fast == null) {
            return head.next;
        }

        while(fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }

        slow.next = slow.next.next;

        return head;
    }
}

6.返回倒数第 k 个节点

class Solution {
    public int kthToLast(ListNode head, int k) {
        ListNode fast = head;
        
        while (k-- != 0) {
            fast = fast.next;
        }

        if (fast == null) {
            return head.val;
        }

        ListNode slow = head;

        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }

        return slow.next.val;
    }
}

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

class Solution {
    public ListNode swapPairs(ListNode head) {
        // 1 结束条件
        // 当链表为 null 时或者只剩一个链表时结束
        if (head == null || head.next == null) {
            return head;
        }
        // 2 本级递归应该做什么
        // 交换两个链表节点的值
        ListNode next = head.next;
        head.next = swapPairs(next.next);
        next.next = head;

        // 返回值
        // 已经交换好了的链表
        return next;
    }
}

8. 两数相加 II

class Solution { 
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        // 因为是逆序计算,所以使用栈来计算最为方便
        Stack<Integer> l1Stack = build(l1);
        Stack<Integer> l2Stack = build(l2);

        // 新链表的头指针
        ListNode head = new ListNode();
        // 要进的位数,例如 9 + 3 = 12,carry = 12 % 10 = 2
        int carry = 0;
        // 当 l1 和 l2 的栈为空,同时没有要进的位数就跳出循环
        // 构造链表
        while (!l1Stack.isEmpty() || !l2Stack.isEmpty() || carry != 0) {
            // 栈不为空才取值,为空就给 0,因为是加法
            Integer a = l1Stack.isEmpty() ? 0 : l1Stack.pop();
            Integer b = l2Stack.isEmpty() ? 0 : l2Stack.pop();

            Integer sum = a + b + carry;
            carry = sum / 10;

            // 构造一个新节点
            ListNode node = new ListNode(sum % 10);
            // 加入到链表中
            node.next = head.next;
            head.next = node;
        }

        return head.next;
    }


    /**
     * 将链表压入栈中
     */ 
    public Stack<Integer> build(ListNode l) {
        Stack<Integer> stack = new Stack<>();

        while (l != null) {
            stack.push(l.val);
            l = l.next;
        }

        return stack;
    }
}

9. 回文链表

class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) {
            return true;
        }

        // 快慢指针,走的过程中同时反转
        ListNode slow = head;
        ListNode fast = head;
        ListNode pre = slow;
        ListNode prepre = null;

        // 如果是奇数个节点,那么 fast != null 就会跳出循环
        // 如果是偶数个节点,那么 fast 为 null 时才会跳出循环
        while (fast != null && fast.next != null) {
            pre = slow;
            slow = slow.next;
            fast = fast.next.next;
            pre.next = prepre;
            prepre = pre;
        }

        if (fast != null) {
            slow = slow.next;
        }

        // 进行比较
        while (pre != null && slow != null) {
            if (pre.val != slow.val) {
                return false;
            }
            pre = pre.next;
            slow = slow.next;
        }

        return true;
    }
}

10. 分隔链表

class Solution {
    public ListNode[] splitListToParts(ListNode head, int k) {
        // 计算出链表的长度
        int N = 0;
        ListNode cur = head;
        while (cur != null) {
            N++;
            cur = cur.next;
        }

        // 计算出每段的长度以及多余的长度
        int size = N / k;
        int mod = N % k;
        
        // 多余的长度的处理:分别加在从第一段开始的每一段
        // 没有多余就不用加了

        // 定义要返回的链表数组
        ListNode[] result = new ListNode[k];
        cur = head;

        for (int i = 0; cur != null && i < k; i++) {
            result[i] = cur;

            // 找到合适长度,然后截断
            int curSize = size + (mod-- > 0 ? 1 : 0);
            for (int j = 0; j < curSize - 1; j++) {
                cur = cur.next;
            }
            ListNode next = cur.next;
            cur.next = null;
            cur = next;
        }

        return result;
    }
}

11. 奇偶链表

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

        // 思想就是:把奇偶节点分离出来,组成两段链表
        // 最后再把偶链表的头节点连在奇链表的尾节点就行
        ListNode old = head;
        // 偶链表的头
        ListNode evenHead = head.next;
        ListNode even = evenHead;
        
        // 进行分离
        while (even != null && even.next != null) {
            old.next = old.next.next;
            old = old.next;

            even.next = even.next.next;
            even = even.next;
        }

        // 两段链表的连接
        old.next = evenHead;

        return head;
    }
}

12. 环形链表

public class Solution {
    public boolean hasCycle(ListNode head) {
       ListNode slow = head;
       ListNode fast = head;

       if (fast != null && fast.next != null) {
           slow = slow.next;
           fast = fast.next.next;

           if (slow == fast) {
               return true;
           }
       }

       return false;
    }
}

13. 环形链表 II

(1)使用额外空间

public class Solution {
    // 利用哈希表遍历存储,当节点重复了说明就是环的入口
    public ListNode detectCycle(ListNode head) {
        Set<ListNode> set = new HashSet<>();

        while (head != null) {
            if (set.contains(head)) {
                return head;
            }
            set.add(head);
            head = head.next;
        }

        return null;
    }
}

(2)不使用额外空间(快慢指针)

public class Solution {
    // 使用快慢指针
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;

        // 1 找到第一次快慢指针相遇的位置
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;

            if (slow == fast) {
                break;
            }
        }

        if (fast == null || fast.next == null) {
            return null;
        }

        // 2 快指针指向链表头部,并且一次走一步
        fast = head;

        // 3 当快慢指针再次相遇的位置即为环的入口
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }

        return fast;
    }
}