设计笔记

设计笔记

聚焦在编码设计数据结构、复现数据结构,对常见数据结构的要求熟练掌握,题目较少

💚 -- Easy

💛 -- Medium

🧡 -- Hard

题目顺序,codetop.cc 默认排序规则


两个缓存更新:LFU(频率 frequency)、LRF(最近recently)

💛146. LRU缓存机制

💛面试题 16.25. LRU缓存

LRU标签

/* 
    核心结构 LinkedHashMap 
    LinkedHashMap 的初始化 initialCapacity, 对于本题目是无效的, 仅仅是初始化HashMap而已
    忘了 initialCapacity 吧
    
    LinkedHashMap的put是在链表的尾部添加元素, 故其尾部是最近的元素, 头部是老元素
    
    辅助函数
    1. 将已有元素提至队伍的尾部
 */
class LRUCache {
    // capacity > 0
    public LRUCache(int capacity) {}
    // O(1)
    public int get(int key) {}
    // O(1)
    public void put(int key, int value) {}
}

🧡460. LFU缓存

LFU标签

/*
更为困难
1. 记录 key-value -> HashMap<Integer, Integer>
2. 记录 key-freq  -> HashMap<Integer, Integer>
3. 记录 freq-key  -> HashMap<Integer, LinkedHashSet<Integer>>
	LinkedHashSet 保证一个时序, 同 freq下删除最早的数据
4. 记录 minFreq

辅助函数
1. 将已有元素的freq+1(同时检查是否要更新minFreq)
2. 删除最老的元素(不操作minFreq,因为没有删除的相关方法,cache只增不减)
*/
class LFUCache {
    // capacity > 0
    public LFUCache(int capacity) {}
    // O(1)
    public int get(int key) {}
    // O(1)
    public void put(int key, int value) {}
}


栈实现队列(双栈做队列)

💚232. 用栈实现队列

💚剑指 Offer 09. 用两个栈实现队列

队列实现栈(双队列做栈) -- 很没意思,但是还是练练吧

💚225. 用队列实现栈

最小栈

💛155. 最小栈


单调栈

输入一个数组nums,请返回一个等长的结果数组,结果数组中对应索引存储着下一个更大元素,如果没有更大的元素,就存 -1。函数签名如下:

int[] nextGreaterElement(int[] nums);
/*
nums = [2,1,2,4,3]
ans  = [4,2,4,-1,-1]
*/

💚496. 下一个更大元素 I---->难度不低,认真做,优化空间占用

💛503. 下一个更大元素 II---->如何高效遍历数组

💛739. 每日温度

跳表查找、插入、删除时间复杂度都是O(log n), Redis中的zset使用了压缩列表和跳表

一篇优秀的实现文章

跳表高度h计算公式

\[h=\log_{2}{N}=\frac{\log_{}{N}}{\log_{}{2}} \]

https://leetcode.cn/problems/design-skiplist/solutions/1698876/by-ac_oier-38rd/

🧡1206. 设计跳表

跳表核心数据结构

class Node {
    private int val;
    private Node[] next;
    public Node(int value) {
        this.val = value;
        this.next = new Node[LEVEL]; // LEVEL 为基于数据范围计算出的层高
    }
}

// 添加节点,自下而上,每层的添加概率都是1/2,当前层不添加则上层都不添加
for (int i = 0; i < LEVEL; i++) {
    // 第0层是一定要添加的
    node.next[i] = res[i].next[i];
    res[i].next[i] = node;
    if (random.nextInt(2) == 0) { // 取值范围 [0,2) == {0,1}
        break;
    }
}


前缀树Trie,用于处理字符串前缀。

通常,题目限制在英文小写字母,即总共的选择空间为 ['a'...'z']

因此只需要建立一个26叉树即可

注意,不考虑删除节点的情况

核心结构

/* Trie 树节点实现 */
class TrieNode<V> {
    V val = null;
    TrieNode<V>[] children = new TrieNode[256];
}

// 核心方法
void insert(String word){}
boolean search(String word){}// 树中是否完整存储了word字符串
boolean startsWith(String prefix){} // 树中是否存在prefix前缀

💛208. 实现 Trie (前缀树)

💛1804. 实现 Trie (前缀树) II --> 将判断是否变成了判断数量

实现前缀树 Trie 类:

  • Trie() 初始化前缀树对象。
  • void insert(String word) 将字符串 word 插入前缀树中。
  • int countWordsEqualTo(String word) 返回前缀树中字符串 word 的实例个数。
  • int countWordsStartingWith(String prefix) 返回前缀树中以 prefix 为前缀的字符串个数。
  • void erase(String word) 从前缀树中移除字符串 word
输入
["Trie", "insert", "insert", "countWordsEqualTo", "countWordsStartingWith", "erase", "countWordsEqualTo", "countWordsStartingWith", "erase", "countWordsStartingWith"]
[[], ["apple"], ["apple"], ["apple"], ["app"], ["apple"], ["apple"], ["app"], ["apple"], ["app"]]
输出
[null, null, null, 2, 2, null, 1, 1, null, 0]

解释
Trie trie = new Trie();
trie.insert("apple");               // 插入 "apple"。
trie.insert("apple");               // 插入另一个 "apple"。
trie.countWordsEqualTo("apple");    // 有两个 "apple" 实例,所以返回 2。
trie.countWordsStartingWith("app"); // "app" 是 "apple" 的前缀,所以返回 2。
trie.erase("apple");                // 移除一个 "apple"。
trie.countWordsEqualTo("apple");    // 现在只有一个 "apple" 实例,所以返回 1。
trie.countWordsStartingWith("app"); // 返回 1
trie.erase("apple");                // 移除 "apple"。现在前缀树是空的。
trie.countWordsStartingWith("app"); // 返回 0
  • 1 <= word.length, prefix.length <= 2000
  • wordprefix 只包含小写英文字母。
  • insertcountWordsEqualTocountWordsStartingWitherase 总共调用最多 3 * 104 次。
  • 保证每次调用 erase 时,字符串 word 总是存在于前缀树中。

这题比较折腾,问的是数量,因此两个数量各自维护

核心代码

class Node {
    int prefixWith; // 记录以此节点为前缀的使用数量
    int endWith;    // 记录以此节点为结尾的字符串的数量
    Node[] next = new Node[26];
}

💛648. 单词替换

💛211. 添加与搜索单词 - 数据结构设计-->比较恶心,搜索过程含多叉树遍历,慢慢写吧(建议画图)

💛677. 键值映射---->注意同值替换的逻辑

posted @ 2023-02-26 17:56  jentreywang  阅读(12)  评论(0编辑  收藏  举报