力扣hot100第四天

142.环形链表II

题目

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

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

不允许修改 链表。

代码

class Solution {

public ListNode detectCycle(ListNode head) {

ListNode slow = head;

ListNode fast = head;

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

slow = slow.next;

fast = fast.next.next;

if (slow == fast) {// 有环

ListNode index1 = fast;

ListNode index2 = head;

// 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口

while (index1 != index2) {

index1 = index1.next;

index2 = index2.next;

}

return index1;

}

}

return null;

}

}

21.合并两个有序链表

题目

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

代码

class Solution {

public ListNode mergeTwoLists(ListNode list1, ListNode list2) {

ListNode head = new ListNode();

ListNode pre = head;

ListNode p = list1;

ListNode q = list2;

while (p != null && q != null) {

if (p.val < q.val) {

pre.next = p;

p = p.next;

} else {

pre.next = q;

q = q.next;

}

pre = pre.next;

}

pre.next = p == null ? q : p;

return head.next;

}

}

2.两数之和

题目

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

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

代码

class Solution {

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {

// 创建一个哨兵节点,方便处理边界情况

ListNode dummy = new ListNode(0);

ListNode current = dummy; // 当前指针,用于构建新链表

int carry = 0; // 进位值

复制代码
    // 遍历两个链表,直到所有节点都处理完毕
    while (l1 != null || l2 != null) {
        // 获取当前节点的值,如果链表已经遍历完,则取0
        int x = (l1 != null) ? l1.val : 0;
        int y = (l2 != null) ? l2.val : 0;

        // 计算当前位的和以及新的进位值
        int sum = carry + x + y;
        carry = sum / 10; // 新的进位值
        current.next = new ListNode(sum % 10); // 当前位的值

        // 移动指针
        current = current.next;
        if (l1 != null) l1 = l1.next;
        if (l2 != null) l2 = l2.next;
    }

    // 如果最后有进位,则需要再添加一个节点
    if (carry > 0) {
        current.next = new ListNode(carry);
    }

    // 返回结果链表的头节点(跳过哨兵节点)
    return dummy.next;
}

}

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

题目

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

代码

class Solution {

public ListNode removeNthFromEnd(ListNode head, int n) {

//新建一个虚拟头节点指向head

ListNode dummyNode = new ListNode(0);

dummyNode.next = head;

//快慢指针指向虚拟头节点

ListNode fastIndex = dummyNode;

ListNode slowIndex = dummyNode;

复制代码
    // 只要快慢指针相差 n 个结点即可
    for (int i = 0; i <= n; i++) {
        fastIndex = fastIndex.next;
    }
    while (fastIndex != null) {
        fastIndex = fastIndex.next;
        slowIndex = slowIndex.next;
    }

    // 此时 slowIndex 的位置就是待删除元素的前一个位置。
    // 具体情况可自己画一个链表长度为 3 的图来模拟代码来理解
    // 检查 slowIndex.next 是否为 null,以避免空指针异常
    if (slowIndex.next != null) {
        slowIndex.next = slowIndex.next.next;
    }
    return dummyNode.next;
}

}

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

题目

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

代码

class Solution {

public ListNode swapPairs(ListNode head) {

ListNode dumyhead = new ListNode(-1); // 设置一个虚拟头结点

dumyhead.next = head; // 将虚拟头结点指向head,这样方便后面做删除操作

ListNode cur = dumyhead;

ListNode temp; // 临时节点,保存两个节点后面的节点

ListNode firstnode; // 临时节点,保存两个节点之中的第一个节点

ListNode secondnode; // 临时节点,保存两个节点之中的第二个节点

while (cur.next != null && cur.next.next != null) {

temp = cur.next.next.next;

firstnode = cur.next;

secondnode = cur.next.next;

cur.next = secondnode; // 步骤一

secondnode.next = firstnode; // 步骤二

firstnode.next = temp; // 步骤三

cur = firstnode; // cur移动,准备下一轮交换

}

return dumyhead.next;

}

}

25.K个一组翻转链表

题目

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

代码

class Solution {

public ListNode reverseKGroup(ListNode head, int k) {

ListNode h = new ListNode(0, head);

ListNode pre = h;

ListNode nextKHead = h.next;

while (nextKHead != null) {

int i = 0;

while (i < k) {

i++;

nextKHead = nextKHead.next;

if (nextKHead == null) break;

}

if (i < k) break;

ListNode t = pre.next;

reverseK(pre, nextKHead);

pre = t;

}

return h.next;

}

复制代码
private void reverseK(ListNode pre, ListNode nextKHead) {
    ListNode p = pre.next.next;
    pre.next.next = nextKHead;
    while (p != nextKHead) {
        ListNode next = p.next;
        p.next = pre.next;
        pre.next = p;
        p = next;
    }
}

}

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 作为传入参数。

代码

class Solution {

public Node copyRandomList(Node head) {

Map<Node, Node> map = new HashMap<>();

Node p = head;

while (p != null) {

map.put(p, new Node(p.val));

p = p.next;

}

p = head;

while (p != null) {

map.get§.next = map.get(p.next);

map.get§.random = map.get(p.random);

p = p.next;

}

return map.get(head);

}

}

148.排序链表

题目

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

代码

class Solution {

public ListNode sortList(ListNode head) {

if (head == null || head.next == null) return head;

ListNode p = head;

ListNode q = head.next;

while (q != null && q.next != null) {

p = p.next;

q = q.next.next;

}

ListNode second = p.next;

p.next = null;

return merge(sortList(head), sortList(second));

}

复制代码
public ListNode merge(ListNode l1, ListNode l2) {
    ListNode head = new ListNode();
    ListNode p = l1;
    ListNode q = l2;
    ListNode tail = head;
    while (p != null && q != null) {
        if (p.val < q.val) {
            tail.next = p;
            p = p.next;
        } else {
            tail.next = q;
            q = q.next;
        }
        tail = tail.next;
    }
    tail.next = p == null ? q : p;
    return head.next;
}

}

23.合并K个升序链表

题目

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

思路:递归调用merge函数,主要问题就是如何构造这个递归。下面这个代码使用的思路是首先判断lists的长度,长度为0表示空,直接返回空;长度为1直接返回第一个链表;长度为2返回合并后的链表;长度大于2则进行拆分,拆成两个长度平均的链表组递归调用自身。

代码

class Solution {

public ListNode mergeKLists(ListNode[] lists) {

int n = lists.length;

if (n == 0) return null;

else if (n == 1) {

return lists[0];

} else if (n == 2) {

return merge(lists[0], lists[1]);

} else {

ListNode[] lists1 = new ListNode[(n + 1) / 2];

ListNode[] lists2 = new ListNode[n / 2];

for (int i = 0; i < n; i++) {

if (i < lists1.length) {

lists1[i] = lists[i];

} else {

lists2[i - lists1.length] = lists[i];

}

}

return merge(mergeKLists(lists1), mergeKLists(lists2));

}

}

复制代码
public ListNode merge(ListNode l1, ListNode l2) {
    ListNode head = new ListNode();
    ListNode p = l1;
    ListNode q = l2;
    ListNode tail = head;
    while (p != null && q != null) {
        if (p.val < q.val) {
            tail.next = p;
            p = p.next;
        } else {
            tail.next = q;
            q = q.next;
        }
        tail = tail.next;
    }
    tail.next = p == null ? q : p;
    return head.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

思路:使用哈希表和双向链表实现

代码

class LRUCache {

int capacity;

int size;

DLinkNode head;

DLinkNode tail;

Map<Integer, DLinkNode> map;

复制代码
public LRUCache(int capacity) {
    this.capacity = capacity;
    this.size = 0;
    map = new HashMap<>();
    head = new DLinkNode();
    tail = new DLinkNode();
    head.next = tail;
    tail.pre = head;
}

public int get(int key) {
    if (!map.containsKey(key)) return -1;
    DLinkNode node = remove(key);
    add(node);
    return node.val;
}

public void put(int key, int value) {
    if (map.containsKey(key)) {
        remove(key);
        size--;
    }
    if (size == capacity) {
        remove(tail.pre.key);
        size--;
    }
    add(new DLinkNode(key, value));
    size++;
}

public DLinkNode remove(int key) {
    DLinkNode node = map.get(key);
    node.pre.next = node.next;
    node.next.pre = node.pre;
    map.remove(key);
    return node;
}

public void add(DLinkNode node) {
    node.next = head.next;
    node.pre = head;
    head.next.pre = node;
    head.next = node;
    map.put(node.key, node);
}

class DLinkNode {
    int key;
    int val;
    DLinkNode pre;
    DLinkNode next;

    public DLinkNode() {
    }

    public DLinkNode(int key, int val) {
        this.key = key;
        this.val = val;
        this.pre = null;
        this.next = null;
    }
}

}

相关推荐
罗西的思考2 小时前
[2W字长文] 探秘Transformer系列之(23)--- 长度外推
人工智能·算法
算AI21 小时前
人工智能+牙科:临床应用中的几个问题
人工智能·算法
hyshhhh1 天前
【算法岗面试题】深度学习中如何防止过拟合?
网络·人工智能·深度学习·神经网络·算法·计算机视觉
杉之1 天前
选择排序笔记
java·算法·排序算法
Naive_71 天前
蓝桥杯准备(前缀和差分)
java·职场和发展·蓝桥杯
烂蜻蜓1 天前
C 语言中的递归:概念、应用与实例解析
c语言·数据结构·算法
OYangxf1 天前
图论----拓扑排序
算法·图论
我要昵称干什么1 天前
基于S函数的simulink仿真
人工智能·算法
AndrewHZ1 天前
【图像处理基石】什么是tone mapping?
图像处理·人工智能·算法·计算机视觉·hdr
念九_ysl1 天前
基数排序算法解析与TypeScript实现
前端·算法·typescript·排序算法