文章目录
460. LFU 缓存⭐(数据结构题)
https://leetcode.cn/problems/lfu-cache/description/?envType=daily-question&envId=2023-09-25
提示:
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
解法——找数学规律
队列长度是 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. 餐厅过滤器(简单模拟)
提示:
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. 花期内花的数目⭐
提示:
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
能种就种,比较数量。
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. 全部开花的最早一天(贪心)
先种长的慢的。
提示:
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)
解法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;
}
}