数据结构与算法 - 设计

news2024/11/15 4:10:59

1. 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

提示:

  • 1 <= capacity <= 3000
  • 0 <= key <= 10000
  • 0 <= value <= 10^5
  • 最多调用 2 * 10^5 次 get 和 put

解法一:LinkedHashMap

import java.util.LinkedHashMap;  
import java.util.Map;  

class LRUCache {  
    private int capacity;  
    private LinkedHashMap<Integer, Integer> cache;  

    public LRUCache(int capacity) {  
        this.capacity = capacity;  
        this.cache = new LinkedHashMap<>(capacity, 0.75f, true) {  
            @Override  
            protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {  
                // 当缓存容量超过capacity时自动移除最近最少使用的元素
                return size() > capacity;  
            }  
        };  
    }  

    public int get(int key) {  
        if (!cache.containsKey(key)) {  
            return -1;  
        }  
        // 访问key后将其移到最前面  
        return cache.get(key);  
    }  

    public void put(int key, int value) {  
        cache.put(key, value);  
    }  
}  

/**  
 * 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);  
 */

解法二:自定义节点类 + 自定义双端队列

  • map中存储node,可以省去在双向链表中查找node的时间,这样让使用最近访问的节点移动到链表头时达到O(1)的需求
class LRUCache {

    static class Node {
        Node prev;
        Node next;
        int key;
        int value;

        public Node() {
        }

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

    // 双向链表
    static class DoubleLinkedList {
        Node head;
        Node tail;

        public DoubleLinkedList() {
            head = tail = new Node();
            head.next = tail;
            tail.prev = head;
        }

        // 头部添加
        public void addFirst(Node newFirst) {
            Node oldFirst = head.next;
            newFirst.next = oldFirst;
            newFirst.prev = head;
            head.next = newFirst;
            oldFirst.prev = newFirst;
        }

        // 已知节点删除
        public void remove(Node node) {
            Node prev = node.prev;
            Node next = node.next;
            prev.next = next;
            next.prev = prev;
        }

        // 尾部删除
        public Node removeLast() {
            Node last = tail.prev;
            remove(last);
            return last;
        }

    }

    private int capacity;
    private final HashMap<Integer, Node> map = new HashMap<>();
    private final DoubleLinkedList list = new DoubleLinkedList();

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

        Node node = map.get(key);
        list.remove(node);
        list.addFirst(node);

        return node.value;
    }
    
    public void put(int key, int value) {
        if(map.containsKey(key)) {
            // 更新
            Node node = map.get(key);
            node.value = value;

            list.remove(node);
            list.addFirst(node);
        } else {
            // 新增
            Node node = new Node(key, value);
            map.put(key, node);
            list.addFirst(node);
            if(map.size() > capacity) {
                Node removed = list.removeLast();
                map.remove(removed.key);
            }
        }
    }
}

2. LFU缓存

请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。

实现 LFUCache 类:

  • LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象
  • int get(int key) - 如果键 key 存在于缓存中,则获取键的值,否则返回 -1 。
  • void put(int key, int value) - 如果键 key 已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最久未使用 的键。

为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。

当一个键首次插入到缓存中时,它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作,使用计数器的值将会递增。

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

示例:

输入:
["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]
输出:
[null, null, null, 1, null, -1, 3, null, -1, 3, 4]

解释:
// cnt(x) = 键 x 的使用计数
// cache=[] 将显示最后一次使用的顺序(最左边的元素是最近的)
LFUCache lfu = new LFUCache(2);
lfu.put(1, 1);   // cache=[1,_], cnt(1)=1
lfu.put(2, 2);   // cache=[2,1], cnt(2)=1, cnt(1)=1
lfu.get(1);      // 返回 1
                 // cache=[1,2], cnt(2)=1, cnt(1)=2
lfu.put(3, 3);   // 去除键 2 ,因为 cnt(2)=1 ,使用计数最小
                 // cache=[3,1], cnt(3)=1, cnt(1)=2
lfu.get(2);      // 返回 -1(未找到)
lfu.get(3);      // 返回 3
                 // cache=[3,1], cnt(3)=2, cnt(1)=2
lfu.put(4, 4);   // 去除键 1 ,1 和 3 的 cnt 相同,但 1 最久未使用
                 // cache=[4,3], cnt(4)=1, cnt(3)=2
lfu.get(1);      // 返回 -1(未找到)
lfu.get(3);      // 返回 3
                 // cache=[3,4], cnt(4)=1, cnt(3)=3
lfu.get(4);      // 返回 4
                 // cache=[3,4], cnt(4)=2, cnt(3)=3

提示:

  • 1 <= capacity <= 10^4
  • 0 <= key <= 10^5
  • 0 <= value <= 10^9
  • 最多调用 2 * 10^5 次 get 和 put 方法

解法一:

class LFUCache {

    static class Node {
        Node prev;
        Node next;
        int key;
        int value;
        int freq;

        public Node() {
        }

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

    static class DoubleLinkedList {
        private final Node head;
        private final Node tail;
        int size = 0;

        public DoubleLinkedList() {
            head = tail = new Node();
            head.next = tail;
            tail.prev = head;
        }

        public void remove(Node node) {
            Node prev = node.prev;
            Node next = node.next;
            prev.next = next;
            next.prev = prev;
            node.prev = node.next = null;
            size--;
        }

        public void addFirst(Node newFirst) {
            Node oldFirst = head.next;
            newFirst.prev = head;
            newFirst.next = oldFirst;
            head.next = newFirst;
            oldFirst.prev = newFirst;
            size++;
        }

        public Node removeLast() {
            Node last = tail.prev;
            remove(last);
            return last;
        }

        public boolean isEmpty() {
            return size == 0;
        }
    }

    // 频度链表
    private final HashMap<Integer, DoubleLinkedList> freqMap = new HashMap<>();
    // key和value链表
    private final HashMap<Integer, Node> kvMap = new HashMap<>();
    private final int capacity;
    private int minFreq = 1;  // 最小频度

    public LFUCache(int capacity) {
        this.capacity = capacity;
    }

    /*
        key不存在,返回-1
        key存在,返回value值,增加节点的使用频度,将其转移到频度+1的链表当中
     */
    public int get(int key) {
        if(!kvMap.containsKey(key)) {
            return -1;
        }
        // 将节点频度+1,从旧链表转移至新链表
        Node node = kvMap.get(key);
        // 先删
        freqMap.get(node.freq).remove(node);
        if(freqMap.get(node.freq).isEmpty() && node.freq == minFreq) {
            minFreq++;
        }

        node.freq++;
        /*DoubleLinkedList list = freqMap.get(node.freq);
        // 后加
        if(list == null) {
            list = new DoubleLinkedList();
            freqMap.put(node.freq, list);
        }
        list.addFirst(node);*/

        freqMap.computeIfAbsent(node.freq, k -> new DoubleLinkedList()).
                addFirst(node);

        return node.value;
    }

    /*
        更新:将节点的value更新,增加节点的使用频度,将其转移到频度+1的链表当中
        新增:检查是否超过容量,若超过,淘汰minFreq链表的最后节点。创建新节点,放入kvMap,并加入频度为1的双向链表
     */
    public void put(int key, int value) {
        if(kvMap.containsKey(key)) {
            // 更新
            Node node = kvMap.get(key);
            freqMap.get(node.freq).remove(node);
            if(freqMap.get(node.freq).isEmpty() && node.freq == minFreq) {
                minFreq++;
            }
            node.freq++;
            freqMap.computeIfAbsent(node.freq, k -> new DoubleLinkedList()).
                    addFirst(node);
            node.value = value;
        } else {
            // 新增
            if(kvMap.size() == capacity) {
                Node last = freqMap.get(minFreq).removeLast();
                kvMap.remove(last.key);
            }
            
            Node node = new Node(key, value, 1);
            kvMap.put(key, node);
            minFreq = 1;
            freqMap.computeIfAbsent(node.freq, k -> new DoubleLinkedList()).addFirst(node);
        }
    }
}

/**
 * Your LFUCache object will be instantiated and called as such:
 * LFUCache obj = new LFUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

3. 随机数

3.1 线性同余发生器

  • 公式:nextSeed = (seed * a + c) % m
  • 32位随机数生成器
  • 乘法会超过int范围导致随机性被破坏
package com.itheima.algorithms.leetcode;

import java.util.Arrays;
import java.util.stream.IntStream;

/*
    线性同余发生器
 */
public class MyRandom {
    private final int a;
    private final int c;
    private final int m;
    private int seed;

    public MyRandom(int a, int c, int m, int seed) {
        this.a = a;
        this.c = c;
        this.m = m;
        this.seed = seed;
    }

    public int  nextInt() {
        return seed = (a * seed + c) % m;
    }

    public static void main(String[] args) {
        MyRandom r1 = new MyRandom(7, 0, 11, 1);
        System.out.println(Arrays.toString(IntStream.generate(r1::nextInt).limit(30).toArray()));
        MyRandom r2 = new MyRandom(7, 0, Integer.MAX_VALUE, 1);
        System.out.println(Arrays.toString(IntStream.generate(r2::nextInt).limit(30).toArray()));
    }
}

3.2 Java版

  • 0x5DEECE66DL * 0x5DEECE66DL 不会超过 long 的范围
  • m决定只取48位随机数
  • 对于nextInt,只取48位随机数的高32位
package com.itheima.algorithms.leetcode;

import java.util.Arrays;
import java.util.Random;
import java.util.stream.IntStream;

public class MyRandom2 {
    private static final long a = 0x5DEECE66DL;
    private static final long c = 0xBL;
    private static final long m = 1L << 48;
    private long seed;

    public MyRandom2(long seed) {
        this.seed = (seed ^ a) & (m - 1);
    }

    public int nextInt() {
        seed = (a * seed + c) & (m - 1);
        return ((int) (seed >>> 16));
    }

    public static void main(String[] args) {
        Random r1 = new Random(1);
        MyRandom2 r2 = new MyRandom2(1);

        System.out.println(Arrays.toString(IntStream.generate(r1::nextInt).limit(10).toArray()));

        System.out.println(Arrays.toString(IntStream.generate(r2::nextInt).limit(10).toArray()));
    }
}

4. 跳表

4.1 randomLevel

设计一个方法调用若干次,每次返回 1~max 的数字,从 1 开始,返回数字的比例减半,例如 max = 4,让大概

  • 50% 的几率返回 1

  • 25% 的几率返回 2

  • 12.5% 的几率返回 3

  • 12.5% 的几率返回 4

package com.itheima.algorithms.leetcode;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;

/*
    跳表
 */
public class SkipList {

    public static void main(String[] args) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < 1000; i++) {
            int level = randomLevel(4);
            map.compute(level, (k, v) -> v == null ? 1 : v + 1);
        }
        System.out.println(map.entrySet().stream().collect(
                Collectors.toMap(
                        Map.Entry::getKey,
                        e -> String.format("%d%%", e.getValue() * 100 / 1000)
                )
        ));
    }

    /*
        设计一个方法,方法会随机返回 1 ~ max的数字
        从1开始,数字的几率逐级减半,例如max = 4,让大概
        - 50% 的几率返回1
        - 25% 的几率返回2
        - 12.5% 的几率返回3
        - 12.5% 的几率返回4
     */

    static Random r = new Random();
    static int randomLevel(int max) {
        // return r.nextBoolean() ? 1 : r.nextBoolean() ? 2 : r.nextBoolean() ? 3 : 4;
        int x = 1;
        while(x < max) {
            if(r.nextBoolean()) {
                return x;
            }
            x++;
        }
        return x;
    }

}

4.2 跳表

下楼梯规则:

  • 若next指针为null,或者next指向的节点值 >= 目标,向下找
  • 若next指针不为null,且next指向的节点值 < 目标,向右找

节点的【高度】

  • 高度并不需要额外属性来记录,而是由之前节点next == 本节点的个数来决定,或是本节点next数组长度
  • 本实现选择了第一种方式来决定高度,本节点的next数组长度统一为MAX

4.3 设计跳表

不使用任何库函数,设计一个 跳表 。

跳表 是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。

例如,一个跳表包含 [30, 40, 50, 60, 70, 90] ,然后增加 8045 到跳表中,以下图的方式操作:

跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 O(n)。跳表的每一个操作的平均时间复杂度是 O(log(n)),空间复杂度是 O(n)

了解更多 : 跳表 - OI Wiki

在本题中,你的设计应该要包含这些函数:

  • bool search(int target) : 返回target是否存在于跳表中。
  • void add(int num): 插入一个元素到跳表。
  • bool erase(int num): 在跳表中删除一个值,如果 num 不存在,直接返回false. 如果存在多个 num ,删除其中任意一个即可。

注意,跳表中可能存在多个相同的值,你的代码需要处理这种情况。

示例 1:

输入
["Skiplist", "add", "add", "add", "search", "add", "search", "erase", "erase", "search"]
[[], [1], [2], [3], [0], [4], [1], [0], [1], [1]]
输出
[null, null, null, null, false, null, true, false, true, false]

解释
Skiplist skiplist = new Skiplist();
skiplist.add(1);
skiplist.add(2);
skiplist.add(3);
skiplist.search(0);   // 返回 false
skiplist.add(4);
skiplist.search(1);   // 返回 true
skiplist.erase(0);    // 返回 false,0 不在跳表中
skiplist.erase(1);    // 返回 true
skiplist.search(1);   // 返回 false,1 已被擦除

提示:

  • 0 <= num, target <= 2 * 10^4
  • 调用searchadd,  erase操作次数不大于 5 * 10^4 
class Skiplist {

    static Random r = new Random();

    static int randomLevel(int max) {
        int x = 1;
        while (x < max) {
            if (r.nextBoolean()) {
                return x;
            }
            x++;
        }
        return x;
    }

    private static final int MAX = 16; // redis 32 java 62
    private final Node head = new Node(-1);

    static class Node {
        int val; // 值
        Node[] next = new Node[MAX]; // next指针数组

        public Node(int val) {
            this.val = val;
        }

        @Override
        public String toString() {
            return "Node(" + val + ")";
        }
    }

    public Skiplist() {

    }

    public Node[] find(int target) {
        Node[] path = new Node[MAX];
        Node curr = head;
        for (int level = MAX - 1; level >= 0; level--) { // 从下向下找
            while (curr.next[level] != null && curr.next[level].val < target) { // 向右找
                curr = curr.next[level];
            }
            path[level] = curr;
        }

        return path;
    }

    public boolean search(int target) {
        Node[] path = find(target);
        Node node = path[0].next[0];
        return node != null && node.val == target;
    }

    public void add(int num) {
        // 1. 确定添加位置
        Node[] path = find(num);
        // 2. 创建新节点随机高度
        Node node = new Node(num);
        int level = randomLevel(MAX);
        // 3. 修改路径节点next指针,以及新结点next指针
        for (int i = 0; i < level; i++) {
            node.next[i] = path[i].next[i];
            path[i].next[i] = node;
        }
    }

    public boolean erase(int num) {
        // 1. 确定删除位置
        Node[] path = find(num);
        Node node = path[0].next[0];
        if (node == null || node.val != num) {
            return false;
        }
        for (int i = 0; i < MAX; i++) {
            if (path[i].next[i] != node) {
                break;
            }
            path[i].next[i] = node.next[i];
        }
        return true;
    }
}

5. 最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int val) 将元素val推入堆栈。
  • void pop() 删除堆栈顶部的元素。
  • int top() 获取堆栈顶部的元素。
  • int getMin() 获取堆栈中的最小元素。

示例 1:

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

提示:

  • -2^31 <= val <= 2^31 - 1
  • poptop 和 getMin 操作总是在 非空栈 上调用
  • pushpoptop, and getMin最多被调用 3 * 10^4 次

解法一:

class MinStack {
    LinkedList<Integer> stack = new LinkedList<>();
    LinkedList<Integer> min = new LinkedList<>();

    public MinStack() {
        min.push(Integer.MAX_VALUE);
    }

    public void push(int val) {
        stack.push(val);
        min.push(Math.min(val, min.peek()));
    }

    public void pop() {
        if (stack.isEmpty()) {
            return;
        }
        stack.pop();
        min.pop();
    }

    public int top() {
        return stack.peek();
    }

    public int getMin() {
        return min.peek();
    }
}

6. TinyURL的加密与解密

TinyURL 是一种 URL 简化服务, 比如:当你输入一个 URL https://leetcode.com/problems/design-tinyurl 时,它将返回一个简化的URL http://tinyurl.com/4e9iAk 。请你设计一个类来加密与解密 TinyURL 。

加密和解密算法如何设计和运作是没有限制的,你只需要保证一个 URL 可以被加密成一个 TinyURL ,并且这个 TinyURL 可以用解密方法恢复成原本的 URL 。

实现 Solution 类:

  • Solution() 初始化 TinyURL 系统对象。
  • String encode(String longUrl) 返回 longUrl 对应的 TinyURL 。
  • String decode(String shortUrl) 返回 shortUrl 原本的 URL 。题目数据保证给定的 shortUrl 是由同一个系统对象加密的。

示例:

输入:url = "https://leetcode.com/problems/design-tinyurl"
输出:"https://leetcode.com/problems/design-tinyurl"

解释:
Solution obj = new Solution();
string tiny = obj.encode(url); // 返回加密后得到的 TinyURL 。
string ans = obj.decode(tiny); // 返回解密后得到的原本的 URL 。

提示:

  • 1 <= url.length <= 10^4
  • 题目数据保证 url 是一个有效的 URL

解法一:

要让【长】【短】网址一一对应
1. 用【随机数】作为短网址标识
2. 用【hash码】作为短网址标识
3. 用【递增数】作为短网址标识
public class TinyURLLeetcode535 {

    public static void main(String[] args) {
        /*CodecSequence codec = new CodecSequence();
        String encoded = codec.encode("https://leetcode.cn/problems/1");
        System.out.println(encoded);

        encoded = codec.encode("https://leetcode.cn/problems/2");
        System.out.println(encoded);*/
//        for (int i = 0; i <= 62; i++) {
//            System.out.println(i + "\t" + CodecSequence.toBase62(i));
//        }

        System.out.println(CodecSequence.toBase62(237849728));
    }


    /*
        要让【长】【短】网址一一对应

            1. 用【随机数】作为短网址标识
            2. 用【hash码】作为短网址标识
            3. 用【递增数】作为短网址标识
                1) 多线程下可以使用吗?
                2) 分布式下可以使用吗?
                3) 4e9iAk 是怎么生成的?

                a-z 0-9 A-Z  62进制的数字

        0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f

        十进制 => 十六进制
        31       1f

        31 % 16 = 15
        31 / 16 = 1

        1 % 16 = 1
        1 / 16 = 0


        长网址: https://leetcode.cn/problems/encode-and-decode-tinyurl/description/
        对应的短网址: http://tinyurl.com/4e9iAk
     */

    static class CodecSequence {
        private static final char[] toBase62 = {
                'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
                'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
                'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
                'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
                '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
        };

        public static String toBase62(int number) {
            if (number == 0) {
                return String.valueOf(toBase62[0]);
            }
            StringBuilder sb = new StringBuilder();
            while (number > 0) {
                int r = number % 62;
                sb.append(toBase62[r]);
                number = number / 62;
            }
            return sb.toString();
        }

        private final Map<String, String> longToShort = new HashMap<>();
        private final Map<String, String> shortToLong = new HashMap<>();
        private static final String SHORT_PREFIX = "http://tinyurl.com/";
        private static int id = 1;

        public String encode(String longUrl) {
            String shortUrl = longToShort.get(longUrl);
            if (shortUrl != null) {
                return shortUrl;
            }
            // 生成短网址
            shortUrl = SHORT_PREFIX + id;
            longToShort.put(longUrl, shortUrl);
            shortToLong.put(shortUrl, longUrl);
            id++;
            return shortUrl;
        }

        public String decode(String shortUrl) {
            return shortToLong.get(shortUrl);
        }
    }

    static class CodecHashCode {
        private final Map<String, String> longToShort = new HashMap<>();
        private final Map<String, String> shortToLong = new HashMap<>();
        private static final String SHORT_PREFIX = "http://tinyurl.com/";

        public String encode(String longUrl) {
            String shortUrl = longToShort.get(longUrl);
            if (shortUrl != null) {
                return shortUrl;
            }
            // 生成短网址
            int id = longUrl.hashCode(); // int
            while (true) {
                shortUrl = SHORT_PREFIX + id;
                if (!shortToLong.containsKey(shortUrl)) {
                    longToShort.put(longUrl, shortUrl);
                    shortToLong.put(shortUrl, longUrl);
                    break;
                }
                id++;
            }
            return shortUrl;
        }

        public String decode(String shortUrl) {
            return shortToLong.get(shortUrl);
        }
    }

    static class CodecRandom {
        private final Map<String, String> longToShort = new HashMap<>();
        private final Map<String, String> shortToLong = new HashMap<>();
        private static final String SHORT_PREFIX = "http://tinyurl.com/";

        public String encode(String longUrl) {
            String shortUrl = longToShort.get(longUrl);
            if (shortUrl != null) {
                return shortUrl;
            }
            // 生成短网址
            while (true) {
                int id = ThreadLocalRandom.current().nextInt();// 1
                shortUrl = SHORT_PREFIX + id;
                if (!shortToLong.containsKey(shortUrl)) {
                    longToShort.put(longUrl, shortUrl);
                    shortToLong.put(shortUrl, longUrl);
                    break;
                }
            }
            return shortUrl;
        }

        public String decode(String shortUrl) {
            return shortToLong.get(shortUrl);
        }
    }
}

7. 设计Twitter

设计一个简化版的推特(Twitter),可以让用户实现发送推文,关注/取消关注其他用户,能够看见关注人(包括自己)的最近 10 条推文。

实现 Twitter 类:

  • Twitter() 初始化简易版推特对象
  • void postTweet(int userId, int tweetId) 根据给定的 tweetId 和 userId 创建一条新推文。每次调用此函数都会使用一个不同的 tweetId 。
  • List<Integer> getNewsFeed(int userId) 检索当前用户新闻推送中最近  10 条推文的 ID 。新闻推送中的每一项都必须是由用户关注的人或者是用户自己发布的推文。推文必须 按照时间顺序由最近到最远排序 。
  • void follow(int followerId, int followeeId) ID 为 followerId 的用户开始关注 ID 为 followeeId 的用户。
  • void unfollow(int followerId, int followeeId) ID 为 followerId 的用户不再关注 ID 为 followeeId 的用户。

示例:

输入
["Twitter", "postTweet", "getNewsFeed", "follow", "postTweet", "getNewsFeed", "unfollow", "getNewsFeed"]
[[], [1, 5], [1], [1, 2], [2, 6], [1], [1, 2], [1]]
输出
[null, null, [5], null, null, [6, 5], null, [5]]

解释
Twitter twitter = new Twitter();
twitter.postTweet(1, 5); // 用户 1 发送了一条新推文 (用户 id = 1, 推文 id = 5)
twitter.getNewsFeed(1);  // 用户 1 的获取推文应当返回一个列表,其中包含一个 id 为 5 的推文
twitter.follow(1, 2);    // 用户 1 关注了用户 2
twitter.postTweet(2, 6); // 用户 2 发送了一个新推文 (推文 id = 6)
twitter.getNewsFeed(1);  // 用户 1 的获取推文应当返回一个列表,其中包含两个推文,id 分别为 -> [6, 5] 。推文 id 6 应当在推文 id 5 之前,因为它是在 5 之后发送的
twitter.unfollow(1, 2);  // 用户 1 取消关注了用户 2
twitter.getNewsFeed(1);  // 用户 1 获取推文应当返回一个列表,其中包含一个 id 为 5 的推文。因为用户 1 已经不再关注用户 2

提示:

  • 1 <= userId, followerId, followeeId <= 500
  • 0 <= tweetId <= 10^4
  • 所有推特的 ID 都互不相同
  • postTweetgetNewsFeedfollow 和 unfollow 方法最多调用 3 * 10^4 次

解法一:

class Twitter {

    // 文章类
    static class Tweet {
        int id;
        int time;
        Tweet next;

        public Tweet(int id, int time, Tweet next) {
            this.id = id;
            this.time = time;
            this.next = next;
        }

        public int getId() {
            return id;
        }
        
        public int getTime() {
            return time;
        }
    }

    // 用户类
    static class User {
        int id;
        Set<Integer> followees = new HashSet<>();
        Tweet head = new Tweet(-1, -1, null);

        public User(int id) {
            this.id = id;
        }
    }

    private final Map<Integer, User> userMap = new HashMap<>();
    private static int time;

    public Twitter() {

    }

    // 发布文章
    public void postTweet(int userId, int tweetId) {
        User user = userMap.computeIfAbsent(userId, User::new);
        user.head.next = new Tweet(tweetId, time++, user.head.next);
    }

    // 获取最新10篇文章(包括自己和关注者)
    public List<Integer> getNewsFeed(int userId) {
        // 思路:合并k个有序链表
        User user = userMap.get(userId);
        if(user == null) {
            return List.of();
        }

        PriorityQueue<Tweet> queue = new PriorityQueue<>(Comparator.comparingInt(Tweet::getTime).reversed());
        if(user.head.next != null) {
            queue.offer(user.head.next);
        }
        for (Integer id : user.followees) {
            User followee = userMap.get(id);
            if(followee.head.next != null) {
                queue.offer(followee.head.next);
            }
        }

        List<Integer> res = new ArrayList<>();
        int count = 0;
        while(!queue.isEmpty() && count < 10) {
            Tweet max = queue.poll();
            res.add(max.id);
            if(max.next != null) {
                queue.offer(max.next);
            }
            count++;
        }

        return res;
    }

    // 新增关注
    public void follow(int followerId, int followeeId) {
        User follower = userMap.computeIfAbsent(followerId, User::new);
        User followee = userMap.computeIfAbsent(followeeId, User::new);
        follower.followees.add(followee.id);
    }

    // 取消关注
    public void unfollow(int followerId, int followeeId) {
        User user = userMap.get(followerId);
        if(user != null) {
            user.followees.remove(followeeId);
        }
    }
}

/**
 * Your Twitter object will be instantiated and called as such:
 * Twitter obj = new Twitter();
 * obj.postTweet(userId,tweetId);
 * List<Integer> param_2 = obj.getNewsFeed(userId);
 * obj.follow(followerId,followeeId);
 * obj.unfollow(followerId,followeeId);
 */

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2057357.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

0819、0820梳理及一些面试题梳理

一、抓包分析 二、HTTP服务器 三、动态库与静态库 四、一些面试题 指针数组和数组指针的区别&#xff1a;指针数组本质是一个数组&#xff0c;只是数组中存储的是指针变量。数组指针存储的是该数组的起始地址&#xff0c;对该指针来说每偏移一个单位就是偏移了一整个数组的地…

如何寻找专业精密机械零件代加工工厂

在现代工业生产中&#xff0c;精密机械零件的加工质量直接关系到产品的性能和可靠性。因此&#xff0c;寻找一家专业的精密机械零件代加工工厂至关重要。以下时利和整理分享的一些关于寻找专业精密机械零件代加工工厂的关键步骤和要点&#xff0c;帮助你找到合适的合作伙伴。 首…

想投资现货黄金?在TMGM开户需要多少钱?

最近&#xff0c;越来越多的人开始关注黄金投资&#xff0c;希望通过黄金来对冲风险、保值增值。而选择一家可靠的交易平台是进行黄金投资的第一步。TMGM作为全球知名的外汇交易商&#xff0c;也为投资者提供了黄金交易服务。那么&#xff0c;在TMGM开户投资黄金&#xff0c;需…

尚硅谷VUE项目实战,前端项目-尚品汇2

尚硅谷VUE项目实战&#xff0c;前端项目-尚品汇2 1、路由传参 2、重写push

数字转化为千位符形式, 百分比形式

Intl.NumberFormat 千位符&#xff1a; function formatAsRMB(num){return new Intl.NumberFormat(zh-CN,{style: decimal, //将数字格式化为十进制数currency: CNY, // 货币为人民币minimumFractionDigits: 0 // 表示不显示小数部分}).format(num)}const number 12345678co…

【C++进阶学习】第十四弹——特殊类设计——探寻各种情况下类的应用

前言&#xff1a; C类是C很重要的一个部分&#xff0c;在很多应用场景中都发挥着十分重要的作用&#xff0c;今天我们来讲解几个特殊场景下类的应用 目录 一、特殊类&#xff1a;只能在栈/堆上创建对象 1. 只在栈上创建对象 2. 只在堆上创建对象 二、特殊类&#xff1a;不能…

8.20 QT

1.思维导图 2. 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTime> #include <QTimerEvent> #include <QTextToSpeech>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidge…

Zookeeper中 Server 服务器的四种工作状态详解

Zookeeper中 Server 服务器的四种工作状态详解 1. LOOKING2. FOLLOWING3. LEADING4. OBSERVING&#xff08;3.3.0及以后版本&#xff09; &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; ZooKeeper集群中的服务器主要存在以下四种工作状态&a…

32位入门级MCU(ARM Cortex-M3内核)STM32F103系列

推荐: 32位入门级MCU(ARM Cortex-M3内核)中的六边形战士——STM32F103系列 为什么MCU中需要ADC模块 原创 IPBrain平台君 集成电路大数据平台 2024年08月19日 19:18 北京 自从平台君发布了几期关于MCU的文章之后,后台有很多小伙伴们留言。其中有位读者问平台君:平台君,…

python构建一个web程序

from flask import Flaskapp Flask(__name__)app.route(/) def hello_world():return 欢迎来到我的Python Web程序!if __name__ __main__:app.run(debugTrue)1、安装flask D:\Users\USER\PycharmProjects\pythonProject1\p01>pip install flask WARNING: Ignoring invalid…

LLM才硬件(显存)需求

参考&#xff1a; https://www.hardware-corner.net/guides/computer-to-run-llama-ai-model/ GitHub - hiyouga/LLaMA-Factory: Efficiently Fine-Tune 100 LLMs in WebUI (ACL 2024) 直观的一个表&#xff1a;

多模态学习Multimodal Learning:人工智能中的多模态原理与技术介绍初步了解

多模态学习&#xff08;Multimodal Learning&#xff09;是机器学习中的一个前沿领域&#xff0c;旨在综合处理和理解来自不同模态的数据。模态可以包括文本、图像、音频、视频等。随着数据多样性和复杂性增加&#xff0c;多模态学习在自然语言处理、计算机视觉、语音识别等领域…

存储和传输/寻找大端字节序/有哪款MCU或MPU是真支持大端?

文章目录 概述ARM基础ARM 不生产芯片ARM分类和命名ARM指令集和架构ARM架构和内核ARM内核和芯片其他处理器架构 在手册中找寻字节序的踪迹ST 32 MCU 没有大端&#xff1f;Cortex-M 系列Cortex-A 系列Cortex-R 系列小结 芯片外设支持大端字节序DMAC 支持大端字节序的情况CRC 支持…

SpringBoot接入高德地图猎鹰轨迹服务API

SpringBoot接入高德地图猎鹰轨迹服务API 一、AP文档 猎鹰轨迹服务API文档 二、页面图 1、需登录账号&#xff0c;申请对应的应用key值 三、代码部分&#xff1a; 1、控制层 RestController RequestMapping("/gdTrack") public class TrackController {private …

VS Code开发C#(.NET)之快速入门

本篇快速介绍在VS Code中开发C#的完整说明和示例&#xff1a; 环境准备 安装VS Code&#xff1a; 前往Visual Studio Code官网 下载并安装VS Code。 安装.NET SDK&#xff1a; C#是基于.NET框架的&#xff0c;因此需要安装 .NET SDK。 前往 .NET官网 下载并安装适用于操…

【微信小程序】自定义组件 - 插槽

1. 什么是插槽 2. 单个插槽 在小程序中&#xff0c;默认每个自定义组件中只允许使用一个 进行占位&#xff0c;这种个数上的限制叫做单个插槽。 3. 启用多个插槽 在小程序的自定义组件中&#xff0c;需要使用多 插槽时&#xff0c;可以在组件的 .js 文件中&#xff0c;通过…

数据库之间表的迁移

什么时候用&#xff1f; 相信大家在开发中&#xff0c;会先在本地和测试环境的数据库去建立表&#xff0c;没问题了再去正式环境建立相同的表。或者我们有旧系统和新系统&#xff0c;要把旧系统里面的数据库某个功能的相关的表给导出来&#xff0c;这时候就会发生表的数据迁移…

【网络安全】漏洞挖掘:IDOR实例

未经许可&#xff0c;不得转载。 文章目录 正文 正文 某提交系统&#xff0c;可以选择打印或下载passport。 点击Documents > Download后&#xff0c;应用程序将执行 HTTP GET 请求&#xff1a; /production/api/v1/attachment?id4550381&enamemId123888id为文件id&am…

Vue3 的 expose 介绍

在 Vue 3 中&#xff0c;expose 是一个用于控制组件内部方法和属性暴露给父组件的新功能。这使得父组件可以调用子组件内部的方法或访问其数据&#xff0c;尤其在使用组合式 API&#xff08;Composition API&#xff09;时&#xff0c;这种能力非常有用。 1. 基本用法 expose…

目标检测 | yolov9 原理和介绍

相关系列&#xff1a; 目标检测 | yolov1 原理和介绍 目标检测 | yolov2/yolo9000 原理和介绍 目标检测 | yolov3 原理和介绍 目标检测 | yolov4 原理和介绍 目标检测 | yolov5 原理和介绍 目标检测 | yolov6 原理和介绍 目标检测 | yolov7 原理和介绍 目标检测 | yolov8 原理和…