设计笔记
设计笔记
聚焦在编码设计数据结构、复现数据结构,对常见数据结构的要求熟练掌握,题目较少
💚 -- Easy
💛 -- Medium
🧡 -- Hard
题目顺序,codetop.cc 默认排序规则
两个缓存更新:LFU(频率 frequency)、LRF(最近recently)
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) {}
}
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) {}
}
栈实现队列(双栈做队列)
队列实现栈(双队列做栈) -- 很没意思,但是还是练练吧
最小栈
单调栈
输入一个数组
nums
,请返回一个等长的结果数组,结果数组中对应索引存储着下一个更大元素,如果没有更大的元素,就存 -1。函数签名如下:int[] nextGreaterElement(int[] nums); /* nums = [2,1,2,4,3] ans = [4,2,4,-1,-1] */
💚496. 下一个更大元素 I---->难度不低,认真做,优化空间占用
💛503. 下一个更大元素 II---->如何高效遍历数组
跳表查找、插入、删除时间复杂度都是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/
跳表核心数据结构
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前缀
💛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
word
和prefix
只包含小写英文字母。insert
、countWordsEqualTo
、countWordsStartingWith
和erase
总共调用最多3 * 104
次。- 保证每次调用
erase
时,字符串word
总是存在于前缀树中。
这题比较折腾,问的是数量,因此两个数量各自维护
核心代码
class Node {
int prefixWith; // 记录以此节点为前缀的使用数量
int endWith; // 记录以此节点为结尾的字符串的数量
Node[] next = new Node[26];
}
💛211. 添加与搜索单词 - 数据结构设计-->比较恶心,搜索过程含多叉树遍历,慢慢写吧(建议画图)
💛677. 键值映射---->注意同值替换的逻辑