【LeetCode每日一题合集】2023.9.25-2023.10.1(⭐LFU缓存&Java数据流&花期内花的数量)

文章目录

  • 460. LFU 缓存⭐(数据结构题)
    • 解法1——平衡树 + 哈希表(TreeSet + HashMap) O ( l o g n ) O(logn) O(logn)
    • 解法2——双哈希表 + 双向链表 O ( 1 ) O(1) O(1) (LRU缓存的升级版)
  • 2582. 递枕头
    • 解法——找数学规律
  • 1333. 餐厅过滤器(简单模拟)
    • 写法1——手动模拟
    • 写法2——Java数组流处理⭐
  • 2251. 花期内花的数目⭐
    • 解法1——差分数组
    • 解法2——二分查找
  • 605. 种花问题(贪心)
  • 2136. 全部开花的最早一天(贪心)
  • 121. 买卖股票的最佳时机(贪心 | DP)
    • 解法1——DP
    • 解法2——贪心

460. LFU 缓存⭐(数据结构题)

https://leetcode.cn/problems/lfu-cache/description/?envType=daily-question&envId=2023-09-25

【LeetCode每日一题合集】2023.9.25-2023.10.1(⭐LFU缓存&Java数据流&花期内花的数量)_第1张图片

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

解法1——平衡树 + 哈希表(TreeSet + HashMap) O ( l o g n ) O(logn) O(logn)

自定义节点维护每个键值对的 time 和 cnt。
用 TreeSet 对节点排序,HashMap 存储 key 和 node 的对应关系。

class LFUCache {
    int capacity, time;
    Map<Integer, Node> keyTable;
    TreeSet<Node> s;

    public LFUCache(int capacity) {
        this.capacity = capacity;
        this.time = 0;
        keyTable = new  HashMap<>();
        s = new TreeSet<Node>();
    }
    
    public int get(int key) {
        if (!keyTable.containsKey(key)) return -1;
        // 取出旧的
        Node cache = keyTable.get(key);
        s.remove(cache);
        // 修改旧的 并重新放入
        cache.cnt++;
        cache.time = time++;
        s.add(cache);
        keyTable.put(key, cache);
        return cache.value;
    }
    
    public void put(int key, int value) {
        if (!keyTable.containsKey(key)) {
            if (keyTable.size() == capacity) {
                // 删除最近最少使用的
                keyTable.remove(s.first().key);
                s.remove(s.first());
            }
            // 创建新的缓存
            Node cache = new Node(1, time++, key, value);
            keyTable.put(key, cache);
            s.add(cache);
        } else {
            Node cache = keyTable.get(key);
            s.remove(cache);
            cache.cnt++;
            cache.time = time++;
            cache.value = value;
            s.add(cache);
            keyTable.put(key, cache);
        }
    }
}

class Node implements Comparable<Node> {
    int cnt, time, key, value;

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

    public boolean equals(Object anObject) {
        if (this == anObject) return true;
        if (anObject instanceof Node) {
            Node rhs = (Node) anObject;
            return this.cnt == rhs.cnt && this.time == this.time;
        }
        return false;
    }

    public int compareTo(Node rhs) {
        return cnt == rhs.cnt? time - rhs.time: cnt - rhs.cnt;
    }

    public int hashCode() {
        return cnt * 1000000007 + time;
    }
}

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

解法2——双哈希表 + 双向链表 O ( 1 ) O(1) O(1) (LRU缓存的升级版)

一个哈希表存储 key 和 Node 之间的关系。另一个哈希表存储 freq 和 DoublyLinkedList 的对应关系。
每个使用频率对应一个双向链表,双向链表维护了该频次所有节点的先后顺序,头节点是最近被使用的,尾节点是最不常被使用的。(类似LRU缓存那道题)。

同时维护一个变量 minFreq,方便快速确定最低使用的频率。

class LFUCache {
    int minFreq, capacity;
    Map<Integer, Node> keyTable;
    Map<Integer, DoublyLinkedList> freqTable;

    public LFUCache(int capacity) {
        this.minFreq = 0;
        this.capacity = capacity;
        keyTable = new HashMap<Integer, Node>();
        freqTable = new HashMap<Integer, DoublyLinkedList>();
    }
    
    public int get(int key) {
        if (capacity == 0) {
            return -1;
        }
        if (!keyTable.containsKey(key)) {
            return -1;
        }
        Node node = keyTable.get(key);
        int val = node.val, freq = node.freq;
        freqTable.get(freq).remove(node);
        // 如果当前链表为空,我们需要在哈希表中删除,且更新minFreq
        if (freqTable.get(freq).size == 0) {
            freqTable.remove(freq);
            if (minFreq == freq) {
                minFreq += 1;
            }
        }
        // 插入到 freq + 1 中
        DoublyLinkedList list = freqTable.getOrDefault(freq + 1, new DoublyLinkedList());
        list.addFirst(new Node(key, val, freq + 1));
        freqTable.put(freq + 1, list);
        keyTable.put(key, freqTable.get(freq + 1).getHead());
        return val;
    }
    
    public void put(int key, int value) {
        if (!keyTable.containsKey(key)) {
            // 缓存已满,需要删除
            if (keyTable.size() == capacity) {
                Node node = freqTable.get(minFreq).getTail();
                keyTable.remove(node.key);
                freqTable.get(minFreq).remove(node);
                if (freqTable.get(minFreq).size == 0) {
                    freqTable.remove(minFreq);
                }
            }
            // 创建新节点
            DoublyLinkedList list = freqTable.getOrDefault(1, new DoublyLinkedList());
            list.addFirst(new Node(key, value, 1));
            freqTable.put(1, list);
            keyTable.put(key, freqTable.get(1).getHead());
            minFreq = 1;
        } else {
            // 与 get 操作基本一致,除了需要更新缓存的值
            Node node = keyTable.get(key);
            int freq = node.freq;
            freqTable.get(freq).remove(node);
            if (freqTable.get(freq).size == 0) {
                freqTable.remove(freq);
                if (minFreq == freq) {
                    minFreq += 1;
                }
            }
            DoublyLinkedList list = freqTable.getOrDefault(freq + 1, new DoublyLinkedList());
            list.addFirst(new Node(key, value, freq + 1));
            freqTable.put(freq + 1, list);
            keyTable.put(key, freqTable.get(freq + 1).getHead());
        }
    }
}

class Node {
    int key, val, freq;
    Node prev, next;

    Node() {
        this(-1, -1, 0);
    }

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

class DoublyLinkedList {
    Node dummyHead, dummyTail;
    int size;

    DoublyLinkedList() {
        dummyHead = new Node();
        dummyTail = new Node();
        dummyHead.next = dummyTail;
        dummyTail.prev = dummyHead;
        size = 0;
    }

    public void addFirst(Node node) {
        Node prevHead = dummyHead.next;
        node.prev = dummyHead;
        dummyHead.next = node;
        node.next = prevHead;
        prevHead.prev = node;
        size++;
    }

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

    public Node getHead() {
        return dummyHead.next;
    }

    public Node getTail() {
        return dummyTail.prev;
    }
}


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

2582. 递枕头

https://leetcode.cn/problems/pass-the-pillow/?envType=daily-question&envId=2023-09-26
【LeetCode每日一题合集】2023.9.25-2023.10.1(⭐LFU缓存&Java数据流&花期内花的数量)_第2张图片

解法——找数学规律

队列长度是 n,每次循环传递 n - 1 次。
计算传递几次循环,以及最后一次循环完成了几次传递。

class Solution {
    public int passThePillow(int n, int time) {
        int x = time / (n - 1), y = time % (n - 1);
        if (x % 2 == 0) return y + 1;
        return n - y;
    }
}

1333. 餐厅过滤器(简单模拟)

https://leetcode.cn/problems/filter-restaurants-by-vegan-friendly-price-and-distance/description/?envType=daily-question&envId=2023-09-27

【LeetCode每日一题合集】2023.9.25-2023.10.1(⭐LFU缓存&Java数据流&花期内花的数量)_第3张图片
提示:
1 <= restaurants.length <= 10^4
restaurants[i].length == 5
1 <= idi, ratingi, pricei, distancei <= 10^5
1 <= maxPrice, maxDistance <= 10^5
veganFriendlyi 和 veganFriendly 的值为 0 或 1 。
所有 idi 各不相同。

写法1——手动模拟

class Solution {
    public List<Integer> filterRestaurants(int[][] restaurants, int veganFriendly, int maxPrice, int maxDistance) {
        List<int[]> ans = new ArrayList<>();
        for (int[] r: restaurants) {
            if (veganFriendly == 1 && r[2] == 0) continue;
            if (r[3] <= maxPrice && r[4] <= maxDistance) {
                ans.add(r);
            }
        }
        Collections.sort(ans, (x, y) -> {
            if (x[1] != y[1]) return y[1] - x[1];
            return y[0] - x[0];
        });
        List<Integer> res = new ArrayList<>();
        for (int[] x: ans) res.add(x[0]);
        return res;
    }
}

写法2——Java数组流处理⭐

class Solution {
    public List<Integer> filterRestaurants(int[][] restaurants, int veganFriendly, int maxPrice, int maxDistance) {
        Stream<int[]> stream = Arrays.stream(restaurants);
        if (veganFriendly == 1) stream = stream.filter(x -> x[2] == 1);
        return stream.filter(x -> x[3] <= maxPrice)
                .filter(x -> x[4] <= maxDistance)
                .sorted((x, y) -> x[1] != y[1]? y[1] - x[1]: y[0] - x[0])
                .map(x -> x[0])
                .collect(Collectors.toList());
    }
}

2251. 花期内花的数目⭐

https://leetcode.cn/problems/number-of-flowers-in-full-bloom/description/?envType=daily-question&envId=2023-09-28

【LeetCode每日一题合集】2023.9.25-2023.10.1(⭐LFU缓存&Java数据流&花期内花的数量)_第4张图片

提示:
1 <= flowers.length <= 5 * 10^4
flowers[i].length == 2
1 <= starti <= endi <= 10^9
1 <= people.length <= 5 * 10^4
1 <= people[i] <= 10^9

解法1——差分数组

由于花期的数据范围很大,所以使用哈希表来代替数组存储差分结果。

代码的重点是差分数组的还原过程。

class Solution {
    public int[] fullBloomFlowers(int[][] flowers, int[] people) {
        Map<Integer, Integer> m = new HashMap<>();  // diff数组
        for (int[] f: flowers) {
            int start = f[0], end = f[1];
            m.merge(start, 1, Integer::sum);
            m.merge(end + 1, -1, Integer::sum);
        }
        // 取出所有的key,从小到大排序
        int[] times = m.keySet().stream().mapToInt(Integer::intValue).sorted().toArray();
        int n = people.length, s = 0;
        int[] ans = new int[n];
        Integer[] idx = IntStream.range(0, n).boxed().toArray(Integer[]::new);
        Arrays.sort(idx, (a, b) -> people[a] - people[b]);
        // 差分数组的还原过程
        for (int i = 0, j = 0; i < n; ++i) {
            while (j < times.length && times[j] <= people[idx[i]]) {
                s += m.get(times[j++]);
            }
            ans[idx[i]] = s;
        }
        return ans;
    }
}

解法2——二分查找

计算出某个人到达之前开花的数量 x 和 花谢的数量 y,那么他对应的答案就是 x - y。这个过程可以用二分查找来做。

class Solution {
    public int[] fullBloomFlowers(int[][] flowers, int[] people) {
        int n = people.length, m = flowers.length;
        int[] ans = new int[n];
        int[] start = new int[m], end = new int[m];
        for (int i = 0; i < m; ++i) {
            start[i] = flowers[i][0];
            end[i] = flowers[i][1];
        }
        Arrays.sort(start);
        Arrays.sort(end);
        for (int i = 0; i < n; ++i) ans[i] = op(start, end, people[i]);
        return ans;
    }

    public int op(int[] start, int[] end, int time) {
        int n = start.length;
        if (start[0] > time || end[n - 1] < time) return 0;
        return bs(start, time) - bs(end, time - 1);
    }

    public int bs(int[] a, int k) {
        int l = -1, r = a.length - 1;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (a[mid] <= k) l = mid;
            else r = mid - 1;
        }
        return l;
    }
}

605. 种花问题(贪心)

https://leetcode.cn/problems/can-place-flowers/description/?envType=daily-question&envId=2023-09-29

【LeetCode每日一题合集】2023.9.25-2023.10.1(⭐LFU缓存&Java数据流&花期内花的数量)_第5张图片
能种就种,比较数量。

class Solution {
    public boolean canPlaceFlowers(int[] flowerbed, int n) {
        int k = 0, m = flowerbed.length;
        for (int i = 0; i < m; ++i) {
            if (flowerbed[i] == 0) {
                if (i - 1 >= 0 && flowerbed[i - 1] == 1) continue;
                if (i + 1 < m && flowerbed[i + 1] == 1) continue;;
                k++;
                flowerbed[i] = 1;
            }
        }
        return k >= n;
    }
}

2136. 全部开花的最早一天(贪心)

https://leetcode.cn/problems/earliest-possible-day-of-full-bloom/description/?envType=daily-question&envId=2023-09-30

【LeetCode每日一题合集】2023.9.25-2023.10.1(⭐LFU缓存&Java数据流&花期内花的数量)_第6张图片

先种长的慢的。

提示:
n == plantTime.length == growTime.length
1 <= n <= 10^5
1 <= plantTime[i], growTime[i] <= 10^4

class Solution {
    public int earliestFullBloom(int[] plantTime, int[] growTime) {
        Integer[] id = IntStream.range(0, plantTime.length).boxed().toArray(Integer[]::new);
        Arrays.sort(id, (i, j) -> growTime[j] - growTime[i]);
        int ans = 0, day = 0;
        for (int i: id) {
            day += plantTime[i];
            ans = Math.max(ans, day + growTime[i]);
        }
        return ans;
    }
}

这里学习到 IntStream 流创建数组的使用方法——Integer[] id = IntStream.range(0, plantTime.length).boxed().toArray(Integer[]::new);

121. 买卖股票的最佳时机(贪心 | DP)

【LeetCode每日一题合集】2023.9.25-2023.10.1(⭐LFU缓存&Java数据流&花期内花的数量)_第7张图片
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/?envType=daily-question&envId=2023-10-01

解法1——DP

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int[] sell = new int[n], buy = new int[n];
        buy[0] = -prices[0];
        for (int i = 1; i < n; ++i) {
            buy[i] = Math.max(buy[i - 1], -prices[i]);
            sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i]);
        }
        return sell[n - 1];
    }
}

解法2——贪心

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length, ans = 0, mn = Integer.MAX_VALUE;
        for (int price: prices) {
            mn = Math.min(mn, price);
            ans = Math.max(ans, price - mn);
        }
        return ans;
    }
}

你可能感兴趣的:(算法刷题记录,leetcode,缓存,java,LFU缓存,二分查找,差分)