【算法】day14 链表

1、回文链表 hot

题目234. 回文链表 - 力扣(LeetCode)

分析

法一:先放到数组,再双指针对比。时间复杂度 O(n+1/2n)=O(n),空间复杂度 O(n)。

法二,递归:先递归判断两边是不是回文,递归出来再判断中间是不是回文。先记录 head,再从 head 开始递归到 tail,tail 与 head 层层从边缘到中间比较。时间复杂度:递归遍历 O(n),再双向遍历 O(2n),共 O(n)。空间复杂度:栈深度 O(n)。

法三:先快慢指针获取中间节点,再翻转后半部分节点,对比完了再翻转回去。时间复杂度 O(获取中点1/2n+翻转1/2n+对比1/2n+翻转1/2n) = O(n),空间复杂度 O(1)。缺点:因为对原链表进行了修改,所以多线程时对链表的操作要加锁。

代码

法二,递归:

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 {
    private ListNode front; // 记录左节点

    public boolean isPalindrome(ListNode head) {
        front = head;
        return isCycle(head);
    }

    public boolean isCycle(ListNode cur) {
        if (cur == null) return true; // 递归结束条件
        if (!isCycle(cur.next)) return false; // 靠两边的节点不满足回文,返回 false
        if (front.val != cur.val) return false; // 靠中间的不满足回文,返回 false
        front = front.next; // 更新左节点,比较下一对
        return true;
    }
}

法三:

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 boolean isPalindrome(ListNode head) {
        // 1. 快慢指针获取中间节点
        ListNode middle = getMiddle(head);
        // 2. 把中间结点后的链表翻转
        reserve(middle);
        // 3. 对比
        ListNode t1 = head, t2 = middle.next;
        boolean flag = true;
        while (t2 != null) {
            if (t1.val != t2.val) {
                flag = false;
                break;
            }
            t1 = t1.next;
            t2 = t2.next;
        }
        // 4. 把链表翻转回去,恢复原样
        reserve(middle);
        return flag;
    }

    public ListNode getMiddle (ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode slow = head, fast = head.next;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

    public void reserve (ListNode head) {
        ListNode t = head.next;
        head.next = null;
        while (t != null) {
            ListNode next = t.next;
            t.next = head.next;
            head.next = t;
            t = next;
        }
    }
}

2、随机链表的复制 hot

题目138. 随机链表的复制 - 力扣(LeetCode)

分析:对于每个节点,我们需要访问并创建其下一个节点和随机节点,并插入新链表中。但是因为是随机节点,所以我们并不知道下一个节点/随机节点是否已经在之前被创建,如果不进行判断就会创建并连接多余的节点。当然也可以不判断,先遍历一遍创建完整链表,把旧-新节点对放到 map;再遍历一遍每个节点的随机节点,从 map 中取出对应的新节点进行连接。

时间复杂度 O(2n) = O(n),空间复杂度 哈希表 O(n)。

代码

java 复制代码
/*
// 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) {
        Map<Node, Node> map = new HashMap<>();
        // 1. 创建正常链表
        Node t = head;
        Node newHead = new Node(0), tail = newHead;
        while (t != null) {
            Node node = new Node(t.val);
            map.put(t, node);
            tail.next = node;
            tail = tail.next;
            t = t.next;
        }
        // 2. 连接随机节点
        t = head;
        tail = newHead.next;
        while (t != null) {
            Node node = map.get(t.random);
            tail.random = node;
            tail = tail.next;
            t = t.next;
        }
        return newHead.next;
    }
}

3、LRU 缓存 hot

题目146. LRU 缓存 - 力扣(LeetCode)

分析:哈希表(频繁查找 key 对应的节点 O(1))+双向链表(频繁删除尾节点,单链表无法获取尾节点前驱)删插都是 O(1)。

  • LRUCache(int capacity):初始化容量、双向链表。
  • int get(int key):哈希表里查 key 对应的 node,没有则返回 -1;有则将其移动到链头(最近使用),并返回 value。
  • void put(int key, int value):哈希表查 key 对应的 node,有则将其移动到链头,并更新 value;没有则插入到链头,如果size超过了容量,就删除链尾最久没有使用的 node。
  • 使用空值的头结点、尾节点,方便删除、插入。

代码

java 复制代码
class LRUCache {
    class DoubleListNode {
        int key;
        int val;
        DoubleListNode pre;
        DoubleListNode next;

        public DoubleListNode () {
        }

        public DoubleListNode (int key, int val) {
            this.key = key;
            this.val = val;
        }
    }

    private DoubleListNode head, tail;
    private int size, capacity;
    private Map<Integer, DoubleListNode> map = new HashMap<>();

    public LRUCache(int capacity) {
        size = 0;
        this.capacity = capacity;
        head = new DoubleListNode();
        tail = new DoubleListNode();
        head.next = tail;
        tail.pre = head;
    }
    
    public int get(int key) {
        DoubleListNode node = map.get(key);
        // 存在则移到链头,返回 val
        if (node != null) {
            int val = node.val;
            removeToHead(node);
            return val;
        }
        // 不存在,返回 -1
        return -1;
    }
    
    public void put(int key, int val) {
        DoubleListNode node = map.get(key);
        // 如果存在,则更新值,并移到链头
        if (node != null) {
            node.val = val;
            removeToHead(node);
            return;
        }
        // 不存在,则创建新节点插到链头
        DoubleListNode newNode = new DoubleListNode(key, val);
        map.put(key, newNode);
        insertToHead(newNode);
        size++;
        // 如果链表大小超过容量,删除尾部最久没使用的
        if (size > capacity) {
            deleteTail();
            size--;
        }
    }

    public void removeToHead(DoubleListNode node) {
        delete(node);
        insertToHead(node);
    }

    public void delete(DoubleListNode node) {
        DoubleListNode pre = node.pre;
        DoubleListNode next = node.next;
        pre.next = next;
        next.pre = pre;
    }

    public void insertToHead(DoubleListNode node) {
        node.next = head.next;
        node.next.pre = node;
        head.next = node;
        node.pre = head;
    }

    public void deleteTail() {
        DoubleListNode deleteNode = tail.pre;
        DoubleListNode pre = deleteNode.pre;
        map.remove(deleteNode.key);
        pre.next = tail;
        tail.pre = pre;
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */
相关推荐
EnCi Zheng2 小时前
LeetCode_1_两数之和完全解析-从暴力枚举到哈希表优化的算法进阶之路
算法
寂静山林3 小时前
UVa 1620 Lazy Susan
算法
天选之女wow3 小时前
【代码随想录算法训练营——Day51】图论——99.计数孤岛、100.最大岛屿的面积
算法·图论
yy_xzz3 小时前
【数据结构】队列(Queue)详解——数据结构的“先进先出”
开发语言·数据结构
承渊政道3 小时前
算法复杂度
c语言·数据结构·c++·算法·visual studio
Giser探索家3 小时前
无人机数字资产采集技术架构与实践:从多维度感知到云端化建模的实现路径
大数据·人工智能·算法·计算机视觉·分类·无人机
~~李木子~~3 小时前
动态规划算法实践:从斐波那契到数字推理
算法·动态规划·代理模式
泡沫冰@4 小时前
数据结构(12)
数据结构
磊-4 小时前
数组双指针总结
数据结构·算法