From 7417736f7aab44e0937fce985d506772509719eb Mon Sep 17 00:00:00 2001 From: zhangsan <646228430@qq.com> Date: Wed, 15 Oct 2025 20:25:53 +0800 Subject: [PATCH] =?UTF-8?q?10.15=20=E5=A4=8D=E4=B9=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/All/Compress.java | 135 +++++++++++++++++ src/main/java/All/FindTheWinner.java | 33 +++++ .../All/GetLengthOfOptimalCompression.java | 140 ++++++++++++++++++ .../binary_search/FindMedianSortedArrays.java | 29 +++- src/main/java/binary_search/FindMin.java | 6 + .../dynamic_programming/CanPartition.java | 1 + src/main/java/dynamic_programming/Change.java | 2 - .../dynamic_programming/CountSubstrings.java | 1 + .../java/dynamic_programming/FindMaxForm.java | 1 + .../FindTargetSumWays.java | 1 + .../dynamic_programming/IntegerBreak.java | 1 + .../dynamic_programming/IsSubsequence.java | 2 +- .../java/dynamic_programming/MaxProduct.java | 11 +- .../java/dynamic_programming/MaxProfit4.java | 1 + .../dynamic_programming/MaximalSquare.java | 1 + .../java/dynamic_programming/MinDistance.java | 35 +++++ .../java/range/EraseOverlapIntervals.java | 1 + src/main/java/实现类功能/LRUCache.java | 2 +- 18 files changed, 393 insertions(+), 10 deletions(-) create mode 100644 src/main/java/All/Compress.java create mode 100644 src/main/java/All/FindTheWinner.java create mode 100644 src/main/java/All/GetLengthOfOptimalCompression.java diff --git a/src/main/java/All/Compress.java b/src/main/java/All/Compress.java new file mode 100644 index 0000000..44faf72 --- /dev/null +++ b/src/main/java/All/Compress.java @@ -0,0 +1,135 @@ +package All; + +/** + * 题目: 443. 压缩字符串 + * 描述:给你一个字符数组 chars ,请使用下述算法压缩: + * + * 从一个空字符串 s 开始。对于 chars 中的每组 连续重复字符 : + * + * 如果这一组长度为 1 ,则将字符追加到 s 中。 + * 否则,需要向 s 追加字符,后跟这一组的长度。 + * 压缩后得到的字符串 s 不应该直接返回 ,需要转储到字符数组 chars 中。需要注意的是,如果组长度为 10 或 10 以上,则在 chars 数组中会被拆分为多个字符。 + * + * 请在 修改完输入数组后 ,返回该数组的新长度。 + * + * 你必须设计并实现一个只使用常量额外空间的算法来解决此问题。 + * + * 注意:数组中超出返回长度的字符无关紧要,应予忽略。 + + 示例 1: + 输入:chars = ["a","a","b","b","c","c","c"] + 输出:返回 6 ,输入数组的前 6 个字符应该是:["a","2","b","2","c","3"] + 解释:"aa" 被 "a2" 替代。"bb" 被 "b2" 替代。"ccc" 被 "c3" 替代。 + + 注意:数组中超出返回长度的字符无关紧要,应予忽略。 + + * 链接:https://leetcode.cn/problems/string-compression/ + */ +public class Compress { + /* + 自己写的又臭又长 + */ + public int compress2(char[] chars) { +// Arrays.sort(chars); + int cnt=1; + char cur=chars[0]; + int read=1,write=0; + while (read 1) { + // 直接转字符串再逐位写入 + for (char c : String.valueOf(count).toCharArray()) { + chars[write++] = c; + } + } + } + + return write; + } + + private String compress1(char[] chars) { + if (chars.length == 0) return ""; + + StringBuilder sb = new StringBuilder(); + char current = chars[0]; + int cnt = 1; + + for (int i = 1; i < chars.length; i++) { + if (chars[i] == current) { + cnt++; + } else { + sb.append(current); + if (cnt > 1) sb.append(cnt); + current = chars[i]; + cnt = 1; + } + } + + // 处理最后一组字符 + sb.append(current); + if (cnt > 1) sb.append(cnt); + + return sb.toString(); + } + + public static void main(String[] args) { + Compress solution = new Compress(); + char[] chars = {'a','b','c'}; + int res=solution.compress(chars); + System.out.println(res); + System.out.println(chars); + } +} diff --git a/src/main/java/All/FindTheWinner.java b/src/main/java/All/FindTheWinner.java new file mode 100644 index 0000000..a41fa64 --- /dev/null +++ b/src/main/java/All/FindTheWinner.java @@ -0,0 +1,33 @@ +package All; +/** + * 题目: 1823. 找出游戏的获胜者 + * 描述:共有 n 名小伙伴一起做游戏。小伙伴们围成一圈,按 顺时针顺序 从 1 到 n 编号。确切地说,从第 i 名小伙伴顺时针移动一位会到达第 (i+1) 名小伙伴的位置,其中 1 <= i < n ,从第 n 名小伙伴顺时针移动一位会回到第 1 名小伙伴的位置。 + * 游戏遵循如下规则: + * 从第 1 名小伙伴所在位置 开始 。 + * 沿着顺时针方向数 k 名小伙伴,计数时需要 包含 起始时的那位小伙伴。逐个绕圈进行计数,一些小伙伴可能会被数过不止一次。 + * 你数到的最后一名小伙伴需要离开圈子,并视作输掉游戏。 + * 如果圈子中仍然有不止一名小伙伴,从刚刚输掉的小伙伴的 顺时针下一位 小伙伴 开始,回到步骤 2 继续执行。 + * 否则,圈子中最后一名小伙伴赢得游戏。 + * 给你参与游戏的小伙伴总数 n ,和一个整数 k ,返回游戏的获胜者。 + + 示例 1: + 输入:n = 5, k = 2 + 输出:3 + 解释:游戏运行步骤如下: + 1) 从小伙伴 1 开始。 + 2) 顺时针数 2 名小伙伴,也就是小伙伴 1 和 2 。 + 3) 小伙伴 2 离开圈子。下一次从小伙伴 3 开始。 + 4) 顺时针数 2 名小伙伴,也就是小伙伴 3 和 4 。 + 5) 小伙伴 4 离开圈子。下一次从小伙伴 5 开始。 + 6) 顺时针数 2 名小伙伴,也就是小伙伴 5 和 1 。 + 7) 小伙伴 1 离开圈子。下一次从小伙伴 3 开始。 + 8) 顺时针数 2 名小伙伴,也就是小伙伴 3 和 5 。 + 9) 小伙伴 5 离开圈子。只剩下小伙伴 3 。所以小伙伴 3 是游戏的获胜者。 + + * 链接:https://leetcode.cn/problems/find-the-winner-of-the-circular-game/ + */ +public class FindTheWinner { + public int findTheWinner(int n, int k) { + return 0; + } +} diff --git a/src/main/java/All/GetLengthOfOptimalCompression.java b/src/main/java/All/GetLengthOfOptimalCompression.java new file mode 100644 index 0000000..54e7931 --- /dev/null +++ b/src/main/java/All/GetLengthOfOptimalCompression.java @@ -0,0 +1,140 @@ +package All; +/** + * 【题目描述】 + * + * 给定一个仅包含大小写英文字母的字符串 s, + * 你可以删除最多 k 个字符,以使得删除后压缩字符串的长度最小。 + * + * 压缩规则如下(类似于 LeetCode 443 “字符串压缩”): + * - 将连续出现的相同字符压缩为:字符 + 出现次数; + * - 如果某个字符只出现一次,则不写次数; + * - 例如: + * "aabcccccaaa" 压缩后为 "a2bc5a3",长度为 7。 + * + * 你可以选择删除字符串中的任意 k 个字符(也可以不删除), + * 目标是使得压缩后的字符串长度最短。 + * + * 【要求返回】 + * 删除最多 k 个字符后,压缩字符串的最短可能长度。 + * + * 【示例】 + * 输入:s = "aabcccccaaa", k = 0 + * 输出:7 + * 解释: + * 压缩后为 "a2bc5a3",长度为 7。 + */ + +public class GetLengthOfOptimalCompression { + private int minLen = Integer.MAX_VALUE; + + /* + “用回溯法(DFS)枚举所有可能的删除方式, +对每个保留下来的字符串调用 compress1() 求压缩长度, +最后取最小值。” +会超时,时间复杂度非常大 + */ + public int getLengthOfOptimalCompression(String s, int k) { + if (s == null || s.length() == 0) return 0; + char[] chars = s.toCharArray(); + dfs(chars, k, 0, new StringBuilder()); + return minLen; + } + + // 回溯枚举删除情况 + private void dfs(char[] s, int k, int index, StringBuilder current) { + // 如果删除次数超过 k,直接剪枝 + if (k < 0) return; + + // 到达字符串末尾 + if (index == s.length) { + String compressed = compress1(current.toString().toCharArray()); + minLen = Math.min(minLen, compressed.length()); + return; + } + + // 选择1️⃣:保留当前字符 + current.append(s[index]); + dfs(s, k, index + 1, current); + current.setLength(current.length() - 1); + + // 选择2️⃣:删除当前字符 + dfs(s, k - 1, index + 1, current); + } + + // 你的压缩逻辑,原样复用 ✅ + private String compress1(char[] chars) { + if (chars.length == 0) return ""; + + StringBuilder sb = new StringBuilder(); + char current = chars[0]; + int cnt = 1; + + for (int i = 1; i < chars.length; i++) { + if (chars[i] == current) { + cnt++; + } else { + sb.append(current); + if (cnt > 1) sb.append(cnt); + current = chars[i]; + cnt = 1; + } + } + + sb.append(current); + if (cnt > 1) sb.append(cnt); + return sb.toString(); + } + + + + /* + 标准解法是使用 动态规划(DP + 记忆化 DFS),时间复杂度 O(n²·k)。 + dp[i][k] = 从下标 i 开始,最多删除 k 个字符后,最短压缩长度 + 从第 i 位开始枚举字符; +统计连续相同字符出现次数 count; +删除掉部分不同字符(消耗 k); +更新最小压缩长度: +dp[i][k] = min( + dp[j+1][k - (段长 - count)] + 压缩该段的长度 +) + */ + public int getLengthOfOptimalCompression2(String s, int k) { + int n = s.length(); + Integer[][] memo = new Integer[n][k + 1]; + return dfs(s.toCharArray(), 0, k, memo); + } + + private int dfs(char[] s, int i, int k, Integer[][] memo) { + if (k < 0) return Integer.MAX_VALUE / 2; // 删除过多 + if (i >= s.length || s.length - i <= k) return 0; // 全删完 + if (memo[i][k] != null) return memo[i][k]; + + int res = Integer.MAX_VALUE; + int[] cnt = new int[26]; + int most = 0; + + // 从 i 开始,逐步扩展区间 + for (int j = i; j < s.length; j++) { + int idx = s[j] - 'a'; + cnt[idx]++; + most = Math.max(most, cnt[idx]); + + int del = (j - i + 1) - most; // 删除不匹配字符 + if (del > k) continue; + + res = Math.min( + res, + 1 + numLength(most) + dfs(s, j + 1, k - del, memo) + ); + } + + return memo[i][k] = res; + } + + private int numLength(int x) { + if (x <= 1) return 0; + if (x < 10) return 1; + if (x < 100) return 2; + return 3; + } +} diff --git a/src/main/java/binary_search/FindMedianSortedArrays.java b/src/main/java/binary_search/FindMedianSortedArrays.java index e4d0f02..aac4850 100644 --- a/src/main/java/binary_search/FindMedianSortedArrays.java +++ b/src/main/java/binary_search/FindMedianSortedArrays.java @@ -15,6 +15,7 @@ package binary_search; */ //不会 //二刷不会 +//三刷不会 public class FindMedianSortedArrays { @@ -63,7 +64,7 @@ public class FindMedianSortedArrays { // 每次比较 nums1 和 nums2 各自第 k/2 个候选(如果不足 k/2,则取末尾) int half = k / 2; - int newIndex1 = Math.min(index1 + half, len1) - 1; + int newIndex1 = Math.min(index1 + half, len1) - 1; //例如 index=0,要找第 3 个元素: → 索引 = 0 + 3 - 1 = 2。 int newIndex2 = Math.min(index2 + half, len2) - 1; int pivot1 = nums1[newIndex1]; int pivot2 = nums2[newIndex2]; @@ -80,4 +81,30 @@ public class FindMedianSortedArrays { } } } + /* + 如果不考虑时间复杂度 + */ + public int findKthMerge(int[] nums1, int[] nums2, int k) { + int i = 0, j = 0, count = 0; + int val = 0; + + while (i < nums1.length || j < nums2.length) { + int a = (i < nums1.length) ? nums1[i] : Integer.MAX_VALUE; + int b = (j < nums2.length) ? nums2[j] : Integer.MAX_VALUE; + + if (a <= b) { + val = a; + i++; + } else { + val = b; + j++; + } + + count++; + if (count == k) return val; + } + return -1; // 理论上不会走到这里 + } + + } diff --git a/src/main/java/binary_search/FindMin.java b/src/main/java/binary_search/FindMin.java index 786efdd..86724f5 100644 --- a/src/main/java/binary_search/FindMin.java +++ b/src/main/java/binary_search/FindMin.java @@ -17,6 +17,7 @@ package binary_search; * 链接:https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/ */ //二刷不会 +//三刷不会 public class FindMin { /** @@ -29,7 +30,12 @@ public class FindMin { * 判断最小值在哪个区间 * * 左侧有序情况:如果 nums[mid] >= nums[left],说明从 left 到 mid 是有序的,此时最小值不可能出现在有序部分,所以最小值一定在右半部分,因此更新 left = mid + 1。 + * 为什么说'此时最小值不可能出现在有序部分'? 因为 左边最小值此时是nums[left] 但是这里有个前提,一开始就判断了nums[left]>nums[right] ,故最小值肯定在右半段。 + * + * * 旋转点在左侧情况:如果 nums[mid] < nums[left],说明中间部分处于旋转状态,即最小值可能出现在左半部分或就是 nums[mid],因此将 right 更新为 mid。 + * 为什么? 因为最小值肯定在旋转的区间内,4 5 6 7 0 1 2 ,最小值左边就是最大值。 + * * 退出条件 * * 循环在 left == right 时退出,此时 nums[left] 就是整个数组的最小值。 diff --git a/src/main/java/dynamic_programming/CanPartition.java b/src/main/java/dynamic_programming/CanPartition.java index 5e24c5e..6c6334b 100644 --- a/src/main/java/dynamic_programming/CanPartition.java +++ b/src/main/java/dynamic_programming/CanPartition.java @@ -13,6 +13,7 @@ package dynamic_programming; * 链接:https://leetcode.cn/problems/partition-equal-subset-sum/ */ //二刷不会 +//三刷会做 public class CanPartition { public boolean canPartition1(int[] nums) { int total=0,target=0; diff --git a/src/main/java/dynamic_programming/Change.java b/src/main/java/dynamic_programming/Change.java index 9705e86..5fae3f7 100644 --- a/src/main/java/dynamic_programming/Change.java +++ b/src/main/java/dynamic_programming/Change.java @@ -1,7 +1,5 @@ package dynamic_programming; -import java.util.Arrays; - /** * 题目: 518. 零钱兑换 II (change) * 描述:给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 diff --git a/src/main/java/dynamic_programming/CountSubstrings.java b/src/main/java/dynamic_programming/CountSubstrings.java index 8df1738..8fd7af8 100644 --- a/src/main/java/dynamic_programming/CountSubstrings.java +++ b/src/main/java/dynamic_programming/CountSubstrings.java @@ -14,6 +14,7 @@ package dynamic_programming; */ //不会 //二刷不会 +//三刷不会 public class CountSubstrings { /** *布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串 diff --git a/src/main/java/dynamic_programming/FindMaxForm.java b/src/main/java/dynamic_programming/FindMaxForm.java index 6cd1975..abd8e27 100644 --- a/src/main/java/dynamic_programming/FindMaxForm.java +++ b/src/main/java/dynamic_programming/FindMaxForm.java @@ -14,6 +14,7 @@ package dynamic_programming; * 链接:https://leetcode.cn/problems/ones-and-zeroes/ */ //二刷不会 +//三刷会做 public class FindMaxForm { // 统计字符串中 '0' 和 '1' 的数量 private int[] countZeroAndOne(String str) { diff --git a/src/main/java/dynamic_programming/FindTargetSumWays.java b/src/main/java/dynamic_programming/FindTargetSumWays.java index dac4e56..4f23d7c 100644 --- a/src/main/java/dynamic_programming/FindTargetSumWays.java +++ b/src/main/java/dynamic_programming/FindTargetSumWays.java @@ -20,6 +20,7 @@ package dynamic_programming; */ //二刷不会 //三刷不会,数学转换很重要! + //四刷会做 public class FindTargetSumWays { /** diff --git a/src/main/java/dynamic_programming/IntegerBreak.java b/src/main/java/dynamic_programming/IntegerBreak.java index f22dc00..786f0d2 100644 --- a/src/main/java/dynamic_programming/IntegerBreak.java +++ b/src/main/java/dynamic_programming/IntegerBreak.java @@ -11,6 +11,7 @@ package dynamic_programming; * 链接:https://leetcode.cn/problems/integer-break/ */ +//二刷不会 public class IntegerBreak { public int integerBreak(int n) { //dp[i] 为正整数 i 拆分后的结果的最大乘积 diff --git a/src/main/java/dynamic_programming/IsSubsequence.java b/src/main/java/dynamic_programming/IsSubsequence.java index e5b8d66..a83e132 100644 --- a/src/main/java/dynamic_programming/IsSubsequence.java +++ b/src/main/java/dynamic_programming/IsSubsequence.java @@ -14,6 +14,7 @@ package dynamic_programming; * 链接:https://leetcode.cn/problems/is-subsequence/ */ //不会 +//二刷会做 public class IsSubsequence { //动态规划:转为求最长公共子序列,是否为s的长度 public boolean isSubsequence(String s, String t) { @@ -34,7 +35,6 @@ public class IsSubsequence { /** * 我们用两个指针 i 遍历字符串 s,j 遍历字符串 t。初始均指向各自字符串的开头: - * * 如果 s.charAt(i) == t.charAt(j),说明匹配上了,两个指针都往后走:i++, j++。 * 否则,就只能在 t 上“跳过”这个字符,j++。 * 当 i 走到 s.length() 时,说明 s 中的所有字符都在 t 中按顺序找到了,返回 true;否则,等 j 扫完 t 还没把 s 的所有字符匹配完,就返回 false。 diff --git a/src/main/java/dynamic_programming/MaxProduct.java b/src/main/java/dynamic_programming/MaxProduct.java index 9317280..6347b96 100644 --- a/src/main/java/dynamic_programming/MaxProduct.java +++ b/src/main/java/dynamic_programming/MaxProduct.java @@ -1,7 +1,7 @@ package dynamic_programming; /** * 题目: 152. 乘积最大子数组 (MaxProduct) - * 描述:给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续 子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。 + * 描述:给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。 * 测试用例的答案是一个 32-位 整数。 * 示例 1: @@ -14,6 +14,7 @@ package dynamic_programming; //不会 //二刷不会 //三刷不会 +//四刷会做 public class MaxProduct { /** @@ -60,9 +61,9 @@ public class MaxProduct { public static void main(String[] args) { MaxProduct sol = new MaxProduct(); System.out.println(sol.maxProduct(new int[]{2, 3, -2, 4})); // 输出 6 - System.out.println(sol.maxProduct(new int[]{-2, 0, -1})); // 输出 0 - System.out.println(sol.maxProduct(new int[]{-2, 3, -4})); // 输出 24 - System.out.println(sol.maxProduct(new int[]{-1, -3, -10, 0, 60})); // 输出 60 - System.out.println(sol.maxProduct(new int[]{-2, -5, -2, -4, 3})); // 输出 240 +// System.out.println(sol.maxProduct(new int[]{-2, 0, -1})); // 输出 0 +// System.out.println(sol.maxProduct(new int[]{-2, 3, -4})); // 输出 24 +// System.out.println(sol.maxProduct(new int[]{-1, -3, -10, 0, 60})); // 输出 60 +// System.out.println(sol.maxProduct(new int[]{-2, -5, -2, -4, 3})); // 输出 240 } } diff --git a/src/main/java/dynamic_programming/MaxProfit4.java b/src/main/java/dynamic_programming/MaxProfit4.java index a9d3362..8049e66 100644 --- a/src/main/java/dynamic_programming/MaxProfit4.java +++ b/src/main/java/dynamic_programming/MaxProfit4.java @@ -14,6 +14,7 @@ package dynamic_programming; */ //不会 //二刷不会 +//三刷会做 public class MaxProfit4 { /** * 状态定义: diff --git a/src/main/java/dynamic_programming/MaximalSquare.java b/src/main/java/dynamic_programming/MaximalSquare.java index 7e3552c..6e5174e 100644 --- a/src/main/java/dynamic_programming/MaximalSquare.java +++ b/src/main/java/dynamic_programming/MaximalSquare.java @@ -11,6 +11,7 @@ package dynamic_programming; */ //不会 //二刷不会 +//三刷会做 public class MaximalSquare { /** * dp[i][j] 表示 以 (i, j) 作为右下角的最大全 1 正方形的边长 diff --git a/src/main/java/dynamic_programming/MinDistance.java b/src/main/java/dynamic_programming/MinDistance.java index ccf8ed2..f97f3ce 100644 --- a/src/main/java/dynamic_programming/MinDistance.java +++ b/src/main/java/dynamic_programming/MinDistance.java @@ -11,7 +11,18 @@ package dynamic_programming; * 链接:https://leetcode.cn/problems/delete-operation-for-two-strings/ */ +//二刷会做 public class MinDistance { + /** + * 法一:转为LCS + * 假设两个字符串的 最长公共子序列长度 是 LCS, + * 那我们只要: + * 把 word1 中那些 不在 LCS 里的字符 删除; + * 把 word2 中那些 不在 LCS 里的字符 删除; + * 就能让它们都变成 LCS 一样的字符串。 + * 因此最少删除步数公式:minDistance = (len1 - LCS) + (len2 - LCS) + * = len1 + len2 - 2 * LCS + */ public int minDistance(String word1, String word2) { int len1=word1.length(),len2=word2.length(); int[][]dp=new int[len1+1][len2+1]; @@ -24,4 +35,28 @@ public class MinDistance { } return len1+len2-2*dp[len1][len2]; } + + /** + * 直接求最小删除步数 + * dp[i][j] = 让 word1 的前 i 个字符 和 word2 的前 j 个字符 变成相同字符串 所需的最少删除步数。 + */ + public int minDistance2(String word1, String word2) { + int len1 = word1.length(), len2 = word2.length(); + int[][] dp = new int[len1 + 1][len2 + 1]; + + // 初始化:任意一个为空,必须删完另一个 + for (int i = 0; i <= len1; i++) dp[i][0] = i; + for (int j = 0; j <= len2; j++) dp[0][j] = j; + + for (int i = 1; i <= len1; i++) { + for (int j = 1; j <= len2; j++) { + if (word1.charAt(i - 1) == word2.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j - 1]; // 字符相等,无需删除 + } else { + dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + 1; // 删除一个 + } + } + } + return dp[len1][len2]; + } } diff --git a/src/main/java/range/EraseOverlapIntervals.java b/src/main/java/range/EraseOverlapIntervals.java index 12fef0d..639b385 100644 --- a/src/main/java/range/EraseOverlapIntervals.java +++ b/src/main/java/range/EraseOverlapIntervals.java @@ -17,6 +17,7 @@ import java.util.Arrays; */ //不会 //二刷不会 +//三刷不会 public class EraseOverlapIntervals { /** * 1.按结束时间排序 diff --git a/src/main/java/实现类功能/LRUCache.java b/src/main/java/实现类功能/LRUCache.java index 2a23ee8..5683106 100644 --- a/src/main/java/实现类功能/LRUCache.java +++ b/src/main/java/实现类功能/LRUCache.java @@ -41,7 +41,7 @@ public class LRUCache { /** * 双向链表节点 */ - class DLinkedNode { + static class DLinkedNode { int key; // 保存 key:便于淘汰节点时同步从 cache 中删除 int value; // 保存 value DLinkedNode prev; // 前驱