算法学习-2、链表学习
算法 算法 25

这两天复习了链表,写了十几道链表的算法题

1、61. 旋转链表

2、19. 删除链表的倒数第 N 个结点

3、143. 重排链表

4、142. 环形链表 II

5、32进制的两个链表加法

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

7、链表升序

8、146. LRU 缓存

9、约瑟夫环

10、21. 合并两个有序链表

11、LCR 024. 反转链表

12、25. K 个一组翻转链表

13、82. 删除排序链表中的重复元素 II

1、61. 旋转链表

    public static ListNode shiftLinkedListRightByK(ListNode head, int k) {
        if (head == null || head.next == null || k == 0) {
            return head;
        }

        // 计算长度并找到尾节点
        int length = 1;
        ListNode tail = head;
        while (tail.next != null) {
            tail = tail.next;
            length++;
        }

        k = k % length;
        if (k == 0) {
            return head;
        }

        // 使用快慢指针找到新的尾节点
        ListNode slow = head;
        ListNode fast = head;
        for (int i = 0; i < k; i++) {
            fast = fast.next;
        }

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

        // 重组链表
        ListNode newHead = slow.next;
        slow.next = null;
        tail.next = head;

        return newHead;
    }

2、19. 删除链表的倒数第 N 个结点

   public static ListNode removeNthNodeFromEnd(ListNode head, int n) {
        // 链表长度为0的特殊场景
        if (head == null) {
            return head;
        }
        // 链表长度为1的特殊场景
        if(head.next == null && n == 1) {
            return null;
        }

        ListNode fast = head;
        ListNode slow = head;

        for (int i = 0; i < n; i++) {
            fast = fast.next;
        }
        // 链表长度为2的情况
        if(fast == null) {
            return slow.next;
        }

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

        slow.next = slow.next.next;

        return head;
    }

3、143. 重排链表

    public void reorderList(ListNode head) {
        ListNode m1 = middleNode(head);
        ListNode head2 = reverseList(m1);
        while(head2.next != null){
            ListNode temp1 = head.next;
            ListNode temp2 = head2.next;
            head.next = head2;
            head2.next = temp1;
            head = temp1;
            head2 = temp2;
        }
    }

    public ListNode middleNode(ListNode head){
        ListNode slow = head, fast = head;

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

        return slow;
    }

    public ListNode reverseList(ListNode head){
        if (head == null || head.next == null) {
            return head;
        }
        ListNode prev = null;
        ListNode curr = head;

        while(curr != null){
            ListNode temp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = temp;
        }

        return prev;
    }

4、142. 环形链表 II

    public ListNode detectCycle(ListNode head) {
        if(head == null || head.next == null){
            return null;
        }

        ListNode fast = head, slow = head;

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

            if(slow == fast){ // 如果相等的话,那证明一定有环
                fast = head;  // 根据fast = 2slow, head到环的入口的距离 一定=相遇节点继续走到入口节点的距离
                while(fast != slow){
                    fast = fast.next;
                    slow = slow.next;
                }

                return fast;
            }
        }

        return null;
    }

5、32进制的两个链表加法

🌳 题目描述:

给定两个 非空链表 L1L2 来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一个字符(0 - 9,a - z)。将这两数相加会返回一个新的链表。备注:a 代表 10, b 代表 11,以此类推

可以假设除了数字 0 之外,这两个数字都不会以零开头。

示例 1:
第一个数:'1' -> 'a' -> 'z' (对应 1az)
第二个数:'2' -> 'b' -> 'y' (对应 2by)
相加得到:'3' -> 'n' -> '5'  (对应 3n5)
示例 2:
第一个数:'9' -> '0' -> '1' (对应 901)
第二个数:'1' -> 'b' -> '3' -> '5' (对应 1b35)
相加得到:'1' -> 'k' -> '3' -> '6'  (对应 1k36)

难点:
1、每个节点存储的都是字符类型
解决方案:可以设计两个方法,字符转成整数,整数转成字符
2、字符类型需要相加并且进位
解决方法:通过一个额外的变量保存进位
import java.util.List;

public class LinkNodeAlgorithm {
    /**
     * 内部类
     */
    private class ListNode {
        private int value;

        private ListNode next;

        public ListNode(int value) {
            this.value = value;
            next = null;
        }
    }




    /**
     * 建立链表
     *
     * @param arr
     * @return
     */
    public ListNode createLinkList(int[] arr) {
        ListNode head = new ListNode(0);
        ListNode res = head;
        for (int i = 0; i < arr.length; i++) {
            ListNode listNode = new ListNode(arr[i]);
            head.next = listNode;
            head = head.next;
        }
        return res.next;
    }

    /**
     * 打印链表
     */
    public void printLinkedList(ListNode head) {
        while (head != null) {
            if (head.next != null) {
                System.out.print(head.value + "->");
            } else {
                System.out.println(head.value);
            }

            head = head.next;
        }
    }

    public static void main(String[] args) {
        int[] arr = new int[]{1, 2, 3, 4, 5};
        LinkNodeAlgorithm linkNodeAlgorithm = new LinkNodeAlgorithm();
        ListNode linkList1 = linkNodeAlgorithm.createLinkList(arr);
        linkNodeAlgorithm.printLinkedList(linkList1);

//        ListNode listNode = shiftLinkedListRightByK(linkList1, 2);
//        linkNodeAlgorithm.printLinkedList(listNode);
        ListNode linkList2 = reorderLinkedList(linkList1);
        linkNodeAlgorithm.printLinkedList(linkList2);
    }

    /**
     * 第一题:链表的每个节点右移K个位置
     * 例如:
     * - 输入:1 -> 2 -> 3 -> 4 -> 5, K = 2
     * - 输出:4 -> 5 -> 1 -> 2 -> 3
     */
    public static ListNode shiftLinkedListRightByK(ListNode head, int k) {
        if (head == null || head.next == null || k == 0) {
            return head;
        }

        // 计算长度并找到尾节点
        int length = 1;
        ListNode tail = head;
        while (tail.next != null) {
            tail = tail.next;
            length++;
        }

        k = k % length;
        if (k == 0) {
            return head;
        }

        // 使用快慢指针找到新的尾节点
        ListNode slow = head;
        ListNode fast = head;
        for (int i = 0; i < k; i++) {
            fast = fast.next;
        }

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

        // 重组链表
        ListNode newHead = slow.next;
        slow.next = null;
        tail.next = head;

        return newHead;
    }

    /**
     * 第二题:删除链表的倒数第n个节点
     * 示例 1:
     * 输入:head = [1,2,3,4,5], n = 2
     * 输出:[1,2,3,5]
     * 示例 2:
     * 输入:head = [1], n = 1
     * 输出:[]
     * 示例 3:
     * 输入:head = [1,2], n = 1
     * 输出:[1]
     */
    public static ListNode removeNthNodeFromEnd(ListNode head, int n) {
        // 链表长度为0的特殊场景
        if (head == null) {
            return head;
        }
        // 链表长度为1的特殊场景
        if(head.next == null && n == 1) {
            return null;
        }

        ListNode fast = head;
        ListNode slow = head;

        for (int i = 0; i < n; i++) {
            fast = fast.next;
        }
        // 链表长度为2的情况
        if(fast == null) {
            return slow.next;
        }

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

        slow.next = slow.next.next;

        return head;
    }

    /**
     * 第三道:重排链表
     * 示例 1:
     * 输入:head = [1,2,3,4]
     * 输出:[1,4,2,3]
     * 示例 2:
     * 输入:head = [1,2,3,4,5]
     * 输出:[1,5,2,4,3]
     */
    public static ListNode reorderLinkedList(ListNode head) {
        ListNode mid = searchMiddleNode(head);
        ListNode head2 = reverseLinkedList(mid);
        ListNode res = head;
        while (head2.next != null) {
            ListNode nxt = head.next;
            ListNode nxt2 = head2.next;
            head.next = head2;
            head2.next = nxt;
            head = nxt;
            head2 = nxt2;
        }

        return res;
    }

    /**
     * 第四道:链表是否有环,链表判断是否有环,如果有环,返回环的入口
     * @return
     */
    public ListNode detectCycle(ListNode head) {
        if(head == null || head.next == null){
            return null;
        }

        ListNode fast = head, slow = head;

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

            if(slow == fast){ // 如果相等的话,那证明一定有环
                fast = head;  // 根据fast = 2slow, head到环的入口的距离 一定=相遇节点继续走到入口节点的距离
                while(fast != slow){
                    fast = fast.next;
                    slow = slow.next;
                }

                return fast;
            }
        }

        return null;
    }

    /**
     * 找到中间节点
     * @return
     */
    public static ListNode searchMiddleNode(ListNode head) {
        ListNode slow = head, fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

    /**
     * 反转链表
     * @param head
     * @return
     */
    public static ListNode reverseLinkedList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }

        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode tmp = curr.next;
            curr.next = prev;
            prev  = curr;
            curr = tmp;
        }

        return  prev;
    }




}

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

// 迭代版
public static ListNode swapPairsInLinkedList(ListNode head){
    ListNode dummyNode = new ListNode(0);
    dummyNode.next = head;
    ListNode prev = dummyNode;

    while (prev.next != null && prev.next.next != null){
        ListNode first = prev.next;
        ListNode second = first.next;

        first.next = second.next;
        second.next = first;
        prev.next = second;

        prev = prev.next;
    }

    return dummyNode.next;
}
// 递归版
public static ListNode swapPairsInLinkedListByRecursion(ListNode head){
     // 递归终止条件:当前节点为空或只剩一个节点
    if(head != null && head.next != null){
        return head;
    }

    // 保存下一组的头节点
    ListNode nextGroupHead = head.next.next;

    // 交换当前两个节点
    ListNode newHead = head.next;
    newHead.next = head;

    // 递归处理剩余链表,并连接结果
    head.next = swapPairsInLinkedListByRecursion(nextGroupHead);

    return newHead;
}

7、链表升序

🌳 题目描述:

一个链表,单索引是递增的,双索引是递减的,请对它进行升序排序,要求O(1)空间复杂度

示例 1:

img

public static ListNode sortWaveLinkedList(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }

    // 分割奇偶链表
    ListNode oddDummy = new ListNode(0);
    ListNode evenDummy = new ListNode(0);
    ListNode oddTail = oddDummy;
    ListNode evenTail = evenDummy;
    ListNode current = head;
    boolean isOdd = true;

    while (current != null) {
        if (isOdd) {
            oddTail.next = current;
            oddTail = oddTail.next;
        } else {
            evenTail.next = current;
            evenTail = evenTail.next;
        }
        current = current.next;
        isOdd = !isOdd;
    }
    oddTail.next = null;
    evenTail.next = null;

    ListNode oddHead = oddDummy.next;
    ListNode evenHead = evenDummy.next;

    // 反转偶数链表
    evenHead = reverseLinkedList(evenHead);

    // 合并两个有序链表
    ListNode dummy = new ListNode(0);
    ListNode tail = dummy;

    while (oddHead != null && evenHead != null) {
        if (oddHead.value <= evenHead.value) {
            tail.next = oddHead;
            oddHead = oddHead.next;
        } else {
            tail.next = evenHead;
            evenHead = evenHead.next;
        }
        tail = tail.next;
    }

    // 连接剩余节点
    tail.next = (oddHead != null) ? oddHead : evenHead;

    return dummy.next;
}

8、146. LRU 缓存

import java.util.HashMap;
import java.util.Map;

public class LRUCache {
    private int capacity;
    private Map<Integer, ListNode> map;
    private ListNode dummyHead;
    private ListNode dummyTail;

    private class ListNode {
        int key;
        int value;
        ListNode next;
        ListNode prev;

        public ListNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.map = new HashMap<>();
        this.dummyHead = new ListNode(-1, -1);
        this.dummyTail = new ListNode(-1, -1);
        dummyHead.next = dummyTail;
        dummyTail.prev = dummyHead;
    }

    public int get(int key) {
        if (!map.containsKey(key)) {
            return -1;
        }

        ListNode node = map.get(key);
        remove(node);
        update(node);

        return node.value;
    }

    private void update(ListNode node) {
        node.next = dummyHead.next;
        node.next.prev = node;
        node.prev = dummyHead;
        dummyHead.next = node;
    }

    private void remove(ListNode node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    public void put(int key, int value) {
        if (map.containsKey(key)) {
            ListNode listNode = map.get(key);
            listNode.value = value;
            remove(listNode);
            update(listNode);
        } else {
            ListNode listNode = new ListNode(key, value);
            map.put(key, listNode);

            update(listNode);
            if (map.size() > capacity) {
                map.remove(dummyTail.prev.key);
                remove(dummyTail.prev);
            }
        }
    }

}

9、约瑟夫环问题

🌳 题目描述:

约瑟夫问题是个著名的问题:N个人围成一圈,第一个人从1开始报数,报M的将被杀掉,下一个人接着从1开始报。如此反复,最后剩下一个,求最后的胜利者。
例如只有三个人,把他们叫做A、B、C,他们围成一圈,从A开始报数,假设报2的人被杀掉。

  • 首先A开始报数,他报1。侥幸逃过一劫。

  • 然后轮到B报数,他报2。非常惨,他被杀了

  • C接着从1开始报数

  • 接着轮到A报数,他报2。也被杀死了。

  • 最终胜利者是C

/**
 * 解决约瑟夫环问题, 因为是学习链表,故本题采用链表,模拟流程来解决本问题。
 */
public class SolveJosephusProblem {
    /**
     * 内部类
     */
    private static class ListNode {
        private int value;

        private ListNode prev;
        private ListNode next;

        public ListNode(int value) {
            this.value = value;
            this.prev = this;
            this.next = this;
        }
    }

    /**
     * 打印链表
     */
    public static void printLinkedList(ListNode head) {
        while (head != null) {
            if (head.next != null) {
                System.out.print(head.value + "->");
            } else {
                System.out.println(head.value);
            }

            head = head.next;
        }
    }

    /**
     * 解决约瑟夫环问题
     */
    public static ListNode solveJosephusProblem(int num, int removeKey){
        // 模拟过程
        ListNode head = new ListNode(1);
        ListNode curr = head;

        for (int i = 2; i <= num; i++) {
            ListNode newNode = new ListNode(i);
            newNode.prev = curr;
            newNode.next = head;
            curr.next = newNode;
            head.prev = newNode;
            curr = newNode;
        }

        // 找到指定位置,并移除它
        curr = head;
        while (curr.next != curr){
            for (int i = 1; i < removeKey; i++) {
                curr = curr.next;
            }

            curr.prev.next = curr.next;
            curr.next.prev = curr.prev;
            curr = curr.next;
        }
        return curr;
    }

    public static void main(String[] args) {
        ListNode listNode = solveJosephusProblem(1, 1);
        System.out.println(listNode.value);
    }
}

10、21. 合并两个有序链表

    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy = new ListNode(0);
        ListNode res = dummy;

        while(list1 != null && list2 != null){
            if(list1.val <= list2.val) {
                dummy.next = list1;
                list1 = list1.next;
            }else {
                dummy.next = list2;
                list2 = list2.next;
            }
            dummy = dummy.next;
        }

        dummy.next = (list1 != null) ? list1 : list2;
        return res.next;
    }

11、LCR 024. 反转链表

    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }

        ListNode newHead = reverseList(head.next);

        head.next.next = head;
        head.next = null;

        return newHead;
    }

12、25. K 个一组翻转链表

class Solution {
  public ListNode reverseKGroup(ListNode head, int k) {
        if (k == 1) return head; // 特判 k=1 直接返回

        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode prevGroupEnd = dummy; // 前一组的尾节点
        
        while (head != null) {
            // 1. 找到当前组的起始和结束节点
            ListNode groupStart = head;
            ListNode groupEnd = getKthNode(groupStart, k);
            if (groupEnd == null) break; // 剩余节点不足 k 个

            // 2. 断开当前组与下一组的连接
            ListNode nextGroupStart = groupEnd.next;
            groupEnd.next = null;

            // 3. 反转当前组,并连接前后组
            ListNode newHead = reverse(groupStart); // 反转后新头
            ListNode newTail = groupStart;          // 反转后新尾(即原头)
            prevGroupEnd.next = newHead;            // 前组尾 -> 新头
            newTail.next = nextGroupStart;          // 新尾 -> 后组头

            // 4. 更新指针,准备处理下一组
            prevGroupEnd = newTail;
            head = nextGroupStart;
        }
        return dummy.next;
    }

    // 辅助方法:找到从 start 出发的第 k 个节点(允许中途判空)
    private ListNode getKthNode(ListNode start, int k) {
        ListNode curr = start;
        for (int i = 1; i < k && curr != null; i++) {
            curr = curr.next;
        }
        return curr; // 可能为 null(不足 k 个)
    }

    // 辅助方法:迭代反转链表,返回新头节点
    private ListNode reverse(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev; // 新头节点
    }
}

13、82. 删除排序链表中的重复元素 II

    public ListNode deleteDuplicates(ListNode head) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode curr = dummy;

        while (curr.next != null && curr.next.next != null) {
            // 发现重复起点
            if (curr.next.val == curr.next.next.val) {
                int val = curr.next.val;
                ListNode end = curr.next; // 从第一个重复节点开始找终点
                
                // 定位到最后一个重复节点
                while (end.next != null && end.next.val == val) {
                    end = end.next;
                }
                
                // 跳过所有重复节点(一次性删除)
                curr.next = end.next;
            } else {
                // 无重复则正常移动
                curr = curr.next;
            }
        }

        return dummy.next;
    }

算法学习-2、链表学习
https://www.orioncoder.cn/archives/suan-fa-xue-xi-2-lian-biao-xue-xi
作者
Orion
发布于
更新于
许可