leetcode-hot100-链表

160相交链表

题目:给你两个单链表的头节点headA和headB,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回null。

代码随想录的解法:

求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置。此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。

java 复制代码
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA=headA;
        ListNode curB=headB;
        int lenA=0;
        int lenB=0;

        while(curA!=null){
            curA=curA.next;
            lenA++;
        }

        while(curB!=null){
            curB=curB.next;
            lenB++;
        }

        curA=headA;
        curB=headB;

        if (lenB > lenA) {  // 让curA为最长链表的头,lenA为其长度
            //1. swap (lenA, lenB);
            int tmpLen = lenA;
            lenA = lenB;
            lenB = tmpLen;
            //2. swap (curA, curB);
            ListNode tmpNode = curA;
            curA = curB;
            curB = tmpNode;
        }

        int gap=lenA-lenB;
        while(gap>0){
            curA=curA.next;
            gap--;
        }

        while(curA!=null){
            if(curA==curB){
                return curA;
            }
            curA=curA.next;
            curB=curB.next;
        }

        return null;
    }
}

看了一个觉得很好的题解的方法:

java 复制代码
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p=headA;
        ListNode q=headB;

        while(p!=q){
            if(p!=null){
                p=p.next;
            }else{
                p=headB;
            }

            if(q!=null){
                q=q.next;
            }else{
                q=headA;
            }
        }
        return p;
    }
}

206反转链表

题目:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

双指针一前一后

java 复制代码
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur=head;
        ListNode pre=null;
        ListNode temp=null;

        while(cur!=null){
            temp=cur.next;
            cur.next=pre;
            pre=cur;
            cur=temp;
        }

        return pre;
    }
}

234回文链表

题目:给你一个单链表的头节点head,请你判断该链表是否为回文链表。如果是,返回 true;否则,返回 false。

用数组复制一个单链表,再用双指针来判断。浪费空间,但是思路简单。

java 复制代码
class Solution {
    public boolean isPalindrome(ListNode head) {
        List<Integer> vals=new ArrayList<>();

        ListNode cur=head;
        while(cur!=null){
            vals.add(cur.val);
            cur=cur.next;
        }

        int left=0;
        int right=vals.size()-1;
        while(left<right){
            if(!vals.get(left).equals(vals.get(right))){
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}

避免使用 O(n) 额外空间的方法就是改变输入。我们可以将链表的后半部分反转(修改链表结构),然后将前半部分和后半部分进行比较。比较完成后我们应该将链表恢复原样。虽然不需要恢复也能通过测试用例,但是使用该函数的人通常不希望链表结构被更改。

该方法虽然可以将空间复杂度降到 O(1),但是在并发环境下,该方法也有缺点。在并发环境下,函数运行时需要锁定其他线程或进程对链表的访问,因为在函数执行过程中链表会被修改。

整个流程可以分为以下五个步骤:找到前半部分链表的尾节点。反转后半部分链表。判断是否回文。恢复链表。返回结果。

执行步骤一,我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。

我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。若链表有奇数个节点,则中间的节点应该看作是前半部分。步骤二可以使用206反转链表问题中的解决方法来反转链表的后半部分。步骤三比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。步骤四与步骤二使用的函数相同,再反转一次恢复链表本身。

141环形链表

题目:给你一个链表的头节点 head ,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。如果链表中存在环,则返回 true 。 否则,返回 false。

直接使用快慢指针判断。

java 复制代码
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode slow=head;
        ListNode fast=head;

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

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

142 环形链表II

题目:给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改链表。

和141比多一个返回环的入口。

找环的入口

根据这个图,慢指针走了x+y,快指针走了x + y + n (y + z)。相同的时间不同的速度可以得到一个等式 (x + y) * 2 = x + y + n (y + z),从而得到 x = n (y + z) -- y。x其实就是我们要求的环的入口。

接下来是重点:将这个等式变换一下:x = (n - 1) (y + z) + z (这里n一定大于等于1)

这个等式说明,从头结点出发一个指针,从快慢指针的相遇节点也出发一个指针,这两个指针每次只走一个节点,那么当这两个指针相遇的时候就是环形入口的节点。(在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。让index1和index2同时移动,每次移动一个节点,那么他们相遇的地方就是环形入口的节点。)

java 复制代码
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;

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

            if(fast==slow){
                ListNode index1=head;
                ListNode index2=fast;

                while(index1!=index2){
                    index1=index1.next;
                    index2=index2.next;
                }
                return index1;
            }
        }
        return null;
    }
}

21 合并两个有序链表

题目:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

直接比,注意虚拟头节点。

java 复制代码
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy=new ListNode();
        ListNode cur=dummy;

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

        return dummy.next;
    }
}

2 两数相加

题目:给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储一位数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例1:输入:l1 = [2,4,3], l2 = [5,6,4],输出:[7,0,8],解释:342 + 465 = 807.

使用递归来一个一个处理加法。

java 复制代码
/**
 * 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) {
        return add(l1,l2,0);
    }

    private ListNode add(ListNode l1,ListNode l2,int carry){
        if(l1==null && l2==null && carry==0){
            return null;
        }

        int s=carry;
        if(l1!=null){
            s=s+l1.val;
            l1=l1.next;
        }

        if(l2!=null){
            s=s+l2.val;
            l2=l2.next;
        }

        return new ListNode(s%10, add(l1,l2,s/10));
    }
}

19 删除链表的倒数第n个节点

题目:给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

快慢指针:

java 复制代码
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy=new ListNode();
        dummy.next=head;

        ListNode fast=dummy;
        ListNode slow=dummy;

        for(int i=0;i<=n;i++){
            fast=fast.next;
        }

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

        slow.next=slow.next.next;

        return dummy.next;
    }
}

我最开始return的是head,但是如果链表只有1个节点且节点被删,这个就无法通过。

24 两两交换链表中的节点

题目:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

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

示例 2:输入:head = [],输出:[]

示例 3:输入:head = [1],输出:[1]

双指针,交换的时候记得画图。

java 复制代码
class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummy=new ListNode();
        dummy.next=head;
        ListNode cur=dummy;
        
        while(cur.next!=null && cur.next.next!=null){
            ListNode node1=cur.next;
            ListNode node2=cur.next.next;

            cur.next=node2;
            node1.next=node2.next;
            node2.next=node1;

            cur=cur.next.next;
        }

        return dummy.next;
    }
}

25 K个一组翻转链表

题目:给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

java 复制代码
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy=new ListNode(0);
        dummy.next=head;

        ListNode pre = dummy;//创建一前一后两个指针,用来截取每一个k长度的链表进行分次反转
        ListNode end = dummy;

        while(end.next!=null){
            for (int i = 0; i < k && end != null; i++) end = end.next;
            if (end == null) break;

            ListNode start = pre.next;
            ListNode next = end.next;

            end.next=null;
            pre.next=reverse(start);
            start.next = next;
            pre = start;
            end=pre;
        }
        return dummy.next;
    }

    private ListNode reverse(ListNode head){
        ListNode last = null; //初始化一个尾结点
        ListNode cur = head;
        while (cur != null) {
            ListNode temp = cur.next;
            cur.next = last;
            last = cur;
            cur = temp;
    }
    return last;
    }
}

138随机链表的复制

题目:给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:val:一个表示 Node.val 的整数。

random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null。

你的代码只接受原链表的头节点 head 作为传入参数。

java 复制代码
class Solution {
    public Node copyRandomList(Node head) {
        for(Node cur=head;cur!=null;cur=cur.next.next){// 复制每个节点,把新节点直接插到原节点的后面
            cur.next=new Node(cur.val,cur.next);
        }

        for(Node cur=head;cur!=null;cur=cur.next.next){ // 遍历交错链表中的原链表节点
            if(cur.random!=null){
                cur.next.random=cur.random.next; // 要复制的 random 是cur.random的复制节点,即下一个节点
            }
        }

        Node dummy=new Node(0);
        Node tail=dummy;
        for(Node cur=head;cur!=null;cur=cur.next,tail=tail.next){ // 把交错链表分离成两个链表
            Node copy=cur.next;
            tail.next=copy; // 把新节点插在 tail 的后面,构建新的链表
            cur.next=copy.next;
        }

        return dummy.next;
    }
}

交错链表分离的时候记得画图,不然容易弄错。

148排序链表

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

归并排序:找到链表的中间结点的前一个节点,并断开中间节点与其前一个节点的连接。这样我们就把原链表均分成了两段更短的链表。分治,递归调用 sortList,分别排序 head(只有前一半)和mid。排序后,得到了两个有序链表,那么合并两个有序链表,得到排序后的链表,返回链表头节点。

java 复制代码
class Solution {
    public ListNode sortList(ListNode head) {
        if(head==null || head.next==null) return head;

        ListNode mid=middlenode(head);
        head=sortList(head);
        mid=sortList(mid);
        return merge(head,mid);
    }

    private ListNode middlenode(ListNode head){ //找中间节点,并断开成两个
            ListNode slow=head;
            ListNode fast=head;
            ListNode pre=head;
            while(fast!=null && fast.next!=null){
                pre=slow; //记录slow前一个节点
                slow=slow.next;
                fast=fast.next.next;
            }
            pre.next=null;
            return slow;
        }

    private ListNode merge(ListNode list1,ListNode list2){
            ListNode dummy=new ListNode();
            ListNode cur=dummy;
            while(list1!=null && list2!=null){
                if(list1.val<list2.val){
                    cur.next=list1;
                    list1=list1.next;
                }else{
                    cur.next=list2;
                    list2=list2.next;
                }
                cur=cur.next;
            }

            if(list1!=null){
                cur.next=list1;
            }else{
                cur.next=list2;
            }

            return dummy.next;
        }
}

链表的题经常需要将很多简单操作合到一个题目中。

23 合并K个升序链表

题目:给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:输入:lists = [[1,4,5],[1,3,4],[2,6]],输出:[1,1,2,3,4,4,5,6]

解释:链表数组如下:[1->4->5, 1->3->4, 2->6]

将它们合并到一个有序链表中得到。1->1->2->3->4->4->5->6

用最小堆实现。初始把所有链表的头节点入堆,然后不断弹出堆中最小节点 x,如果 x.next 不为空就加入堆中。循环直到堆为空。把弹出的节点按顺序拼接起来,就得到了答案。

java 复制代码
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        PriorityQueue<ListNode> pq=new PriorityQueue<>((a,b)->a.val-b.val);
        for(ListNode head:lists){ //所有非空链表的头节点入堆
            if(head!=null){
                pq.offer(head); //[1,1,2]
            }
        }

        ListNode dummy=new ListNode();
        ListNode cur=dummy;
        while(!pq.isEmpty()){
            ListNode node=pq.poll(); //[1,2]
            if(node.next!=null){
                pq.offer(node.next); //[1,2,4],然后在while中不断poll,offer
            }
            cur.next=node; 
            cur=cur.next;
        }
        return dummy.next;
    }
}

146 LRU缓存

题目:请你设计并实现一个满足 LRU (最近最少使用) 缓存约束的数据结构。

实现 LRUCache 类:LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。

void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

示例:输入:["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]

\[2\], \[1, 1\], \[2, 2\], \[1\], \[3, 3\], \[2\], \[4, 4\], \[1\], \[3\], \[4\]

输出:[null, null, null, 1, null, -1, null, -1, 3, 4]

解释:LRUCache lRUCache = new LRUCache(2);

lRUCache.put(1, 1); // 缓存是 {1=1}

lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}

lRUCache.get(1); // 返回 1

lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}

lRUCache.get(2); // 返回 -1 (未找到)

lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}

lRUCache.get(1); // 返回 -1 (未找到)

lRUCache.get(3); // 返回 3

lRUCache.get(4); // 返回 4

java 复制代码
class LRUCache {
    private static class Node {
        int key, value;
        Node prev, next;

        Node(int k, int v) {
            key = k;
            value = v;
        }
    }

    private final int capacity;
    private final Node dummy = new Node(0, 0); // 哨兵节点
    private final Map<Integer, Node> keyToNode = new HashMap<>();

    public LRUCache(int capacity) {
        this.capacity = capacity;
        dummy.prev = dummy;
        dummy.next = dummy;
    }

    public int get(int key) {
        Node node = getNode(key); // getNode 会把对应节点移到链表头部
        return node != null ? node.value : -1;
    }

    public void put(int key, int value) {
        Node node = getNode(key); // getNode 会把对应节点移到链表头部
        if (node != null) { // 有这本书
            node.value = value; // 更新 value
            return;
        }
        node = new Node(key, value); // 新书
        keyToNode.put(key, node);
        pushFront(node); // 放到最上面
        if (keyToNode.size() > capacity) { // 书太多了
            Node backNode = dummy.prev;
            keyToNode.remove(backNode.key);
            remove(backNode); // 去掉最后一本书
        }
    }

    // 获取 key 对应的节点,同时把该节点移到链表头部
    private Node getNode(int key) {
        if (!keyToNode.containsKey(key)) { // 没有这本书
            return null;
        }
        Node node = keyToNode.get(key); // 有这本书
        remove(node); // 把这本书抽出来
        pushFront(node); // 放到最上面
        return node;
    }

    // 删除一个节点(抽出一本书)
    private void remove(Node x) {
        x.prev.next = x.next;
        x.next.prev = x.prev;
    }

    // 在链表头添加一个节点(把一本书放到最上面)
    private void pushFront(Node x) {
        x.prev = dummy;
        x.next = dummy.next;
        x.prev.next = x;
        x.next.prev = x;
    }
}
相关推荐
kishu_iOS&AI2 小时前
Python - 链表浅析
开发语言·python·链表
逆境不可逃3 小时前
LeetCode 热题 100 之 33. 搜索旋转排序数组 153. 寻找旋转排序数组中的最小值 4. 寻找两个正序数组的中位数
java·开发语言·数据结构·算法·leetcode·职场和发展
leaves falling4 小时前
二分查找:迭代与递归实现全解析
数据结构·算法·leetcode
做怪小疯子4 小时前
Leetcode刷题——深度优先搜索(DFS)
算法·leetcode·深度优先
想吃火锅10056 小时前
【leetcode】105. 从前序与中序遍历序列构造二叉树
算法·leetcode·职场和发展
圣保罗的大教堂6 小时前
leetcode 3567. 子矩阵的最小绝对差 中等
leetcode
老鼠只爱大米7 小时前
LeetCode经典算法面试题 #215:数组中的第K个最大元素(快速选择、堆排序、计数排序等多种实现方案详解)
算法·leetcode·堆排序·快速选择·topk·数组中的第k个最大元素
逆境不可逃7 小时前
LeetCode 热题 100 之 35. 搜索插入位置 74. 搜索二维矩阵 34. 在排序数组中查找元素的第一个和最后一个位置
数据结构·算法·leetcode
_日拱一卒7 小时前
LeetCode:移动零
算法·leetcode·职场和发展