来源为:LeetCode图解算法数据结构

图书整理 I

书店店员有一张链表形式的书单,每个节点代表一本书,节点中的值表示书的编号。为更方便整理书架,店员需要将书单倒过来排列,就可以从最后一本书开始整理,逐一将书放回到书架上。请倒序返回这个书单链表。

示例 1:

输入:head = [3,6,4,1]

输出:[1,4,6,3]

提示:

0 <= 链表长度 <= 10000

/**
 * 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; }
 * }
 */
import java.util.ArrayList;
import java.util.List;

class Solution {
    public int[] reverseBookList(ListNode head) {
        List<Integer> li = new ArrayList<>();
        ListNode tem = head;
        while(tem != null){
            li.add(tem.val);
            tem = tem.next;
        }
        // if (li.isEmpty()) return null; // 处理空链表的情况
        // ListNode ans = new ListNode(li.get(li.size() - 1));
        // for(int i = li.size() - 2; i >= 0; i--){
        //     ListNode ne = new ListNode(li.get(i));
        //     ne.next = ans;
        //     ans = ne;
        // }
        // return ans;

        for(int i = 0; i < li.size() / 2; i++){
            Collections.swap(li, i, li.size() - i - 1);
        }
        int[] arr = li.stream().mapToInt(Integer::intValue).toArray();
        return arr;
    }
}

改进:

/**
 * 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; }
 * }
 */
import java.util.ArrayList;
import java.util.List;

class Solution {
    public int[] reverseBookList(ListNode head) {
        //count计数,倒序即可
        int count = 0;
        ListNode tem = head;
        while(tem != null){
            count++;
            tem = tem.next;
        }
        int[] arr = new int[count];
        tem = head;
        while(tem != null){
            arr[--count] = tem.val;
            tem = tem.next;
        }
        return arr;
    }
}

删除链表节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

示例 1:

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:

输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        ListNode tem = new ListNode();
        tem = head;
        //找到第一个看有么有,没有直接不彳亍
        while(head != null){
            if(head.val != val){
                tem.val = head.val;
                head = head.next;
                break;
            }
            head = head.next;
        }
        ListNode ans = tem;
//有了头部新建就好
        while(head != null){
            if(head.val != val){
                ListNode li = new ListNode(head.val);
                tem.next = li;
                
                tem = li;
                
            }
            head = head.next;
        }
        return ans;
    }
}

更新:

class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        ListNode dummy = new ListNode(0);
        //哑节点
        dummy.next = head;
        ListNode prev = dummy;
        
        while (prev.next != null) {
            if (prev.next.val == val) {
                prev.next = prev.next.next; // 跳过当前节点
            } else {	//注意这里的else,极端情况下出现空指针。
                prev = prev.next;
            }
        }
        
        return dummy.next;
    }
}

链表倒置

开辟新空间

/**
 * 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; }
 * }
 */
import java.util.*;
class Solution {
    public ListNode trainningPlan(ListNode head) {
        if(head == null){
            return head;
        }
        List<Integer> l = new ArrayList<>();
        ListNode l1 = head;
        while(l1 != null){
            l.add(l1.val);
            l1 = l1.next;
        }
        ListNode ansHead = new ListNode();
        ListNode ans = ansHead;
        
        for(int i = l.size() - 1; i >= 0; i--){
            ListNode tem = new ListNode();
            tem.val = l.get(i);
            ansHead.next = tem;
            ansHead = tem;
        }
        return ans.next;
    }
}

原地倒置

/**
 * 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; }
 * }
 */
import java.util.*;
class Solution {
    public ListNode trainningPlan(ListNode head) {
        if(head == null){
            return head;
        }
        
        ListNode pre = null;//是最终的,在最末尾,左部
        ListNode tem = null;//用于中途放置head的下一个
        while(head != null){
            tem = head.next;
            head.next = pre;
            pre = head;
            head = tem;
            
        }
        return pre;
    }
}

链表倒数第i个节点获取

给定一个头节点为 head 的链表用于记录一系列核心肌群训练项目编号,请查找并返回倒数第 cnt 个训练项目编号。

示例 1:

输入:head = [2,4,7,8], cnt = 1
输出:8

/**
 * 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; }
 * }
 */
 import java.util.*;
class Solution {
    public ListNode trainingPlan(ListNode head, int cnt) {
        int count = 0;
        ListNode tem = head;
        while(tem != null){
            count++;
            tem = tem.next;
        }
        tem = head;
        for(int i = count; i != cnt; i--){
            tem = tem.next;
        }
        return tem;
    }
}

上面是先遍历一遍,再去找到相应位置的。还可以采用双指针的方式来做,中间相差了cnt个数,快的指针到了null,也就是low到倒数第cnt个。

/**
 * 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; }
 * }
 */
 import java.util.*;
class Solution {
    public ListNode trainingPlan(ListNode head, int cnt) {
        ListNode fast = head;
        ListNode low = head;
        for(int i = 1; i <= cnt; i++){
            fast = fast.next;
        }
        while(fast != null){
            fast = fast.next;
            low = low.next;
        }
        return low;
    }
}

链表合并

给定两个以 有序链表 形式记录的训练计划 l1、l2,分别记录了两套核心肌群训练项目编号,请合并这两个训练计划,按训练项目编号 升序 记录于链表并返回。

注意:新链表是通过拼接给定的两个链表的所有节点组成的。

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:

输入:l1 = [], l2 = []
输出:[]
示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

/**
 * 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 trainningPlan(ListNode l1, ListNode l2) {
        ListNode anshead = new ListNode();
        ListNode ans = anshead;
        while(l1 != null && l2 != null){
            if(l1.val >= l2.val){
                ListNode li = new ListNode();
                li.val = l2.val;
                l2 = l2.next;
                anshead.next = li;
                anshead = anshead.next;
            }else{
                ListNode li = new ListNode();
                li.val = l1.val;
                l1 = l1.next;
                anshead.next = li;
                anshead = anshead.next;
            }
        }
        while(l1 != null){
            ListNode li = new ListNode();
                li.val = l1.val;
                l1 = l1.next;
                anshead.next = li;
                anshead = anshead.next;
        }
        while(l2 != null){
            ListNode li = new ListNode();
                li.val = l2.val;
                l2 = l2.next;
                anshead.next = li;
                anshead = anshead.next;
        }
        return ans.next;
    }
}

当然这道题没说得重新开内存,直接赋值l1,l2就好了,这么写内存开销大了就。

链表相交

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
解释:第一个正式训练项目编号为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
class Solution {
    ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int len1 = 0, len2 = 0;
        ListNode tem1 = headA;
        ListNode tem2 = headB;
        while(tem1!= null){
            len1++;
            tem1 = tem1.next;
        }
        while(tem2!= null){
            len2++;
            tem2= tem2.next;
        }
        tem1 = headA;
        tem2 = headB;
        if(len1 >= len2){
            for(int i = 1; i <= len1 - len2; i++){
                tem1 = tem1.next;
            }
        }else{
            for(int i = 1; i <= len2 - len1; i++){
                tem2 = tem2.next;
            }
        }
        while(tem1 != null){
            if(tem1.val == tem2.val){
                return tem1;
            }else{
                tem1=tem1.next;tem2=tem2.next;
            }
        }
        return null;

    }
}

上述解法找到两个差了长度多少,长的先找,短的等到长的与其长度一样的时候在一起向后走,直到相遇或者null。

但还是下面这种更巧妙。

image-20240320172224817

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode A = headA, B = headB;
        while (A != B) {
            A = A != null ? A.next : headB;
            B = B != null ? B.next : headA;
        }
        return A;
    }
}

image-20240320172253779

泪目了。

链表复制

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

image-20240329091829400

==是把内存地址重新开辟==

简单思路,先来直接新的next节点,进行连接。后续每次O(n)查找相应对应位置的信息,遍历啊。

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    public Node copyRandomList(Node head) {
        Node dummy = new Node(0);
        Node li = dummy;
        Node tem = head;
        while(tem != null){
            Node tem1 = new Node(tem.val);
            tem1.next = tem.next;
            li.next = tem1;
            li = li.next;
            tem = tem.next;
        }
        tem = head;
        li = dummy.next;
        
        while(tem != null){
            if(tem.random != null){
                Node nodeyuan = head;
                Node nodehou = dummy.next;
                while(nodeyuan != tem.random){
                    nodeyuan = nodeyuan.next;
                    nodehou = nodehou.next;
                }
                li.random = nodehou;
            }
            tem = tem.next;
            li = li.next;
        }
        return dummy.next;
    }
}

优化的话,每次查询之前的random新的节点,使用Map

class Solution {
    public Node copyRandomList(Node head) {
        if(head == null){
            return null;
        }
        Node cur = head;
        Map<Node, Node> map = new HashMap<Node, Node>();
        while(cur != null){
            map.put(cur, new Node(cur.val));
            cur = cur.next;
        }
        cur = head;
        while(cur != null){
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        return map.get(head);
    }
}