039、设计数据结构(labuladong)

手把手设计数据结构

一、手撸 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. 滑动窗口的最大值

[剑指 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. 队列的最大值

[面试题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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值