链表相关题目总结(含leetcode链表题Java代码)

链表是最常见的数据结构之一,下面主要总结题目,不涉及基本的数据结构的介绍

文章目录

    • 1. 常见的链表操作题目
      • 1.1 删除链表倒数第 n 个结点(leetcode 19)
      • 1.2 两个有序链表的合并(leetcode 21)
      • 1.3 链表中环的检测(leetcode 141)
      • 1.4 链表倒置(leetcode 206)
      • 1.5 链表的中间结点(leetcode 876)
    • 2. leetcode 其他相关题目
      • No.2 Add Two Numbers(两数相加)
      • No.24 Swap Nodes in Pairs(两两交换链表中的节点)
      • No.82 Remove Duplicates from Sorted List II(删除排序链表中的重复元素Ⅱ)
      • No.83 Remove Duplicates from Sorted List(删除排序链表中的重复元素)
      • No.86 Partition List(分隔链表)
      • No.92 Reverse Linked List II(倒置链表Ⅱ)
      • No.160 Intersection of Two Linked Lists(相交链表)
      • No.203 Remove Linked List Elements(移除链表元素)
      • No.234 Palindrome Linked List(回文链表)
      • No.237 Delete Node in a Linked List (删除链表中的节点)
      • No.328 Odd Even Linked List(奇偶链表)
      • No.445 Add Two Numbers II(两数相加Ⅱ)
      • No.817 Linked List Components(链表组件)
    • 3. 解链表题的一些心得
      • 3.1 哨兵
      • 3.2 分链解题
      • 3.3 测试用例

1. 常见的链表操作题目

1.1 删除链表倒数第 n 个结点(leetcode 19)

只遍历一次,保证输入的 n 是有效的,问如何删除倒数第 n 个结点

    // 思路:一快一慢两个节点进行遍历,快的结点比慢的结点先走 n 步
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 设置哨兵结点,处理涉及头结点的情况
        ListNode start = new ListNode(0);
        ListNode slow = start, fast = start;
        slow.next = head;
        // 快结点提前走 n + 1 步(包含了头结点)
        for (int i = 0; i <= n; i++)   {
            fast = fast.next;
        }
        // 找出倒数第 n 个结点
        while (fast != null) {
            slow = slow.next;
            fast = fast.next;
        }
        slow.next = slow.next.next;
        return start.next;
    }

1.2 两个有序链表的合并(leetcode 21)

确定初始节点,之后两条链表一边遍历一边比较跟赋值,直到一条链遍历完,将另一条链剩余的部分接在输出的链尾上

    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 对输入进行判断,如果有一边为空,则直接返回另一边
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        // output为输出时的头指针
        ListNode output = null;
        ListNode temp = null;
        ListNode cur = null;
        int val;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                val = l1.val;
                l1 = l1.next;
            } else {
                val = l2.val;
                l2 = l2.next;
            }
            temp = new ListNode(val);
            // 这里是对起始节点进行赋值,这块可以在循环开始前先进行判断
            if (output == null) {
                output = temp;
                cur = temp;
            } else {
                cur.next = temp;
                cur = cur.next;
            }
        }
        if (l1 != null) {
            cur.next = l1;
            cur = cur.next;
            l1 = l1.next;
        }
        if (l2 != null) {
            cur.next = l2;
            cur = cur.next;
            l2 = l2.next;
        }
        return output;
    }

1.3 链表中环的检测(leetcode 141)

Given a linked list, determine if it has a cycle in it.
Follow up:
Can you solve it without using extra space?

    // 用一快一慢两个结点进行遍历,快结点一次遍历两个结点,慢结点一次遍历一个结点
    // 如果有环存在的话,快结点无法遍历到链表的末尾,最终会与慢结点相遇
    public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        }
        ListNode fast = head;
        ListNode slow = head;
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                return true;
            }
        }
        return false;
    }

1.4 链表倒置(leetcode 206)

思路:先考虑如何改变链表中部的指向问题,之后考虑开头跟结尾的特殊情况

    public ListNode reverseList(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode pre = null;
        ListNode cur = head;
        ListNode next = head.next;
        // 先考虑中间部分的情况,再考虑开头跟结尾
        while (cur != null) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        head = pre;
        return head;
    }

1.5 链表的中间结点(leetcode 876)

Given a non-empty, singly linked list with head node head, return a middle node of linked list.
If there are two middle nodes, return the second middle node.

    public ListNode middleNode(ListNode head) {
        ListNode a = head;
        ListNode b = head;
        while (a != null && a.next != null) {
            a = a.next.next;
            b = b.next;
        }
        return b;
    }

2. leetcode 其他相关题目

代码 github 链接:https://github.com/Parallelline1996/-Algorithm/tree/master/Leetcode/src

No.2 Add Two Numbers(两数相加)

You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.
You may assume the two numbers do not contain any leading zero, except the number 0 itself.

  • Example:
    Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
    Output: 7 -> 0 -> 8
    Explanation: 342 + 465 = 807.
	/*
	思路:注意这里不能将两个数读出来再相加,因为涉及一个Integer类型,Long类型有最大值的问题
	注意这里链表是将数字进行倒置,所以刚好两个链表从头到尾遍历,就是两个数从末一位
	分别相加到最高位的过程,这里要注意相加过程中的进位问题。
	*/
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode output = null;
        ListNode head = null;
        int a, b, add = 0, total;
        while (l1 != null || l2 != null) {
            if (l1 != null) {
                a = l1.val;
                l1 = l1.next;
            } else {
                a = 0;
            }
            if (l2 != null) {
                b = l2.val;
                l2 = l2.next;
            } else {
                b = 0;
            }
            total = a + b + add;
            add = total / 10;
            if (output == null) {
                output = new ListNode(total % 10);
                head = output;
            } else {
                output.next = new ListNode(total % 10);
                output = output.next;
            }
        }
        // 可能最终相加的长度大于两条链中的最长链的长度
        if (add == 1) {
            output.next = new ListNode(add);
        }
        return head;
    }

No.24 Swap Nodes in Pairs(两两交换链表中的节点)

Given a linked list, swap every two adjacent nodes and return its head.

  • Example:
    Given 1->2->3->4, you should return the list as 2->1->4->3.
    Note:
  • Your algorithm should use only constant extra space.
    You may not modify the values in the list’s nodes, only nodes itself may be changed.
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode newHead = null;
        ListNode cur = head;
        ListNode pre = null;
        ListNode next = null;
        while (cur != null && cur.next != null) {
            if (newHead == null) {
                newHead = cur.next;
                pre = cur;
                cur = cur.next.next;
                newHead.next = pre;
                pre.next = cur;
                continue;
            }
            pre.next = cur.next;
            next = cur.next.next;
            pre.next.next = cur;
            pre = cur;
            cur.next = next;
            cur = next;
        }
        return newHead;
    }

这里如果用哨兵的思路,设置一个在前面的newHead,代码会简洁的多:

    public ListNode swapPairs_(ListNode head) {
        // 多用了一个节点做最前的节点,减少判断的问题
        ListNode dummy = new ListNode(0);
        ListNode prev = dummy;
        prev.next = head;
        while(prev.next != null && prev.next.next!= null) {
            // 创建第一个和第二个节点
            ListNode first = prev.next;
            ListNode second = prev.next.next;
            // 更改两个节点之间的指针
            first.next = second.next;
            second.next = first;
            prev.next = second;
            prev = first;
        }
        return dummy.next;
    }

No.82 Remove Duplicates from Sorted List II(删除排序链表中的重复元素Ⅱ)

Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numbers from the original list.

  • Example 1:
    Input: 1->2->3->3->4->4->5
    Output: 1->2->5
  • Example 2:
    Input: 1->1->1->2->3
    Output: 2->3
    /*
    用哨兵的策略,解决删除第一个元素的问题。
    */
    public ListNode deleteDuplicates(ListNode head) {
        ListNode newHead = new ListNode(0);
        newHead.next = head;
        ListNode pre = newHead;
        ListNode cur = null;
        // 标记时候需要删除操作
        boolean weatherNeedDelete = false;
        while (pre.next != null) {
            cur = pre.next;
            while (cur.next != null && cur.next.val == pre.next.val) {
                cur = cur.next;
                weatherNeedDelete = true;
            }
            // 发生删除操作,直接将中间重复的部分略过
            if (weatherNeedDelete) {
                pre.next = cur.next;
                weatherNeedDelete = false;
            } else {
                pre.next = cur;
                pre = pre.next;
            }
        }
        return newHead.next;
    }

No.83 Remove Duplicates from Sorted List(删除排序链表中的重复元素)

Given a sorted linked list, delete all duplicates such that each element appear only once.
Example 1:
Input: 1->1->2
Output: 1->2

    public ListNode deleteDuplicates(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode cur = head;
        ListNode next = cur.next;
        while (cur != null && next != null) {
            if (next.val == cur.val) {
                cur.next = next.next;
            } else {
                cur = cur.next;
            }
            next = next.next;
        }
        return head;
    }

No.86 Partition List(分隔链表)

Given a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x.
You should preserve the original relative order of the nodes in each of the two partitions.

  • Example:
    Input: head = 1->4->3->2->5->2, x = 3
    Output: 1->2->2->4->3->5
    // 思路:分成两条链,一条链的节点大于或等于给定值,一条链小于给点值,最后将两条链相连接
    public ListNode partition1(ListNode head, int x) {
        // 用于分别引用前后两段不同的链表
        ListNode list1 = new ListNode(0);
        ListNode list2 = new ListNode(0);
        ListNode l1 = list1;
        ListNode l2 = list2;
        // 遍历整个链表,将链表分为大于或等于给定值的链跟小于给定值的链
        while (head != null) {
            if (head.val >= x) {
                l2.next = head;
                l2 = l2.next;
            } else {
                l1.next = head;
                l1 = l1.next;
            }
            head = head.next;
        }
        // 将两条链链接起来,注意后面的链末尾要指向null
        l2.next = null;
        l1.next = list2.next;
        return list1.next;
    }

No.92 Reverse Linked List II(倒置链表Ⅱ)

Reverse a linked list from position m to n. Do it in one-pass.
Note: 1 ≤ m ≤ n ≤ length of list.

  • Example:
    Input: 1->2->3->4->5->NULL, m = 2, n = 4
    Output: 1->4->3->2->5->NULL
    /*
    思路:将整段分为三个部分:正序-逆序处理-正序
    因此,先遍历将两个正序部分的起始结点进行记录,然后将中间的部分按照常规的方法进行倒置
    这里注意一点,因为可能逆序的部分是从首结点开始,因此使用哨兵结点,会使操作简单
    */
    public ListNode reverseBetween(ListNode head, int m, int n) {
        if (head == null) {
            return null;
        }
        // 哨兵结点
        ListNode newHead = new ListNode(0);
        newHead.next = head;
        // 第一部分正序的尾结点
        ListNode endOfFirstPart = newHead;
        for (int i = 0; i < m - 1; i++) {
            endOfFirstPart = endOfFirstPart.next;
        }
        // 第二部分正序的头结点
        ListNode startOfThirdPart = endOfFirstPart;
        for (int i = 0; i < n - m + 2; i++) {
            startOfThirdPart = startOfThirdPart.next;
        }
        // 逆序
        ListNode pre = startOfThirdPart, cur = endOfFirstPart.next, next = null;
        for (int i = 0; i <= n - m; i++) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        endOfFirstPart.next = pre;
        return newHead.next;
    }

No.160 Intersection of Two Linked Lists(相交链表)

Write a program to find the node at which the intersection of two singly linked lists begins.

  • For example, the following two linked lists:
    A: a1 → a2

    c1 → c2 → c3

    B: b1 → b2 → b3
    begin to intersect at node c1.
  • Notes:
    If the two linked lists have no intersection at all, return null.
    The linked lists must retain their original structure after the function returns.
    You may assume there are no cycles anywhere in the entire linked structure.
    Your code should preferably run in O(n) time and use only O(1) memory.
	/*
	思路:由于要求不能修改链表的结构,因此思路可以:
	第一步,通过遍历,判断两个链表是否相交,并记录两条链表的长度lenA,lenB
	第二步,设置两个节点对两条链表再次进行遍历,但对于长链表的节点,先向前走|lenA-lenB|的长度,再进行同时遍历
	这样做的是因为两条链表的相交点,一定不是在长链表的开始部分,因此除去。而且此时由于两条链表最终会重合,
	而且两条链表现在长度相同,则第一个相交点,会在同一个长度位置上
	*/
    public static ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        // 遍历两个链表
        ListNode endA = headA;
        ListNode endB = headB;
        int lenA = 1, lenB = 1;
        while (endA.next != null) {
            endA = endA.next;
            lenA++;
        }
        while (endB.next != null) {
            endB = endB.next;
            lenB++;
        }
        // 先判断两个链表是否相交
        if (endA != endB) {
            return null;
        }
        endA = headA;
        endB = headB;
        // 使两条链表长度对齐
        int m = lenA - lenB;
        if (m > 0) {
            while (m > 0) {
                endA = endA.next;
                m--;
            }
        } else {
            while (m < 0) {
                endB = endB.next;
                m++;
            }
        }
        // 寻找交点
        while (endA != endB) {
            endA = endA.next;
            endB = endB.next;
        }
        return endA;
    }

No.203 Remove Linked List Elements(移除链表元素)

Remove all elements from a linked list of integers that have value val.

  • Example:
    Input: 1->2->6->3->4->5->6, val = 6
    Output: 1->2->3->4->5
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return null;
        }
        // 保证了第一个元素指向的对象的值一定不是 val
        while (head != null && head.val == val) {
            head = head.next;
        }
        ListNode output = head;
        while (head != null && head.next != null) {
            if (head.next.val == val) {
                head.next = head.next.next;
            } else {
                head = head.next;
            }
        }
        return output;
    }

No.234 Palindrome Linked List(回文链表)

Given a singly linked list, determine if it is a palindrome.

  • Example 1:
    Input: 1->2
    Output: false
  • Example 2:
    Input: 1->2->2->1
    Output: true
  • Follow up:
    Could you do it in O(n) time and O(1) space?
	/*
	思路:利用一快一慢两个节点进行遍历,将链表分为两个部分。
	然后将后面的链表进行倒置,再比较两节链表内容是否相同
	*/
    public boolean isPalindrome(ListNode head) {
        if (head == null) {
            return true;
        }
        // 一快一慢两个节点遍历
        ListNode fast = head, slow = head;
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        // 这里画图分奇数个节点跟偶数个节点的情况,就可以发现若该节点要成为后半段链表的起始节点,需要向前再遍历一位
        slow = slow.next;
        // 对后半段的链表进行倒置,该方法上面有提到过,这里不再贴代码
        slow = reverse(slow);
        // 注意这里要以后半段的长度进行遍历
        while (slow != null) {
            if (slow.val != head.val) {
                return false;
            }
            slow = slow.next;
            head = head.next;
        }
        return true;
    }

No.237 Delete Node in a Linked List (删除链表中的节点)

Write a function to delete a node (except the tail) in a singly linked list, given only access to that node.
这道题有点诡异。。大量被点踩。。看不懂。。

    public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }

No.328 Odd Even Linked List(奇偶链表)

Given a singly linked list, group all odd nodes together followed by the even nodes. Please note here we are talking about the node number and not the value in the nodes.
You should try to do it in place. The program should run in O(1) space complexity and O(nodes) time complexity.

  • Example 1:
    Input: 1->2->3->4->5->NULL
    Output: 1->3->5->2->4->NULL
  • Example 2:
    Input: 2->1->3->5->6->4->7->NULL
    Output: 2->3->6->7->1->5->4->NULL
  • Note:
    The relative order inside both the even and odd groups should remain as it was in the input.
    The first node is considered odd, the second node even and so on …
	/*
	思路:用一个节点来遍历奇数位上的节点,另一个节点遍历偶数位上的节点,获取一条奇数位的链,
	跟一条偶数位的链,最后把两条链相连
	*/
    public ListNode oddEvenList(ListNode head) {
        if (head == null) {
            return null;
        }
        // 奇数位链
        ListNode oddNode = head;
        // 偶数位链
        ListNode evenNode = head.next;
        // 偶数位链的开端
        ListNode evenListHead = evenNode;
        // 遍历整个链表
        while (evenNode != null && evenNode.next != null) {
            oddNode.next = evenNode.next;
            oddNode = oddNode.next;
            evenNode.next = oddNode.next;
            evenNode = evenNode.next;
        }
        // 在奇数链的末端链接上偶数链
        oddNode.next = evenListHead;
        return head;
    }

No.445 Add Two Numbers II(两数相加Ⅱ)

You are given two non-empty linked lists representing two non-negative integers. The most significant digit comes first and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.
You may assume the two numbers do not contain any leading zero, except the number 0 itself.
Follow up:
What if you cannot modify the input lists? In other words, reversing the lists is not allowed.

  • Example:
    Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
    Output: 7 -> 8 -> 0 -> 7
	/*
	思路:较快的做法,是直接将链表倒置,然后这道题就变成了Leetcode第二题一样的题型了
	这里我用的是三个栈,将链表的数据顺序进行倒置,再相加,将相加结果放入一个栈中,
	再倒置成要求输出的顺序。
	*/
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<Integer> stack1 = new Stack<>();
        Stack<Integer> stack2 = new Stack<>();
        Stack<Integer> outputStack = new Stack<>();
        int len1 = 0, len2 = 0;
        while (l1 != null) {
            stack1.add(l1.val);
            l1 = l1.next;
            len1++;
        }
        while (l2 != null) {
            stack2.add(l2.val);
            l2 = l2.next;
            len2++;
        }
        int n = Math.max(len1, len2);
        int a = 0, b = 0, add = 0, total = 0;
        ListNode output = null;
        for (int i = 0; i < n; i++) {
            a = (len1 > 0)? stack1.pop(): 0;
            b = (len2 > 0)? stack2.pop(): 0;
            len1--;
            len2--;
            total = a + b + add;
            add = total / 10;
            outputStack.add(total % 10);
        }
        if (add == 1) {
            outputStack.add(add);
        }
        output = new ListNode(outputStack.pop());
        ListNode temp = output;
        while (!outputStack.isEmpty()) {
            temp.next = new ListNode(outputStack.pop());
            temp = temp.next;
        }
        return output;
    }

No.817 Linked List Components(链表组件)

We are given head, the head node of a linked list containing unique integer values.
We are also given the list G, a subset of the values in the linked list.
Return the number of connected components in G, where two values are connected if they appear consecutively in the linked list.

  • Example 1:
    Input:
    head: 0->1->2->3
    G = [0, 1, 3]
    Output: 2
    Explanation:
    0 and 1 are connected, so [0, 1] and [3] are the two connected components.
  • Example 2:
    Input:
    head: 0->1->2->3->4
    G = [0, 3, 1, 4]
    Output: 2
    Explanation:
    0 and 1 are connected, 3 and 4 are connected, so [0, 1] and [3, 4] are the two connected components.
    Note:

If N is the length of the linked list given by head, 1 <= N <= 10000.
The value of each node in the linked list will be in the range [0, N - 1].
1 <= G.length <= 10000.
G is a subset of all values in the linked list.

    // 利用题干中最下面的四个条件进行解答。利用空间换时间
    public int numComponents1(ListNode head, int[] G) {
        if (head == null) {
            return 0;
        }
        boolean[] num = new boolean[10001];
        for (int i : G) {
            num[i] = true;
        }
        int number = 0;
        boolean isContain = false;
        while (head != null) {
            if (num[head.val]) {
                if (!isContain) {
                    isContain = true;
                }
            } else {
                if (isContain) {
                    number++;
                    isContain = false;
                }
            }
            head = head.next;
        }
        if (isContain) {
            number++;
        }
        return number;
    }

3. 解链表题的一些心得

3.1 哨兵

如果我们引入哨兵结点,在任何时候,不管链表是不是空,head结点都会一直指向这个哨兵结点。我们也把这种有哨兵结点的链表叫做带头链表。
这种做法多了一个节点的开销,但对于一些会操作到head结点的题目,会方便很多。例如上面的删除倒数第N个元素,倒置链表Ⅱ等。

3.2 分链解题

例如上面的题目,No.86 Partition List(分隔链表),No.328 Odd Even Linked List(奇偶链表),可以考虑设置两个链表,将原有链表分为两个各自满足一定条件的链表,最终再将链表进行结合。

3.3 测试用例

在解答过程中,我一般考虑一下的边界条件:

  • 链表为空
  • 链表中只有一个结点
  • 链表中只包含两个结点
  • 代码在处理头结点跟尾结点是否存在什么问题

你可能感兴趣的:(算法,LeetCode)