目录
和为K的子数组
1 题目描述
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
说明 :
- 数组的长度为 [1, 20,000]。
- 数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。
2 解题(Java)
前缀和 + 哈希表:
class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> cache = new HashMap<>();
int count = 0, pre = 0;
cache.put(0, 1);
for (int num : nums) {
pre += num;
count += cache.getOrDefault(pre - k, 0);
cache.put(pre, cache.getOrDefault(pre, 0) + 1);
}
return count;
}
}
3 复杂性分析
- 时间复杂度:O(n);
- 空间复杂度:O(n);
目标和
1 题目描述
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
- 例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
示例 2:
输入:nums = [1], target = 1
输出:1
提示:
- 1 <= nums.length <= 20
- 0 <= nums[i] <= 1000
- 0 <= sum(nums[i]) <= 1000
- -1000 <= target <= 1000
2 解题(Java)
树的后序遍历+记忆化搜索
class Solution {
Map<String, Integer> cache = new HashMap<>();
int target;
int[] nums;
public int findTargetSumWays(int[] nums, int target) {
this.target = target;
this.nums = nums;
return dfs(0, nums[0]) + dfs(0, -nums[0]);
}
int dfs(int index, int sum) {
String key = index + "_" + sum;
if (cache.containsKey(key)) return cache.get(key);
if (index == nums.length - 1) {
cache.put(key, sum == target ? 1 : 0);
return cache.get(key);
}
int left = dfs(index + 1, sum + nums[index + 1]);
int right = dfs(index + 1, sum - nums[index + 1]);
cache.put(key, left + right);
return cache.get(key);
}
}
3 复杂性分析
前 K 个高频元素
1 题目描述
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
提示:
- 1 <= nums.length <= 105
- k 的取值范围是 [1, 数组中不相同的元素的个数]
- 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。
2 解题(Java)
HashMap + 堆排序:
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
// int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数,根据第二个元素设计小根堆
PriorityQueue<int[]> heap = new PriorityQueue<int[]>((o1, o2) -> o1[1] - o2[1]);
for (Integer num : map.keySet()) {
if (heap.size() == k) {
if (heap.peek()[1] < map.get(num)) {
heap.poll();
heap.offer(new int[]{num, map.get(num)});
}
} else {
heap.offer(new int[]{num, map.get(num)});
}
}
int[] res = new int[k];
int i = 0;
while (!heap.isEmpty()) res[i++] = heap.poll()[0];
return res;
}
}
3 复杂性分析
- 时间复杂度:O(Nlogk),其中 N 为数组的长度。我们首先遍历原数组,并使用哈希表记录出现次数,需 O(N) 时间;随后遍历HashMap,由于堆的大小至多为 k,因此每次堆操作需要 O(logk) 的时间,需 O(Nlogk)的时间;总时间复杂度为O(Nlogk);
- 空间复杂度:O(N)。哈希表 O(N),堆 O(k),总 O(N);
最长连续序列
1 题目描述
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
进阶:你可以设计并实现时间复杂度为 O(n) 的解决方案吗?
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
提示:
- 0 <= nums.length <= 10 ^ 4
- -10 ^ 9 <= nums[i] <= 10 ^ 9
2 解题(Java)
class Solution {
public int longestConsecutive(int[] nums) {
int max = 0;
Set<Integer> set = new HashSet<>();
for (int num : nums) set.add(num);
for (int num : set) {
if (!set.contains(num - 1)) {
int temp = 1;
while (set.contains(++num)) temp++;
max = Math.max(max, temp);
}
}
return max;
}
}
3 复杂性分析
- 时间复杂度O(n):外层循环需要 O(n) 的时间复杂度;只有当一个数是连续序列的第一个数的情况下才会进入内层循环,然后在内层循环中匹配连续序列中的数,因此数组中的每个数只会进入内层循环1次,时间复杂度为O(n) ;因此总时间复杂度为 O(n);
- 空间复杂度O(n):Set存储数组中的数需要 O(n) 空间;
扑克牌中的顺子
1 题目描述
从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
示例 1:
输入: [1,2,3,4,5]
输出: True
示例 2:
输入: [0,0,1,2,5]
输出: True
限制:
- 数组长度为 5
- 数组的数取值为 [0, 13]
2 解题(Java)
class Solution {
public boolean isStraight(int[] nums) {
Set<Integer> repeat = new HashSet<>();
int max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;
for(int num : nums) {
if(num == 0) continue; // 跳过大小王
max = Math.max(max, num); // 最大牌
min = Math.min(min, num); // 最小牌
if(!repeat.add(num)) return false; // 添加此牌至set,若有重复,返回 false
}
return max - min < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
}
}
3 复杂性分析
- 时间复杂度 O(N) = O(5) = O(1): 其中 N 为 nums 长度,本题中 N≡5 ,遍历数组使用 O(N) 时间;
- 空间复杂度 O(N) = O(5) = O(1): 用于判重的辅助 Set 占用 O(N) 额外空间;
最小覆盖子串
1 题目描述
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
示例 2:
输入:s = “a”, t = “a”
输出:“a”
提示:
- 1 <= s.length, t.length <= 10 ^ 5
- s 和 t 由英文字母组成
进阶:你能设计一个在 o(n) 时间内解决此问题的算法吗?
2 解题(Java)
class Solution {
public String minWindow(String s, String t) {
Map<Character, Integer> tMap = new HashMap<>();
Map<Character, Integer> windowMap = new HashMap<>();
// 记录t中所有字符及其出现的次数
for (char c : t.toCharArray()) {
tMap.put(c, tMap.getOrDefault(c, 0) + 1);
}
int left = 0, right = 0;
// 记录窗口中满足条件的字符个数
int count = 0;
// 记录最小覆盖子串的起始索引及长度
int start = 0, minLength = Integer.MAX_VALUE;
while (right < s.length()) {
char c = s.charAt(right);
// 判断取出的字符是否在t中
if (tMap.containsKey(c)) {
windowMap.put(c, windowMap.getOrDefault(c, 0) + 1);
// 判断取出的字符在窗口中出现的次数是否与t中该字符的出现次数相同
if (windowMap.get(c).equals(tMap.get(c))) {
count++;
}
}
// 找到符合条件的子串后,尝试缩小窗口
while (count == tMap.size()) {
if (right - left + 1 < minLength) {
start = left;
minLength = right - left + 1;
}
char c1 = s.charAt(left);
left++;
if (tMap.containsKey(c1)) {
if (windowMap.get(c1).equals(tMap.get(c1))) {
count--;
}
windowMap.put(c1, windowMap.get(c1) - 1);
}
}
// 尝试新方案
right++;
}
return minLength == Integer.MAX_VALUE ? "" : s.substring(start, start + minLength);
}
}
3 复杂性分析
- 时间复杂度O(n):n为s的长度,线性遍历一次s;
- 空间复杂度O(n):HashMap所占空间;
字母异位词分组
1 题目描述
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出:
[
[“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”]
]
说明:
- 所有输入均为小写字母。
- 不考虑答案输出的顺序。
2 解题(Java)
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String str :strs) {
char[] array = str.toCharArray();
Arrays.sort(array);
String key = new String(array);
List<String> list = map.getOrDefault(key, new ArrayList<>());
list.add(str);
map.put(key, list);
}
return new ArrayList<List<String>>(map.values());
}
}
3 复杂性分析
- 时间复杂度O(nklogk):其中 n 是 strs 中的字符串的数量,k 是 strs 中的字符串的的最大长度。需要遍历 n 个字符串,对于每个字符串,需要 O(klogk) 的时间进行排序以及 O(1) 的时间更新哈希表,因此总时间复杂度是 O(nklogk);
- 空间复杂度O(nk):其中 n 是 strs 中的字符串的数量,k 是 strs 中的字符串的的最大长度,需要用哈希表存储全部字符串;
两数之和
1 题目描述
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
2 解题(Java)
class Solution {
public int[] twoSum(int[] nums, int target) {
if (nums == null || nums.length < 2) return new int[0];
Map<Integer,Integer> map = new HashMap<>();
for (int i=0; i<nums.length; i++) {
if (map.containsKey(target-nums[i])) {
return new int[]{map.get(target-nums[i]), i};
}
map.put(nums[i], i);
}
return new int[0];
}
}
3 复杂度分析
- 时间复杂度:O(N),其中 N是数组中的元素数量。对于每一个元素 x,我们可以 O(1)地寻找 target - x。
- 空间复杂度:O(N),其中 N 是数组中的元素数量,主要为哈希表的开销。
复杂链表的复制
1 题目描述
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
示例 4:
输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。
提示:
- -10000 <= Node.val <= 10000
- Node.random 为空(null)或指向链表中的节点。
- 节点数目不超过 1000 。
2 解题(java)
题意理解:
本题的意思是复制一个链表并返回,在这里,复制的意思是指 深拷贝(Deep Copy),事实上,与此对应的还有 浅拷贝,它们的区别是:
- 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
- 但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
if(head == null) return null;
Node cur = head;
Map<Node, Node> map = new HashMap<>();
// 3. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
while(cur != null) {
map.put(cur, new Node(cur.val));
cur = cur.next;
}
cur = head;
// 4. 构建新链表的 next 和 random 指向
while(cur != null) {
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
// 5. 返回新链表的头节点
return map.get(head);
}
}
3 复杂性分析
- 时间复杂度 O(N) : 两轮遍历链表,使用 O(N)时间。
- 空间复杂度 O(N) : 哈希表空间。
有效的括号
1 题目描述
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 注意空字符串可被认为是有效字符串。
示例 1:
输入: “()”
输出: true
示例 2:
输入: “()[]{}”
输出: true
示例 3:
输入: “(]”
输出: false
示例 4:
输入: “([)]”
输出: false
2 解题(Java)
栈和哈希表:
class Solution {
public boolean isValid(String s) {
int n = s.length();
if (n % 2 == 1) {
return false;
}
Map<Character, Character> pairs = new HashMap<>() {{
put(')', '(');
put(']', '[');
put('}', '{');
}};
Deque<Character> stack = new LinkedList<>();
for (int i = 0; i < n; i++) {
char ch = s.charAt(i);
if (pairs.containsKey(ch)) {
if (stack.isEmpty() || stack.peek() != pairs.get(ch)) {
return false;
}
stack.pop();
} else {
stack.push(ch);
}
}
return stack.isEmpty();
}
}
3 复杂性分析
- 时间复杂度 O(N):需要遍历一遍 s;
- 空间复杂度 O(N):栈使用线性的空间大小;
能否连接形成数组
1 题目描述
给你一个整数数组 arr ,数组中的每个整数互不相同 。另有一个由整数数组构成的数组 pieces,其中的整数也互不相同 。请你以任意顺序连接 pieces 中的数组以形成 arr 。但是,不允许对每个数组 pieces[i] 中的整数重新排序。
如果可以连接 pieces 中的数组形成 arr ,返回 true ;否则,返回 false 。
示例 1:
输入:arr = [85], pieces = [[85]]
输出:true
示例 2:
输入:arr = [15,88], pieces = [[88],[15]]
输出:true
解释:依次连接 [15] 和 [88]
示例 3:
输入:arr = [49,18,16], pieces = [[16,18,49]]
输出:false
解释:即便数字相符,也不能重新排列pieces[0]
示例 4:
输入:arr = [91,4,64,78], pieces = [[78],[4,64],[91]]
输出:true
解释:依次连接[91]、[4,64] 和 [78]
示例 5:
输入:arr = [1,3,5,7], pieces = [[2,4,6,8]]
输出:false
提示:
- 1 <= pieces.length <= arr.length <= 100
- sum(pieces[i].length) == arr.length
- 1 <= pieces[i].length <= arr.length
- 1 <= arr[i], pieces[i][j] <= 100
- arr 中的整数 互不相同
- pieces 中的整数 互不相同(也就是说,如果将 pieces 扁平化成一维数组,数组中的所有整数互不相同)
2 解题(Java)
class Solution {
public boolean canFormArray(int[] arr, int[][] pieces) {
// 构造哈希表,key为pieces中各数组的首元素,值为对应的各数组
Map<Integer, int[]> map = new HashMap<>();
for (int[] piece : pieces) {
map.put(piece[0], piece);
}
// 遍历arr数组进行判定
int i = 0;
while(i < arr.length) {
int curVal = arr[i];
// 如果map中的键包含curVal,继续判定,否则返回false
if (map.containsKey(curVal)) {
// 取出curVal对应的数组,继续内循环判定,如果有一个对应不上,返回false;内循环判定成功后,回到外循环,开启下一次判定
int[] piece = map.get(curVal);
for (int value : piece) {
if (arr[i] == value) {
i++;
} else {
return false;
}
}
} else {
return false;
}
}
// 前面的判定没有问题,返回true
return true;
}
}
3 复杂性分析
- 时间复杂度O(N):N是数组arr的长度,循环遍历1次;
- 空间复杂度O(N):哈希表存储pieces,占用O(N)空间;
最长不含重复字符的子字符串
1 题目描述
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
提示:
s.length <= 40000
2 解题(Java)
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> dic = new HashMap<>();
int res = 0, tmp = 0;
for(int right = 0; right < s.length(); right++) {
int left = dic.getOrDefault(s.charAt(right), -1); // 获取索引left
dic.put(s.charAt(right), right); // 更新哈希表
tmp = tmp < right - left ? tmp + 1 : right - left; // dp[right-1] -> dp[right]
res = Math.max(res,tmp);
}
return res;
}
}
3 复杂性分析
- 时间复杂度 O(N) : 其中 N 为字符串长度,动态规划需遍历计算字符串各字符;
- 空间复杂度 O(1) : 字符的 ASCII 码范围为 0 ~ 127 ,哈希表 dic 最多使用 O(128)=O(1)大小的额外空间;
第一个只出现一次的字符
1 题目描述
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例:
s = “abaccdeff”
返回 “b”s = “”
返回 " "
限制:
0 <= s 的长度 <= 50000
2 解题(Java)
class Solution {
public char firstUniqChar(String s) {
Map<Character, Boolean> dic = new HashMap<>();
char[] cs = s.toCharArray();
for (char c : cs) {
dic.put(c, !dic.containsKey(c));
}
for (char c : cs) {
if (dic.get(c)) return c;
}
return ' ';
}
}
3 复杂性分析
- 时间复杂度 O(N) : N 为字符串 s 的长度;需遍历 s 两轮,使用 O(N) ;
- 空间复杂度 O(1) : 由于题目指出 s 只包含小写字母,因此最多有 26 个不同字符,HashMap 存储需占用 O(26) = O(1) 的额外空间。
电话号码的字母组合
1 题目描述
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
2 解题(Java)
2.1 解题思路
回溯算法:
- 用一个哈希表存储每个数字对应的所有字母;
- 回溯过程维护一个StringBuilder ans,表示字母组合;
- ans初始为空,每次取电话号码的一位数字,然后从哈希表中获得该数字对应的所有字母,并将其中一个加入ans中;
- 继续处理电话号码后一位数字,直到处理完电话号码的所有数字,即得到一个完整的字母组合,并将其加入结果res中;
- 回退,遍历其余的字母组合,依次加入res中,返回res即可;
注:
回溯算法主要用于寻找所有的可行解,如果发现一个解不可行,再回溯寻找其它解。而在本题中,由于每个数字对应的每个字母都可能进入字母组合,因此不存在不可行的解,相当于穷举所有解。
2.2 代码
class Solution {
List<String> res = new ArrayList<>();
StringBuilder ans = new StringBuilder();
Map<Character, String> dic = new HashMap<Character, String>() {{
put('2', "abc");
put('3', "def");
put('4', "ghi");
put('5', "jkl");
put('6', "mno");
put('7', "pqrs");
put('8', "tuv");
put('9', "wxyz");
}};
String digits;
public List<String> letterCombinations(String digits) {
this.digits = digits;
if (digits.length() == 0) return res;
backTrack(0);
return res;
}
public void backTrack(int index) {
if (index == digits.length()) {
res.add(ans.toString());
} else {
String letters = dic.get(digits.charAt(index));
for (int i=0; i<letters.length(); i++) {
ans.append(letters.charAt(i));
backTrack(index+1);
ans.deleteCharAt(index);
}
}
}
}
3 复杂性分析
- 时间复杂度O(3 ^ M * 4 ^ N):其中 M 是输入中对应3个字母的数字个数(包括数字 2、3、4、5、6、8),N 是输入中对应4个字母的数字个数(包括数字 7、9),当输入包含M个对应3个字母的数字和N个对应4个字母的数字时,不同的字母组合共有3 ^ M * 4 ^ N种,需要遍历每一种字母组合;
- 空间复杂度O(M + N):其中M是输入中对应3个字母的数字个数,N是输入中对应4个字母的数字个数,M+N是输入数字的总个数。除了返回值以外,空间复杂度主要取决于哈希表以及回溯过程中的递归调用层数,哈希表的大小与输入无关,可以看成常数,递归调用层数最大为M+N;
设计LRU缓存结构
1 题目描述
设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能
- set(key, value):将记录(key, value)插入该结构
- get(key):返回key对应的value值
要求
- set和get方法的时间复杂度为O(1);
- 某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的;
- 当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的;
解释
- 若opt=1,接下来两个整数x, y,表示set(x, y);
- 若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1;
- 对于每个opt2,输出一个答案;
示例1
输入
[[1,1,1],[1,2,2],[1,3,2],[2,1],[1,4,4],[2,2]],3
返回值
[1,-1]
说明
第一次操作后:最常使用的记录为(“1”, 1) 第二次操作后:最常使用的记录为(“2”, 2),(“1”, 1)变为最不常用的
第三次操作后:最常使用的记录为(“3”, 2),(“1”, 1)还是最不常用的 第四次操作后:最常用的记录为(“1”, 1),(“2”, 2)变为最不常用的
第五次操作后:大小超过了3,所以移除此时最不常使用的记录(“2”, 2),加入记录(“4”,4),并且为最常使用的记录,然后(“3”, 2)变为最不常使用的记录
备注
2 解题(Java)
import java.util.*;
public class Solution {
private Map<Integer, DLinkedNode> cache = new HashMap<>();
private int k;
// 使用伪头部和伪尾部节点
private DLinkedNode head = new DLinkedNode();
private DLinkedNode tail = new DLinkedNode();
public int[] LRU (int[][] operators, int k) {
this.k = k;
head.next = tail;
tail.prev = head;
int len = 0;
for (int i=0; i<operators.length; i++) {
if (operators[i][0] == 2) len++;
}
int[] res = new int[len];
for(int i = 0, j = 0; i < operators.length; i++) {
if(operators[i][0] == 1) {
set(operators[i][1], operators[i][2]);
} else {
res[j++] = get(operators[i][1]);
}
}
return res;
}
public int get(int key) {
if (cache.containsKey(key)) {
DLinkedNode node = cache.get(key);
moveToHead(node);
return node.value;
}
return -1;
}
public void set(int key, int value) {
if (cache.containsKey(key)) {
DLinkedNode node = cache.get(key);
node.value = value;
moveToHead(node);
} else {
if (cache.size() == k) {
removeTail();
}
DLinkedNode node = new DLinkedNode(key, value);
cache.put(key, node);
addToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void moveToHead(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
addToHead(node);
}
private void removeTail() {
int rk = tail.prev.key;
tail.prev.prev.next = tail;
tail.prev = tail.prev.prev;
cache.remove(rk);
}
class DLinkedNode {
int key, value;
DLinkedNode prev, next;
public DLinkedNode() {}
public DLinkedNode(int key, int value) {
this.key = key;
this.value = value;
}
}
}