手把手设计数据结构
一、手撸 LRU 算法
基于labuladong的算法网站,算法就像搭乐高:带你手撸 LRU 算法;
1、LRU基本介绍
LRU:
- least recently used(最近最少使用算法);
- 属于一种缓存淘汰策略,认为最近使用过的数据应该是有用的,很久都没有用过的数据是无用的,内存满了就优先删除很久没有用过的数据。
2、算法描述
力扣第146题,LRU 缓存;关于代码的初始模板如下:
class LRUCache {
public LRUCache(int capacity) {
}
public int get(int key) {
}
public void put(int key, int 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);
*/
首先需要接受一个 capacity 参数作为缓存的最大容量,然后实现两个API:
- put(key,value):存入键值对;
- get(key):获取key对应的value值;
3、算法设计
根据算法描述可以得到这个LRUCache的数据结构必要的条件为:
- cache中的元素必须时时有序,便于区分最近使用和最久使用的数据;
- 能快速在cache中查找到某个key是否存在并且返回对应的value值;
- 每次访问cache中的某个key时,需要讲这个元素变为最近使用的;
故这个LRUCache的数据结构就是哈希链表,通过哈希表快速查找,链表进行增删改;
4、代码实现
Java中内置了哈希链表,为LinkedHashMap,但先试试自己造轮子;
(1)节点
先写出双向链表的节点类型的数据结构如下:
class Node {
int key;
int value;
Node next;
Node prev;
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
(2)双向链表
// 双端链表
class DoubleList {
private Node head, tail;// 虚拟的头尾节点
private int size;// 链表中节点个数
// 构造器初始化
public DoubleList() {
this.head = new Node(0, 0);
this.tail = new Node(0, 0);
size = 0;
head.next = tail;
tail.prev = head;
}
// 在链表的尾部添加元素
void addLast(Node node) {
// 未插入前最后元素的指针
tail.prev.next = node;
// node的指针
node.prev = tail.prev;
node.next = tail;
// 修改tail的指针
tail.prev = node;
// 链表的元素个数
size++;
}
// 删除链表中的节点x
void remove(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
size--;
}
// 删除链表中的第一个节点,并返回该节点
Node removeFirst() {
if (head.next == tail) {
return null;
}
Node first = head.next;
remove(first);
return first;
}
// 返回链表的长度
int size() {
return this.size;
}
}
重点:
根据双向链表的API可知,每次删除的都是最后一个元素,加入的也是最后一个元素,故链表的尾部为最近使用的元素,头部为最久使用的元素;
(3)LRU代码框架
// LRU算法的代码框架
class LRUCache {
private HashMap<Integer, Node> map;// map映射到node
private DoubleList list;// 双向链表
private int capacity;// 能存储的最大容量
// get方法
int get(int key) {
if (!map.containsKey(key)) {
return -1;
}
makeRecently(key);
return map.get(key).value;
}
// put 方法
void put(int key, int value) {
Node node = new Node(key, value);
if (map.containsKey(key)) {
deleteKey(key);
addRecently(key, value);
}
if (capacity == list.size()) {
removeLeastRecently();
}
addRecently(key, value);
}
// 构造初始化
public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>();
list = new DoubleList();
}
// 将key设置为最近使用的
void makeRecently(int key) {
Node node = map.get(key);
// 删除
list.remove(node);
// 添加
list.addLast(node);
}
// 添加元素
void addRecently(int key, int value) {
Node node = new Node(key, value);
map.put(key, node);
list.addLast(node);
}
// 删除一个key
void deleteKey(int key) {
Node node = map.get(key);
map.remove(key);
list.remove(node);
}
// 删除最久未使用的元素
void removeLeastRecently() {
Node node = list.removeFirst();
map.remove(node.key);
}
}
5、LRU 缓存
力扣第146题,LRU 缓存;
[146]LRU 缓存
//leetcode submit region begin(Prohibit modification and deletion)
class LRUCache {
int capacity;
LinkedHashMap<Integer, Integer> cache = new LinkedHashMap<>();
public LRUCache(int capacity) {
this.capacity = capacity;
}
public int get(int key) {
if (!cache.containsKey(key)) {
return -1;
}
makeRecently(key);
return cache.get(key);
}
public void put(int key, int value) {
// 判断是否存在key
if (cache.containsKey(key)) {
// 修改值
cache.put(key, value);
// 变为最近使用
makeRecently(key);
return;
}
// 判断容量
if (capacity == cache.size()) {
// 删除最后一个元素
int oldKey = cache.keySet().iterator().next();
cache.remove(oldKey);
}
cache.put(key, value);
}
void makeRecently(int key) {
int value = cache.get(key);
cache.remove(key);
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);
*/
//leetcode submit region end(Prohibit modification and deletion)
二、前缀树算法模板秒杀五道算法题
基于labuladong的算法网站,前缀树算法模板秒杀五道算法题;
1、基本介绍
前缀树:
- 前缀树,trie,处理字符串前缀相关的操作;
2、相关题目
(1)实现 Trie (前缀树)
力扣第208题,实现 Trie (前缀树);
[208]实现 Trie(前缀树)
//leetcode submit region begin(Prohibit modification and deletion)
class Trie {
TrieNode root;
public Trie() {
root = new TrieNode();
}
// 插入 word 到前缀树中
public void insert(String word) {
TrieNode temp = root;
for (char c : word.toCharArray()) {
int next = c - 'a';
if (temp.children[next] == null) {
temp.children[next] = new TrieNode();
}
temp = temp.children[next];
}
temp.end = true;
}
// 判断 word 是否在前缀树中存在
public boolean search(String word) {
TrieNode p = root;
for (char c : word.toCharArray()) {
int next = c - 'a';
if (p.children[next] == null) {
return false;
}
p = p.children[next];
}
return p.end;
}
// 判断该前缀是否存在
public boolean startsWith(String prefix) {
TrieNode p = root;
for (char c : prefix.toCharArray()) {
int next = c - 'a';
if (p.children[next] == null) {
return false;
}
p = p.children[next];
}
return true;
}
}
// 定义一个前缀树的节点类
class TrieNode {
boolean end;// 是否是最后的节点
TrieNode[] children = new TrieNode[26];// 该节点后的子节点数组
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/
//leetcode submit region end(Prohibit modification and deletion)
(2)添加与搜索单词 - 数据结构设计
力扣第211题,添加与搜索单词 - 数据结构设计;
[211]添加与搜索单词-数据结构设计
//leetcode submit region begin(Prohibit modification and deletion)
class WordDictionary {
Node root;
// 初始化
public WordDictionary() {
root = new Node();
}
public void addWord(String word) {
Node node = root;
for (char c : word.toCharArray()) {
int next = c - 'a';
if (node.children[next] == null) {
node.children[next] = new Node();
}
node = node.children[next];
}
node.isEnd = true;
}
public boolean search(String word) {
return find(word, root, 0);
}
/**
* @param word:需要搜索的字符串
* @param root:当前在的节点位置
* @param index:目前字符串的位置
* @return 是否匹配到字符串
*/
public boolean find(String word, Node root, int index) {
// 如果当前节点为空,则未匹配到
if (root == null) {
return false;
}
// 如果来到最后一个元素
if (index == word.length()) {
return root.isEnd;
}
// 判断当前位置
int next = word.charAt(index) - 'a';
// 需要判断是否是 '.'
if ('.' == word.charAt(index)) {
for (int i = 0; i < 26; i++) {
if (find(word, root.children[i], index + 1)) {
return true;
}
}
return false;
} else {
return find(word, root.children[next], index + 1);
}
}
}
// 定义一个前缀树节点类
class Node {
boolean isEnd;// 标记是否是最后一个字符串
Node[] children = new Node[26];// 标记子节点数组
}
/**
* Your WordDictionary object will be instantiated and called as such:
* WordDictionary obj = new WordDictionary();
* obj.addWord(word);
* boolean param_2 = obj.search(word);
*/
//leetcode submit region end(Prohibit modification and deletion)
(3)单词替换
力扣第648题,单词替换;
[648]单词替换
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public String replaceWords(List<String> dictionary, String sentence) {
Trie trie = new Trie();
Node root = trie.generate(dictionary);
// 根据 sentence 开始替换流程
String[] oArr = sentence.split(" ");
StringBuffer sb = new StringBuffer();
// 开始遍历
for (String word : oArr) {
String find = trie.search(word);
if ("".equals(find)) {
sb.append(word).append(" ");
} else {
sb.append(find).append(" ");
}
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
}
// 定义一个前缀树类
class Trie {
Node root;
public Trie() {
root = new Node();
}
// 将词根字典变成前缀树
public Node generate(List<String> dictionary) {
for (String s : dictionary) {
add(s);
}
return root;
}
// 将一个字符串添加到root为根节点的前缀树中
public void add(String word) {
Node node = root;
for (char c : word.toCharArray()) {
int next = c - 'a';
if (node.children[next] == null) {
node.children[next] = new Node();
}
node = node.children[next];
}
node.isEnd = true;
}
// 查找 word 是否存在前缀树中
public String search(String word) {
Node node = root;
StringBuffer sb = new StringBuffer();
for (char c : word.toCharArray()) {
int next = c - 'a';
if (node.isEnd) {
return sb.toString();
}
if (node.children[next] == null) {
return "";
}
sb.append(c);
node = node.children[next];
}
return sb.toString();
}
}
// 定义一个前缀树类的节点
class Node {
boolean isEnd;
Node[] children = new Node[26];
}
//leetcode submit region end(Prohibit modification and deletion)
(4)键值映射
力扣第677题,键值映射;
[677]键值映射
//leetcode submit region begin(Prohibit modification and deletion)
class MapSum {
Node root;
public MapSum() {
root = new Node();
}
public void insert(String key, int val) {
Node node = root;
for (char c : key.toCharArray()) {
int next = c - 'a';
if (node.children[next] == null) {
node.children[next] = new Node();
}
node = node.children[next];
}
node.value = val;
}
public int sum(String prefix) {
Node node = root;
for (char c : prefix.toCharArray()) {
int next = c - 'a';
if (node.children[next] == null) {
return 0;
}
node = node.children[next];
}
return getAllSum(node);
}
// 找到以 root 为根节点的全部子树的和
public int getAllSum(Node root) {
if (root == null) {
return 0;
}
int res = 0;
for (int i = 0; i < 26; i++) {
res += getAllSum(root.children[i]);
}
return res + root.value;
}
}
class Node {
int value;
Node[] children = new Node[26];
}
/**
* Your MapSum object will be instantiated and called as such:
* MapSum obj = new MapSum();
* obj.insert(key,val);
* int param_2 = obj.sum(prefix);
*/
//leetcode submit region end(Prohibit modification and deletion)
(5)实现一个魔法字典
力扣第676题,实现一个魔法字典;
[676]实现一个魔法字典
//leetcode submit region begin(Prohibit modification and deletion)
class MagicDictionary {
Node root;
public MagicDictionary() {
root = new Node();
}
public void buildDict(String[] dictionary) {
for (String s : dictionary) {
add(s);
}
}
public void add(String word) {
Node node = root;
for (char c : word.toCharArray()) {
int next = c - 'a';
if (node.children[next] == null) {
node.children[next] = new Node();
}
node = node.children[next];
}
node.isEnd = true;
}
public boolean search(String searchWord) {
Node node = root;
for (int i = 0; i < searchWord.length(); i++) {
if (find(node, searchWord, 0, i)) {
return true;
}
}
return false;
}
/**
* 找到是否可以在只替换一个字母的前提下,将字符串与字典中的任意字符匹配
*
* @param node:当前所处的节点
* @param word:字符串
* @param index:当前要查找的字符串位置
* @param changeId:可以替换的位置
* @return
*/
public boolean find(Node node, String word, int index, int changeId) {
// 如果当前节点为空,那么不存在该字符
if (node == null) {
return false;
}
// 如果来到最后一个位置
if (index == word.length()) {
return node.isEnd;
}
// 找到下一个要去的位置
int next = word.charAt(index) - 'a';
// 判断当前位置是否是可以替换掉字母的位置
if (index == changeId) {
for (int i = 0; i < 26; i++) {
if (i == next) {
continue;
}
if (find(node.children[i], word, index + 1, changeId)) {
return true;
}
}
return false;
}
return find(node.children[next], word, index + 1, changeId);
}
}
class Node {
boolean isEnd;
Node[] children = new Node[26];
}
/**
* Your MagicDictionary object will be instantiated and called as such:
* MagicDictionary obj = new MagicDictionary();
* obj.buildDict(dictionary);
* boolean param_2 = obj.search(searchWord);
*/
//leetcode submit region end(Prohibit modification and deletion)
三、一道求中位数的算法题把我整不会了
基于labuladong的算法网站的,一道求中位数的算法题把我整不会了;
力扣第295题, 数据流的中位数;
- 该题的核心在于维护两个优先级队列,分别为大顶堆和小顶堆;
- 保证两个队列的元素个数差 ≤ 1;
- 保证大顶堆的整体元素大于小顶堆的元素;
[295]数据流的中位数
//leetcode submit region begin(Prohibit modification and deletion)
class MedianFinder {
// 利用两个优先级队列,分别为大根堆和小根堆
private PriorityQueue<Integer> small;
private PriorityQueue<Integer> large;
public MedianFinder() {
small = new PriorityQueue();
large = new PriorityQueue<>((Integer a, Integer b) -> {
return b - a;
});
}
public void addNum(int num) {
if (small.size() >= large.size()) {
small.add(num);
large.add(small.poll());
} else {
large.add(num);
small.add(large.poll());
}
}
public double findMedian() {
if (small.size() > large.size()) {
return small.peek();
} else if (small.size() < large.size()) {
return large.peek();
} else {
return (small.peek() + large.peek()) / 2.0;
}
}
}
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/
//leetcode submit region end(Prohibit modification and deletion)
四、单调栈结构解决三道算法题
基于labuladong的算法网站,单调栈结构解决三道算法题;
1、单调栈模板
问题:
- 输入一个数组nums,返回一个等长的结果数组;
- 结果数组中存储对应索引元素值存储的下一个更大元素,如果没有下一个更大元素值,就返回-1;
思路:
- 把元素当成人的身高,朝后看,如果看见比自己高的人就是值;
int[] nextGreaterElement(int[] nums) {
int length = nums.length;
int[] res = new int[length];
// 栈结构
Stack<Integer> stack = new Stack<>();
// 利用栈结构,将元素从后朝前遍历
for (int i = length - 1; i >= 0; i--) {
// 先判断栈顶元素和当前元素值的大小关系
while (!stack.isEmpty() && stack.peek() <= nums[i]) {
stack.pop();
}
res[i] = stack.isEmpty() ? -1 : stack.peek();
stack.add(nums[i]);
}
return res;
}
2、问题变形
(1)下一个更大元素 I
力扣第496题,下一个更大元素 I;
[496]下一个更大元素 I
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
// 将数组 nums2 的下一个最大元素全部找到
int[] next = nextGreater(nums2);
// 存储到map中
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums2.length; i++) {
map.put(nums2[i], next[i]);
}
// nums1 是 nums2 的子集
int[] res = new int[nums1.length];
for (int i = 0; i < nums1.length; i++) {
res[i] = map.get(nums1[i]);
}
return res;
}
int[] nextGreater(int[] nums) {
int length = nums.length;
int[] res = new int[length];
Stack<Integer> stack = new Stack<Integer>();
for (int i = length - 1; i >= 0; i--) {
while (!stack.isEmpty() && stack.peek() <= nums[i]) {
stack.pop();
}
res[i] = stack.isEmpty() ? -1 : stack.peek();
stack.add(nums[i]);
}
return res;
}
}
//leetcode submit region end(Prohibit modification and deletion)
(2)每日温度
力扣第739题,每日温度;
[739]每日温度
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int length = temperatures.length;
int[] res = new int[length];
// --------------------------------------------------
Stack<Integer> stack = new Stack<>();
// --------------------------------------------------
for (int i = length - 1; i >= 0; i--) {
while (!stack.isEmpty() && temperatures[stack.peek()] <= temperatures[i]) {
stack.pop();
}
res[i] = stack.isEmpty() ? 0 : stack.peek() - i;
stack.add(i);
}
// --------------------------------------------------
return res;
}
}
//leetcode submit region end(Prohibit modification and deletion)
(3)下一个更大元素 II
力扣第503题,下一个更大元素 II;
[503]下一个更大元素 II
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public int[] nextGreaterElements(int[] nums) {
int length = nums.length;
int[] res = new int[length];
Stack<Integer> stack = new Stack();
for (int i = 2 * length - 1; i >= 0; i--) {
while (!stack.isEmpty() && stack.peek() <= nums[i % length]) {
stack.pop();
}
res[i % length] = stack.isEmpty() ? -1 : stack.peek();
stack.push(nums[i % length]);
}
return res;
}
}
//leetcode submit region end(Prohibit modification and deletion)
五、单调队列结构解决滑动窗口问题
基于labuladong的算法网站,单调队列结构解决滑动窗口问题;
1、滑动窗口最大值
力扣第239题,滑动窗口最大值;
[239]滑动窗口最大值
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
// 使用优先级队列,存储元素值和索引
PriorityQueue<int[]> queue = new PriorityQueue<>((int[] a, int[] b) -> {
return b[1] - a[1];
});
// 开始滑动窗口
int index = 0;
int[] res = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
// 加入到优先级队列中
queue.add(new int[]{i, nums[i]});
// 判断队列中的元素是否为k个
if (i >= k - 1) {
while (queue.peek()[0] <= i - k) {
queue.poll();
}
res[index++] = queue.peek()[1];
}
}
return res;
}
}
//leetcode submit region end(Prohibit modification and deletion)
2、剑指 Offer 59 - I. 滑动窗口的最大值
[剑指 Offer 59-I]滑动窗口的最大值
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int length = nums.length;
// 结果数组
int[] res = new int[length - k + 1];
// 优先级队列
PriorityQueue<int[]> queue = new PriorityQueue<>((int[] a, int[] b) -> {
return b[1] - a[1];
});
// 记录下标
int index = 0;
// 开始遍历所有的元素
for (int i = 0; i < length; i++) {
queue.add(new int[]{i, nums[i]});
// 判断此时队列中的元素值,是否达到了k个
if (i >= k - 1) {
// 并且还要判断此时队列中的最大元素的索引位置是否越界
while (queue.peek()[0] <= i - k) {
queue.poll();
}
res[index++] = queue.peek()[1];
}
}
return res;
}
}
//leetcode submit region end(Prohibit modification and deletion)
3、面试题59 - II. 队列的最大值
[面试题59-II]队列的最大值
//leetcode submit region begin(Prohibit modification and deletion)
class MaxQueue {
Queue<Integer> queue;// 存储顺序
Deque<Integer> deque;// 存储最大值
public MaxQueue() {
queue = new LinkedList<>();
deque = new LinkedList<>();
}
public int max_value() {
return deque.isEmpty() ? -1 : deque.peekFirst();
}
public void push_back(int value) {
queue.offer(value);
while (!deque.isEmpty() && deque.peekLast() < value) {
deque.pollLast();
}
deque.offerLast(value);
}
public int pop_front() {
if (queue.isEmpty()) return -1;
if (queue.peek().equals(deque.peekFirst())) {
deque.pollFirst();
}
return queue.poll();
}
}
/**
* Your MaxQueue object will be instantiated and called as such:
* MaxQueue obj = new MaxQueue();
* int param_1 = obj.max_value();
* obj.push_back(value);
* int param_3 = obj.pop_front();
*/
//leetcode submit region end(Prohibit modification and deletion)
六、队列实现栈以及栈实现队列
基于labuladong的算法网站,队列实现栈以及栈实现队列;
1、用栈实现队列
力扣第232题,用栈实现队列;
[232]用栈实现队列
//leetcode submit region begin(Prohibit modification and deletion)
class MyQueue {
// 用两个栈去实现队列
Stack<Integer> inStack;// 入栈
Stack<Integer> outStack;// 出栈
public MyQueue() {
inStack = new Stack<>();
outStack = new Stack<>();
}
public void push(int x) {
inStack.push(x);
}
public int pop() {
// 如果 outStack 为空,先将 inStack 中所有元素放入
if (outStack.isEmpty()) {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
return outStack.pop();
}
public int peek() {
if (!outStack.isEmpty()) {
return outStack.peek();
}
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
return outStack.peek();
}
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
//leetcode submit region end(Prohibit modification and deletion)
2、用队列实现栈
力扣第225题,用队列实现栈;
[225]用队列实现栈
//leetcode submit region begin(Prohibit modification and deletion)
class MyStack {
// 利用队列
LinkedList<Integer> queue;
int topNum;
public MyStack() {
queue = new LinkedList<>();
topNum = 0;// 栈顶元素
}
public void push(int x) {
// 直接加入到队列中,并更新栈顶的元素记录值
queue.add(x);
topNum = x;
}
// 移除并返回栈顶元素
public int pop() {
// 注意需要更新新的栈顶元素记录值
int size = queue.size();
while (size > 2) {
queue.add(queue.poll());
size--;
}
topNum = queue.peek();
queue.add(queue.poll());
return queue.poll();
}
public int top() {
// 返回栈顶元素
return topNum;
}
public boolean empty() {
return queue.isEmpty();
}
}
/**
* Your MyStack object will be instantiated and called as such:
* MyStack obj = new MyStack();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.top();
* boolean param_4 = obj.empty();
*/
//leetcode submit region end(Prohibit modification and deletion)
七、设计朋友圈时间线功能
基于labuladong的算法网站,设计朋友圈时间线功能;
1、面向对象设计
需要两个辅助类,分别为Tweet、User;
Tweet:
- 记录帖子id;
- 记录这个帖子发表的时间;
- 需要有一个指针,指向以前发过的帖子;
class Tweet {
int id;// 帖子id
int time;// 帖子发表的时间
Tweet next;// 指向以前发过的帖子
public Tweet(int id, int time) {
this.id = id;
this.time = time;
this.next = null;
}
}
User:
- 记录用户id;
- 一个set集合,记录该用户关注的人,不能重复关注,所以用set集合;
- 该用户发过的帖子链表,从最近发帖子到最久发过的帖子,用Tweet实现,包含next指针;
- 该用户记录时间的全局变量,每发一个帖子,时间就需要相应变化;
- 实现关注人和取关人功能;
- 实现发帖子功能;
// 用户
// 用户
class User {
int id;// 用户id
HashSet<Integer> set;// 关注人的列表
Tweet head;// 虚拟发帖头节点
// 应该在Twitter类中定义一个类的成员变量维护时间
public User(int id) {
this.id = id;
this.set = new HashSet<>();
this.head = null;
set.add(id);// 默认自动关注了自己
}
// 关注人
public void follow(int userId) {
set.add(userId);
}
// 取关人
public void unfollow(int userId) {
if (this.id == userId) {
return;
}
set.remove(userId);
}
// 发帖子
public void post(int tweetId) {
Tweet tweet = new Tweet(this.id, time);
time++;
tweet.next = head;
head = tweet;
}
}
真正的Twitter的API实现:
- 需要有一个记录时间的全局变量;
- 需要有一个map,记录userId和user的映射关系;
2、代码实现
[355]设计推特
//leetcode submit region begin(Prohibit modification and deletion)
class Twitter {
// 定义一个全局变量时间
public static int time = 0;
// 定义一个map 作为 userId 和 user 的映射
Map<Integer, User> map;
// 定义一个 User 类
class User {
int userId;
Set<Integer> set;// 用户关注人的集合
Tweet head;// 用户发过的帖子的头帖子
public User(int userId) {
this.userId = userId;
this.set = new HashSet<>();
this.head = null;
// 自动关注自己
set.add(userId);
}
// 关注用户
public void follow(int userId) {
this.set.add(userId);
}
// 取关用户
public void unfollow(int userId) {
// 不能取关自己
if (userId == this.userId) {
return;
}
this.set.remove(userId);
}
// 自己本人发帖子
public void post(int tweetId) {
Tweet tweet = new Tweet(tweetId, time);
// 改变时间
time++;
tweet.next = head;
head = tweet;
}
}
// 定义一个 Tweet 类
class Tweet {
int tweetId;
int time;
Tweet next;
public Tweet(int tweetId, int time) {
this.tweetId = tweetId;
this.time = time;
this.next = null;
}
}
public Twitter() {
map = new HashMap<>();
}
/**
* @param userId:用户id
* @param tweetId:推特id
*/
public void postTweet(int userId, int tweetId) {
// 判断用户是否存在,如果不存在需要新建用户
if (!map.containsKey(userId)) {
map.put(userId, new User(userId));
}
User user = map.get(userId);
user.post(tweetId);
}
/**
* @param userId:用户id
* @return 返回用户关注的最新十条帖子
*/
public List<Integer> getNewsFeed(int userId) {
// 返回结果类
List<Integer> res = new ArrayList<>();
// 判断用户id是否存在
if (!map.containsKey(userId)) {
return res;
}
// 利用优先级队列
PriorityQueue<Tweet> queue = new PriorityQueue<>((Tweet a, Tweet b) -> {
return b.time - a.time;
});
// 找到用户关注的所有用户,并将所有用户的帖子(包括用户自己的帖子)放入优先级队列中
User user = map.get(userId);
Set<Integer> set = user.set;
for (int id : set) {
Tweet head = map.get(id).head;
while (head != null) {
queue.add(head);
head = head.next;
}
}
// 拿取队列中最前是个元素
int size = 0;
while (!queue.isEmpty()) {
if (size == 10) {
return res;
}
res.add(queue.poll().tweetId);
size++;
}
return res;
}
/**
* @param followerId:用户
* @param followeeId:被用户关注的id
*/
public void follow(int followerId, int followeeId) {
// 判断用户是否存在
if (!map.containsKey(followerId)) {
map.put(followerId, new User(followerId));
}
// 判断被用户关注的人是否存在
if (!map.containsKey(followeeId)) {
map.put(followeeId, new User(followeeId));
}
// 让用户去关注用户
map.get(followerId).follow(followeeId);
}
public void unfollow(int followerId, int followeeId) {
// 判断用户是否存在
if (map.containsKey(followerId)) {
// 让用户取关用户
map.get(followerId).unfollow(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);
*/
//leetcode submit region end(Prohibit modification and deletion)