LeetcodeTop100 刷题总结(一)

LeetCode 热题 100:https://leetcode.cn/studyplan/top-100-liked/


文章目录

  • 一、哈希
    • 1. 两数之和
    • 49. 字母异位词分组
    • 128. 最长连续序列
  • 二、双指针
    • 283. 移动零
    • 11. 盛水最多的容器
    • 15. 三数之和
    • 42. 接雨水(待完成)
  • 三、滑动窗口
    • 3. 无重复字符的最长子串
    • 438. 找到字符串中所有字母异位词
  • 四、子串
    • 560. 和为 K 的子数组
    • 239. 滑动窗口最大值
    • 76. 最小覆盖子串
    • 补充:209. 长度最小的子数组
  • 五、普通数组
    • 53. 最大子数组和
    • 56. 合并区间
    • 189. 轮转数组
    • 238. 除自身以外数组的乘积
    • 41. 缺失的第一个正数(待完成)
  • 六、矩阵
    • 73. 矩阵置零
    • 54. 螺旋矩阵
    • 48. 旋转图像
    • 240. 搜索二维矩阵 II
  • 七、链表
    • 160. 相交链表
    • 206. 反转链表
    • 234. 回文链表
    • 141. 环形链表
    • 142. 环形链表 II
    • 21. 合并两个有序链表
    • 2. 两数相加
    • 19. 删除链表的倒数第 N 个结点
    • 24. 两两交换链表中的节点
    • 25. K 个一组翻转链表(待完成)
    • 138. 随机链表的复制
    • 148. 排序链表
    • 23. 合并 K 个升序链表
    • 146. LRU 缓存


一、哈希

1. 两数之和

思路:设置一个 map 容器,用于存储当前元素和索引。遍历时一边将数据存入 map,一边比从map中查找满足加和等于 target 的另一个元素。

class Solution {/*** 输入:nums = [2,7,11,15], target = 9* 输出:[0,1]* 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。*/public int[] twoSum(int[] nums, int target) {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[] {};}
}

49. 字母异位词分组

思路:设置一个 map 容器,key是排序后的字符组合,value是字母异位词的集合。

class Solution {/*** 输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]* 输出: [["bat"],["nat","tan"],["ate","eat","tea"]]*/public List<List<String>> groupAnagrams(String[] strs) {Map<String, List<String>> map = new HashMap<>();for (String str : strs) {char[] chars = str.toCharArray();Arrays.sort(chars);String sortStr = Arrays.toString(chars);// 如果存在key,即:new String(chars),那么返回对应的 value;// 否则将执行先初始化 key:new String(chars),value: new ArrayList<>(),然后在返回value。map.computeIfAbsent(new String(chars), s -> new ArrayList<>()).add(str);}return new ArrayList<>(map.values());}
}

128. 最长连续序列

思路:因为题目要求O(n)的时间复杂度,因此使用set对数组进行转存,并利用滑动窗口一次遍历即可得出连续序列的最长长度。

class Solution {/*** 输入:nums = [100,4,200,1,3,2]* 输出:4* 解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。*/public int longestConsecutive(int[] nums) {if (nums.length == 0) {return 0;}Set<Integer> set = new TreeSet<>();for (int num : nums) {set.add(num);}int co = 0;for (Integer num : set) {nums[co++] = num;}return sliderWindow(nums);}private int sliderWindow(int[] nums) {int left = 0;int len = nums.length;int max = 1;for (int right = 1; right < len; right++) {if (nums[right] - nums[right - 1] != 1) {left = right;}max = Math.max(right - left + 1, max);}return max;}
}

二、双指针

283. 移动零

class Solution {/*** 输入: nums = [0,1,0,3,12]* 输出: [1,3,12,0,0]*/public void moveZeroes(int[] nums) {int j = 0;for (int i = 0; i < nums.length; i++) {if (nums[i] != 0) {nums[j++] = nums[i];}}for (; j < nums.length; j++) {nums[j] = 0;}}
}

11. 盛水最多的容器

思路:定义双指针,分别指向数组的最左边和最右边,每次往里移动较短的元素的指针。这里解释为什么要移动短的?
根据木桶原理,整个木桶盛水的最大体积取决于小的那一段木板。如果移动短的指针,体积可能变大,也可能不变,还有可能变小。但如果移动长的指针,体积一定会变小。因此在指针不断往里移动的同时,移动指向较短元素的指针能得出盛水最大的容量。

在这里插入图片描述

class Solution {public int maxArea(int[] height) {int len = height.length;int left = 0;int right = len - 1;int maxArea = 0;// 面积 = 短板 * 底边// 向内移动短板,水槽短板 min(h[i], h[j]) 可能变大,下个水槽面积可能增大// 向内移动长板,水槽短板 min(h[i], h[j]) 可能变小或不变,下个水槽面积一定减小(因为底边长变小)while (left < right) {maxArea = Math.max(Math.min(height[left], height[right]) * (right - left), maxArea);if (height[left] < height[right]) {left++;} else {right--;}}return maxArea;}
}

15. 三数之和

思路:将数组排完序后进行遍历,遍历时选取当前元素的后一个元素和数组的最后一个元素为双指针。(注意对重复元素进行去重)

class Solution {/*** 输入:nums = [-1,0,1,2,-1,-4]* 输出:[[-1,-1,2],[-1,0,1]]* 解释:* nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。* nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。* nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。* 不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。* 注意,输出的顺序和三元组的顺序并不重要。*/public List<List<Integer>> threeSum(int[] nums) {List<List<Integer>> res = new ArrayList<>();int len = nums.length;Arrays.sort(nums);for (int i = 0; i < len; i++) {if (nums[i] > 0) {break;}if (i != 0 && nums[i] == nums[i - 1]) { // 去除重复元素continue;}int left = i + 1;int right = len - 1;while (left < right) {int sum = nums[i] + nums[left] + nums[right];if (sum == 0) {res.add(Arrays.asList(nums[i], nums[left], nums[right]));while (left < right && nums[left] == nums[left + 1]) { // 去除重复元素left++;}while (left < right && nums[right - 1] == nums[right]) {right--;}left++;right--;} else if (sum > 0) {right--;} else {left++;}}}return res;}
}

42. 接雨水(待完成)


三、滑动窗口

3. 无重复字符的最长子串

思路:定义一个 map 容器, key 存储字符,value 存储当前字符索引。使用滑动窗口计算最长字串,当窗口内存在重复字符时,调整窗口的左边界,调整为重复元素索引的下一位,并且注意左边界不能向左移动

class Solution {/*** 输入: s = "abcabcbb"* 输出: 3 * 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。*/public int lengthOfLongestSubstring(String s) {Map<Character, Integer> map = new HashMap<>(); // key:字符,value:当前字符索引int len = s.length();int start = 0;int max = 0;for (int end = 0; end < len; end++) {char ch = s.charAt(end);if (map.containsKey(ch)) {start = Math.max(map.get(ch) + 1, start); // 处理 'abba',如果不用max比较当遍历到最后一个a时,start将会指向第一个b,即start-end范围是 bba}map.put(ch, end);max = Math.max(end - start + 1, max);}return max;}
}

438. 找到字符串中所有字母异位词

思路:使用数组统计字符串中26个字符的出现次数,固定滑动窗口大小,并使用 Arrays.equals(...) 方法一边遍历一边比较。

class Solution {/*** 输入: s = "cbaebabacd", p = "abc"* 输出: [0,6]* 解释:* 起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。* 起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。*/public List<Integer> findAnagrams(String s, String p) {List<Integer> res = new ArrayList<>();int sLen = s.length();int pLen = p.length();if (sLen < pLen) {return res;}int[] sWin = new int[26];int[] pWin = new int[26];for (int i = 0; i < pLen; i++) {sWin[s.charAt(i) - 'a']++;pWin[p.charAt(i) - 'a']++;}if (Arrays.equals(sWin, pWin)) {res.add(0);}for (int i = pLen; i < sLen; i++) {sWin[s.charAt(i - pLen) - 'a']--;sWin[s.charAt(i) - 'a']++;if (Arrays.equals(sWin, pWin)) {res.add(i - pLen + 1);}}return res;}
}

四、子串

560. 和为 K 的子数组

思路:首先计算前缀和,利用前缀和的差值确定子数组的和是否等于K。
如:下面数组求子数组和为 6,pre[4] - pre[1] == 6 就代表:num[1:3] 加和等于 6。

ind01234567
value41230624
前缀和 pre04571010161822

注:这里我们预留第一个位置为0,代表索引为 0 的元素前缀和为 0。

class Solution {/*** 输入:nums = [1,2,3], k = 3* 输出:2*/public int subarraySum(int[] nums, int k) {int res = 0;int len = nums.length;int[] pre = new int[len + 1];// 计算前缀和for (int i = 0; i < len; i++) {pre[i + 1] = pre[i] + nums[i];}for (int left = 0; left < len; left++) {for (int right = left; right < len; right++) {if (pre[right + 1] - pre[left] == k) {res++;}}}return res;}
}

上面做法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),因此用哈希表进行优化。

思路:设置一个 map 容器用于存储前缀和以及前缀和的个数,当计算前缀和的同时来查找是否存在 前缀和 - 目标和,如果存在则说明存在子数组和等于 k。如:上述例子中,求子数组和为 6,当遍历到索引 4 时前缀和为 10, map 中存在键 key=“10-6”=4 {key=4,value=1},说明当前元素存在前缀和为 4 的情况。

ind01234567
value41230624
累加的前缀和4571010161822
class Solution {/*** 输入:nums = [1,2,3], k = 3* 输出:2*/public int subarraySum(int[] nums, int k) {Map<Integer, Integer> map = new HashMap<>(); // key:前缀和,value: 前缀和的个数int res = 0;map.put(0, 1); // 前缀和为 0 的个数有一个int sum = 0; // 记录前缀和for (int num : nums) {sum += num;if (map.containsKey(sum - k)) {res += map.get(sum - k);}map.put(sum, map.getOrDefault(sum, 0) + 1);}return res;}
}

239. 滑动窗口最大值

思路:设置一个大顶堆,固定窗口大小,遍历时首先清除过期元素,然后将元素入堆。
值得注意的是,有些比较小的元素由于不在堆顶,不会立即删除。但是在后面如果到了堆顶,也会删除

class Solution {/*** 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3* 输出:[3,3,5,5,6,7]* 解释:* 滑动窗口的位置                最大值* ---------------               -----* [1  3  -1] -3  5  3  6  7       3*  1 [3  -1  -3] 5  3  6  7       3*  1  3 [-1  -3  5] 3  6  7       5*  1  3  -1 [-3  5  3] 6  7       5*  1  3  -1  -3 [5  3  6] 7       6*  1  3  -1  -3  5 [3  6  7]      7*/public int[] maxSlidingWindow(int[] nums, int k) {PriorityQueue<Elem> heap = new PriorityQueue<>((elem1, elem2) -> elem2.value - elem1.value);// 初始化大顶堆int len = nums.length;int[] res = new int[len - k + 1];for (int i = 0; i < k; i++) {heap.add(new Elem(nums[i], i));}res[0] = heap.element().value;int co = 1;for (int i = k; i < len; i++) {while (!heap.isEmpty() && heap.element().index <= i - k) { // 处理不在窗口的元素// 有些比较小的元素由于不在堆顶,不会立即删除。但是在后面如果到了堆顶,也会删除// 如:nums = [5,6,-1,-2,3], k = 3// 当窗口在[6,-1,-2]时,5还在堆内,但是当窗口在[-1,-2,3]时,会在堆顶被删除heap.remove();}heap.add(new Elem(nums[i], i));res[co++] = heap.element().value;}return res;}class Elem {int value;int index;public Elem() {}public Elem(int value, int index) {this.value = value;this.index = index;}}
}

76. 最小覆盖子串

思路:分别设置两个数组用来存储字符的出现次数,利用滑动窗口边一边右移一边检查模式串是否被覆盖。

 class Solution {/*** 输入:s = "ADOBECODEBANC", t = "ABC"* 输出:"BANC"* 解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。*/public String minWindow(String s, String t) {if (s.length() < t.length()) {return "";}int[] sChars = new int[128];int[] tChars = new int[128];for (char ch : t.toCharArray()) {tChars[ch]++;}int left = 0;int sLen = s.length();int resLeft = -1;int resRight = sLen;for (int right = 0; right < sLen; right++) {sChars[s.charAt(right)]++;while (left <= right && isCovered(sChars, tChars)) {if (right - left < resRight - resLeft) {resLeft = left;resRight = right;}sChars[s.charAt(left)]--;left++;}}return resLeft == -1 ? "" : s.substring(resLeft, resRight + 1);}private boolean isCovered(int[] sChars, int[] tChars) {for (int i = 'A'; i <= 'Z'; i++) {if (sChars[i] < tChars[i]) {return false;}}for (int i = 'a'; i <= 'z'; i++) {if (sChars[i] < tChars[i]) {return false;}}return true;}
}

上面代码在每次遍历的时候都需要检查子串是否被覆盖,因此可以考虑设置两个变量 sNum 和 tNum。tNum 用于记录 t 中不同字符的数量, sNum 用于记录 s 指定字符达到覆盖 t 的程度数量。如:当 s 的子串中如果 ‘a’ 的数量等于 t 中 ‘a’ 字符的数量时 sNum + 1,否则不变。

class Solution {/*** 输入:s = "ADOBECODEBANC", t = "ABC"* 输出:"BANC"* 解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。*/public String minWindow(String s, String t) {int[] sChars = new int[128];int[] tChars = new int[128];int sNum = 0; // 记录 s 中的指定字符数量达到覆盖 t 程度的数量int tNum = 0; // 记录 t 中有多少不同字符for (char ch : t.toCharArray()) {if (tChars[ch]++ == 0) {tNum++;}}int len = s.length();int resLeft = -1;int resRight = len;int left = 0;for (int right = 0; right < len; right++) {if (++sChars[s.charAt(right)] == tChars[s.charAt(right)]) {sNum++; // s中的该字符数量达到覆盖 t 中该字符的程度}while (left <= right && sNum == tNum) {if (right - left < resRight - resLeft) { // 更新结果左右边界resLeft = left;resRight = right;}if (sChars[s.charAt(left)]-- == tChars[s.charAt(left)]) {sNum--;}left++;}}return resLeft == -1 ? "" : s.substring(resLeft, resRight + 1);}
}

补充:209. 长度最小的子数组

最小覆盖子串题目类似:209. 长度最小的子数组

class Solution {/*** 输入:target = 7, nums = [2,3,1,2,4,3]* 输出:2* 解释:子数组 [4,3] 是长度最小且总和大于等于 target 的子数组。*/public int minSubArrayLen(int target, int[] nums) {int len = nums.length;int sum = 0;int left = 0;int res = len + 1;for (int right = 0; right < len; right++) {sum += nums[right];while (left <= right && sum >= target) {res = Math.min(right - left + 1, res);sum -= nums[left++];}}return res == len + 1 ? 0 : res;}
}

五、普通数组

53. 最大子数组和

思路:设置变量 curr 用于记录子数组和,遍历数组时,当子数组和大于零时累加当前元素,否则令子数组和等于当前数组元素。

class Solution {/*** 输入:nums = [-2,1,-3,4,-1,2,1,-5,4]* 输出:6* 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。*/public int maxSubArray(int[] nums) {int curr = 0;int max = nums[0];for (int i = 0; i < nums.length; i++) {if (curr >= 0) {curr += nums[i];} else {curr = nums[i];}max = Math.max(curr, max);}return max;}
}

56. 合并区间

思路:定义内部类用于记录区间的左右端点,对二维数组按照左端点递增,左端点相同时右端点递增的规则排序。将数组第一个元素加入集合后进行遍历,若发现当前 数组元素左端点和集合最后一个元素的左端点相同 或者 集合最后一个元素的右端点大于数组的左端点,则将集合的最后一个元素的右端点进行取大处理,否则将数组元素加入集合。

class Solution {/*** 输入:intervals = [[1,3],[2,6],[8,10],[15,18]]* 输出:[[1,6],[8,10],[15,18]]* 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].*/public int[][] merge(int[][] intervals) {int len = intervals.length;List<Range> list = new ArrayList<>();Arrays.sort(intervals,(range1, range2) -> range1[0] != range2[0] ? range1[0] - range2[0] : range1[1] - range2[1]);list.add(new Range(intervals[0][0], intervals[0][1]));for (int i = 1; i < len; i++) {Range range = list.get(list.size() - 1);if (range.begin == intervals[i][0] || range.end >= intervals[i][0]) {range.end = Math.max(intervals[i][1], range.end); // Max比较大小是为了处理这种情况 [[1,4],[2,3]]} else {list.add(new Range(intervals[i][0], intervals[i][1]));}}int size = list.size();int[][] res = new int[size][2];for (int i = 0; i < size; i++) {res[i][0] = list.get(i).begin;res[i][1] = list.get(i).end;}return res;}class Range {int begin;int end;public Range() {}public Range(int begin, int end) {this.begin = begin;this.end = end;}}
}

189. 轮转数组

思路:先将数组全部翻转,然后对前 k 个元素和其余的元素分别做翻转。

class Solution {/*** 输入: nums = [1,2,3,4,5,6,7], k = 3* 输出: [5,6,7,1,2,3,4]* 解释:* 向右轮转 1 步: [7,1,2,3,4,5,6]* 向右轮转 2 步: [6,7,1,2,3,4,5]* 向右轮转 3 步: [5,6,7,1,2,3,4]*/public void rotate(int[] nums, int k) {int len = nums.length;k %= len;reverseArr(nums, 0, len - 1);reverseArr(nums, 0, k - 1);reverseArr(nums, k, len - 1);}private void reverseArr(int[] nums, int begin, int end) {while (begin < end) {int temp = nums[begin];nums[begin] = nums[end];nums[end] = temp;begin++;end--;}}
}

238. 除自身以外数组的乘积

思路:将数组元素累乘以后逐个相除可能会存在除零异常。因此,考虑分别求当前元素的左侧累乘积和右侧累乘积,最后再将两侧数组做累乘。

class Solution {/*** 输入: nums = [1,2,3,4]* 输出: [24,12,8,6]*/public int[] productExceptSelf(int[] nums) {int len = nums.length;int[] left = new int[len];int[] right = new int[len];int[] res = new int[len];left[0] = 1;right[len - 1] = 1;// nums: [1, 2, 3, 4]// left: [1, 1, 2, 6]// right: [24,12,4, 1]for (int i = 1; i < len; i++) {left[i] = nums[i - 1] * left[i - 1];}for (int i = len - 2; i >= 0; i--) {right[i] = nums[i + 1] * right[i + 1];}for (int i = 0; i < len; i++) {res[i] = left[i] * right[i];}return res;}
}

41. 缺失的第一个正数(待完成)


六、矩阵

73. 矩阵置零

思路:设置矩阵行列大小的两个数组,用于对矩阵元素为零的行列进行标记。再次遍历矩阵,然后将标记过的行和列进行置零。

在这里插入图片描述

class Solution {public void setZeroes(int[][] matrix) {int m = matrix.length;int n = matrix[0].length;int[] rows = new int[m];int[] columns = new int[n];for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (matrix[i][j] == 0) {rows[i] = 1;columns[j] = 1;}}}for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (rows[i] == 1 || columns[j] == 1) {matrix[i][j] = 0;}}}}
}

54. 螺旋矩阵

思路:初始化矩阵的上下左右四个边界,按照 “从左向右、从上向下、从右向左、从下向上” 四个方向循环打印,每次都需要更新边界,并判断结束条件。

在这里插入图片描述

class Solution {public List<Integer> spiralOrder(int[][] matrix) {List<Integer> res = new ArrayList<>();int left = 0;int right = matrix[0].length - 1;int up = 0;int down = matrix.length - 1;while (true) {for (int i = left; i <= right; i++) {res.add(matrix[up][i]);}if (++up > down) {break;}for (int i = up; i <= down; i++) {res.add(matrix[i][right]);}if (left > --right) {break;}for (int i = right; i >= left; i--) {res.add(matrix[down][i]);}if (up > --down) {break;}for (int i = down; i >= up; i--) {res.add(matrix[i][left]);}if (++left > right) {break;}}return res;}
}

48. 旋转图像

思路:先将矩阵转置,然后将左右对称的两列互换元素,即可达到顺时针旋转 90 度的效果。

在这里插入图片描述

class Solution {public void rotate(int[][] matrix) {int n = matrix.length;for (int i = 0; i < n; i++) { // 矩阵转置for (int j = i + 1; j < n; j++) {int temp = matrix[i][j];matrix[i][j] = matrix[j][i];matrix[j][i] = temp;}}for (int i = 0; i < n; i++) { // 左右对称的两列互换for (int j = 0; j < n / 2; j++) {int temp = matrix[i][j];matrix[i][j] = matrix[i][n - 1 - j];matrix[i][n - 1 - j] = temp;}}}
}

240. 搜索二维矩阵 II

思路:利用 “每行的所有元素从左到右升序排列,每列的所有元素从上到下升序排列” 这个特点,从右上角开始向左下角的方向查找,当元素大于目标元素,这一列下面的元素都大于目标元素;当元素小于目标元素,这一行前面的元素都小于目标元素。

在这里插入图片描述

class Solution {public boolean searchMatrix(int[][] matrix, int target) {int m = matrix.length;int n = matrix[0].length;int x = 0; // 右上角int y = n - 1;while (x < m && y >= 0) {if (matrix[x][y] > target) { // 当前元素大于target,这一列下面的元素都大于targety--;} else if (matrix[x][y] < target) { // 当前元素小于target,这一行前面的元素都小于targetx++;} else {return true;}}return false;}
}

也可以从左下角开始查找,代码如下:

class Solution {public boolean searchMatrix(int[][] matrix, int target) {int m = matrix.length;int n = matrix[0].length;int x = m - 1; // 左下角int y = 0;while (x >= 0 && y < n) {if (matrix[x][y] > target) { // 当前元素大于target,这一行后面的元素都大于targetx--;} else if (matrix[x][y] < target) { // 当前元素小于target,这一列上面的元素都小于targety++;} else {return true;}}return false;}
}

七、链表

160. 相交链表

思路:利用乘法交换律,设两个链表相交前分别有 A B 个节点,相交部分有 C 个节点,那么 A+C+B=B+C+A。设置两个指针分别指向两个链表的头部,同时向后移动。当其中一个指针移动到结尾时,则转向指向另一个链表的头部,另一个指针步骤同上,最终两个指针会在相交处会面。
在这里插入图片描述

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode(int x) {*         val = x;*         next = null;*     }* }*/
public class Solution {public ListNode getIntersectionNode(ListNode headA, ListNode headB) {ListNode pa = headA;ListNode pb = headB;while (pa != pb) {pa = pa == null ? headB : pa.next;pb = pb == null ? headA : pb.next;}return pa;}
}

注:如果两个链表不相交,也适合以上规律,最终两个指针都会指向空,也会跳出循环。


206. 反转链表

思路:链表头插法。

在这里插入图片描述

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode reverseList(ListNode head) {ListNode pre = new ListNode();ListNode p;p = head;pre.next = null;while(p != null){ListNode temp = p.next;p.next = pre.next;pre.next = p;p = temp;}return pre.next;}
}

234. 回文链表

思路:本地的实现很多,这里采用栈进行辅助判断回文。

在这里插入图片描述

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public boolean isPalindrome(ListNode head) {Deque<ListNode> stack = new LinkedList<>();ListNode p = head;while (p != null) {stack.push(p);p = p.next;}while (head != null) {p = stack.pop();if (p.val != head.val) {return false;}head = head.next;}return true;}
}

141. 环形链表

思路1:使用 hash 表进行辅助判断是否存在环。

在这里插入图片描述

/*** Definition for singly-linked list.* class ListNode {*     int val;*     ListNode next;*     ListNode(int x) {*         val = x;*         next = null;*     }* }*/
public class Solution {public boolean hasCycle(ListNode head) {Set<ListNode> set = new HashSet<>();ListNode p = head;while (p != null) {if (set.contains(p)) {return true;}set.add(p);p = p.next;}return false;}
}

思路2:使用快慢指针,slow 每次向前走一步,fast 每次向前走两步。
当存在环时,fast 由于走得快,会发生扣圈的情况,且最终与 slow 相遇
当不存在环时,fast 可能在某次循环后,发生当前位置为空,或下一位置为空的两种情况,当然由于走的快,最终会返回 false
总之,循环的结束条件,要么出现环 slow == fast,要么 fast 先一步为空。下面列举两种实现方式:

/*** Definition for singly-linked list.* class ListNode {*     int val;*     ListNode next;*     ListNode(int x) {*         val = x;*         next = null;*     }* }*/
public class Solution {public boolean hasCycle(ListNode head) {ListNode slow = head;ListNode fast = head;while (true) {if (fast == null || fast.next == null) {return false;}slow = slow.next;fast = fast.next.next;if (slow == fast) {return true;}}}
}// 推荐
public class Solution {public boolean hasCycle(ListNode head) {if (head == null) {return false;}ListNode slow = head;ListNode fast = head;while (fast.next != null && fast.next.next != null) {slow = slow.next;fast = fast.next.next;if (slow == fast) {return true;}}return false;}
}

142. 环形链表 II

思路1:使用 hash 表。

在这里插入图片描述

/*** Definition for singly-linked list.* class ListNode {*     int val;*     ListNode next;*     ListNode(int x) {*         val = x;*         next = null;*     }* }*/
public class Solution {public ListNode detectCycle(ListNode head) {Set<ListNode> set = new HashSet<>();ListNode p = head;while (p != null) {if (set.contains(p)) {return p;}set.add(p);p = p.next;}return null;}
}

思路2:使用快慢指针,思路如下:

  • fast 每次走两个节点, slow 每次走一个节点。环外有 a 个结点,环内有 b 个结点。
  • 第一次相遇时,fast 走了 f 步,slow 走了 s 步。
    f = 2s
    f = s + nb 表示 fs 多走了 n*b 步,即 n 圈。这样表示的原因在于扣圈。
    化简得:f = 2nb, s = nbn 代表扣圈的次数,可能等于1,2,3,…
  • 设刚开始 slow 指针从开始到环的入口要走 k 步:k = a + tbt 代表在环中循环的次数,可能等于0,1,2,3,…。因此当发生第一次相遇时,再走 a 步即可重新回到入环的起点。
/*** Definition for singly-linked list.* class ListNode {*     int val;*     ListNode next;*     ListNode(int x) {*         val = x;*         next = null;*     }* }*/
public class Solution {public ListNode detectCycle(ListNode head) {if (head == null) {return null;}ListNode slow = head;ListNode fast = head;while (fast.next != null && fast.next.next != null) {slow = slow.next;fast = fast.next.next;if (slow == fast) {fast = head; // 令 fast 指针指向链表头部break;}}if (fast.next == null || fast.next.next == null) {return null;}while (slow != fast) {slow = slow.next;fast = fast.next;}return fast;}
}

21. 合并两个有序链表

思路:设置两个指针,分别指向链表头部,逐个比较向后迭代即可。

在这里插入图片描述

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode mergeTwoLists(ListNode list1, ListNode list2) {ListNode p1 = list1;ListNode p2 = list2;ListNode pre = new ListNode();ListNode p = pre;while (p1 != null && p2 != null) {if (p1.val < p2.val) {p.next = p1;p = p1;p1 = p1.next;} else {p.next = p2;p = p2;p2 = p2.next;}}if (p1 != null) {p.next = p1;}if (p2 != null) {p.next = p2;}return pre.next;}
}

2. 两数相加

思路:设置两个指针和进位标志,逐个向后相加迭代即可。

在这里插入图片描述
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {ListNode pre = new ListNode();ListNode p = pre;ListNode p1 = l1;ListNode p2 = l2;int sign = 0;int sum;while (p1 != null || p2 != null) {if (p1 != null && p2 != null) {sum = p1.val + p2.val + sign;p1 = p1.next;p2 = p2.next;} else if (p1 != null) {sum = p1.val + sign;p1 = p1.next;} else {sum = p2.val + sign;p2 = p2.next;}p.next = new ListNode(sum % 10);p = p.next;sign = sum / 10;}if (sign != 0) {p.next = new ListNode(sign);}return pre.next;}
}

19. 删除链表的倒数第 N 个结点

思路:让前面的指针先移动 n 步,之后前后指针共同移动直到前面的指针到尾部为止。

在这里插入图片描述

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {ListNode pre = new ListNode();pre.next = head;ListNode p = pre;ListNode q = pre;int co = 0;while (p.next != null) {if (++co > n) {q = q.next;}p = p.next;}q.next = q.next.next;return pre.next;}
}

24. 两两交换链表中的节点

思路:链表节点两两交换位置,逐个向后迭代。

在这里插入图片描述

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode swapPairs(ListNode head) {ListNode pre = new ListNode(0);pre.next = head;ListNode p = head;ListNode q = pre;while (p != null && p.next != null) {ListNode temp = p.next.next;q.next = p.next;q.next.next = p;p.next = null;q = p;p = temp;}if (p != null) {q.next = p;}return pre.next;}
}

25. K 个一组翻转链表(待完成)


138. 随机链表的复制

思路:题意是让我们把下面的随机链表做整体复制,这里我们设置一个 map 容器,用于对应原始节点和复制的节点,存储以后再处理 next 指针和 random 指针。

在这里插入图片描述

/*
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;}Map<Node, Node> map = new HashMap<>();Node p = head;while (p != null) {Node copyNode = new Node(p.val);map.put(p, copyNode);p = p.next;}p = head;while (p != null) {Node copyNode = map.get(p);if (p.random != null) {copyNode.random = map.get(p.random);}if (p.next != null) {copyNode.next = map.get(p.next);}p = p.next;}return map.get(head);}
}

148. 排序链表

思路:这里我们采用堆结构辅助链表排序,将大顶堆构造好以后,一边出堆一边利用头插法对链表结构进行重塑。

在这里插入图片描述

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode sortList(ListNode head) {PriorityQueue<ListNode> queue = new PriorityQueue<>((a, b) -> b.val-a.val); // 大顶堆while(head != null){queue.offer(head); // 从堆底插入head = head.next;}ListNode pre = new ListNode(0);while(!queue.isEmpty()){ListNode p = queue.poll(); // 出队列并调整堆p.next = pre.next; // 头插法倒序pre.next = p;}return pre.next;}
}

23. 合并 K 个升序链表

思路:K 个有序链表重复调用两个有序链表的算法。

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {/*** 输入:lists = [[1,4,5],[1,3,4],[2,6]]* 输出:[1,1,2,3,4,4,5,6]*/public ListNode mergeKLists(ListNode[] lists) {int len = lists.length;ListNode pre = null;      for (int i = 0; i < len; i++) {pre = mergeTwoLists(pre, lists[i]);}return pre;}private ListNode mergeTwoLists(ListNode list1, ListNode list2) {ListNode p1 = list1;ListNode p2 = list2;ListNode pre = new ListNode();ListNode p = pre;while (p1 != null && p2 != null) {if (p1.val < p2.val) {p.next = p1;p = p1;p1 = p1.next;} else {p.next = p2;p = p2;p2 = p2.next;}}if (p1 != null) {p.next = p1;}if (p2 != null) {p.next = p2;}return pre.next;}
}

146. LRU 缓存

输入:
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出:
[null, null, null, 1, null, -1, null, -1, 3, 4]
解释:

LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1);  // 缓存是 {1=1}
lRUCache.put(2, 2);  // 缓存是 {1=1, 2=2}
lRUCache.get(1);   // 返回 1
lRUCache.put(3, 3);  // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);   // 返回 -1 (未找到)
lRUCache.put(4, 4);  // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);   // 返回 -1 (未找到)
lRUCache.get(3);   // 返回 3
lRUCache.get(4);   // 返回 4

思路:参考灵神的思路,想象有一摞书。
get:时将一本书(key) 抽出来,放在最上面。
put:放入一本新书,如果已经有这本书(key),把他抽出来放在最上面,并替换它的 value。如果没有这本书(key),就放在最上面。如果超出了 capacity 本书,就把最下面的书移除。

题目要求 get 和 put 都是 O(1) 的时间复杂度,因此考虑双向链表实现。

class LRUCache {class Node {int key, value;Node prev, next;public Node(int key, int value) {this.key = key;this.value = value;}}Map<Integer, Node> map;Node dummy;int capacity;public LRUCache(int capacity) {map = new HashMap<>();dummy = new Node(0, 0); // 头结点this.capacity = capacity;dummy.next = dummy;dummy.prev = dummy;}public int get(int key) {Node node = getNode(key);return node != null ? node.value : -1;}public void put(int key, int value) {Node node = getNode(key);if (node != null) {node.value = value; // 如果存在,则在getRoot方法里面已经放到了头部return;}node = new Node(key, value);map.put(key, node);pushFirst(node); // 放在链表头部if (map.size() > capacity) {map.remove(dummy.prev.key);remove(dummy.prev);}}private Node getNode(int key) {if (!map.containsKey(key)) {return null;}Node node = map.get(key);remove(node); // 删除旧节点pushFirst(node); // 将新节点加到链表头部return node;}private void pushFirst(Node node) {node.next = dummy.next;node.prev = dummy;dummy.next.prev = node;dummy.next = node;}private void remove(Node node) {node.prev.next = node.next;node.next.prev = node.prev;}
}
/*** 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);*/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/54560.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式入门小工程

此代码基于s3c2440 1.点灯 //led.c void init_led(void) {unsigned int t;t GPBCON;t & ~((3 << 10) | (3 << 12) | (3 << 14) | (3 << 16));t | (1 << 10) | (1 << 12) | (1 << 14) | (1 << 16);GPBCON t; }void le…

上位机图像处理和嵌入式模块部署(linux小系统开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 和若干年前相比较&#xff0c;现在嵌入式linux开发要简单得多。稍微贵一点的有树莓派&#xff0c;国产的有各种水果派&#xff0c;基本上都可以按照…

Google 扩展 Chrome 安全和隐私功能

过去一周&#xff0c;谷歌一直在推出新特性和功能&#xff0c;旨在让用户在 Chrome 上的桌面体验更加安全&#xff0c;最新的举措是扩展在多个设备上保存密钥的功能。 到目前为止&#xff0c;Chrome 网络用户只能将密钥保存到 Android 上的 Google 密码管理器&#xff0c;然后…

【学习笔记】STM32F407探索者HAL库开发(四)F103时钟系统配置

【学习笔记】STM32F407探索者HAL库开发&#xff08;四&#xff09;F103时钟系统配置 1 STM32F1时钟树1.1 STM32F103时钟系统图1.2 STM32F103时钟树简图1.2.1 高速部分1.2.2 低速部分 1.3 函数配置1.4 时钟输出1.5 STM32CubeMX时钟树配置F11.6 时钟系统对与嵌入式开发的重要性 1…

Spring IDEA 2024 自动生成get和set以及toString方法

1.简介 在IDEA中使用自带功能可以自动生成get和set以及toString方法 2.步骤 在目标类中右键&#xff0c;选择生成 选择Getter和Setter就可以生成每个属性对应的set和get方法&#xff0c; 选择toString就可以生成类的toString方法&#xff0c;

Linux 文件系统(下)

目录 一.文件系统 1.文件在磁盘上的存储方式 a.盘面、磁道和扇区 b.分区和分组 2.有关Block group相关字段详解 a.inode编号 b.inode Table&#xff08;节点表&#xff09; c.Data blocks&#xff08;数据区&#xff09; d.小结 二.软硬链接 1.软链接 a.软链接的创建…

数据湖 Data Lake-概述

Data Lake 1. 数据湖的定义 数据湖是一种存储系统&#xff0c;用于集中存储大量的原始数据&#xff0c;可以按数据本来的原始格式进行存储&#xff0c;用户可以在需要时提取和分析这些数据。 A data lake is a centralized repository designed to hold vast volumes of data …

OpenCV特征检测(4)检测图像中的角点函数cornerHarris()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 Harris 角点检测器。 该函数在图像上运行 Harris 角点检测器。类似于 cornerMinEigenVal 和 cornerEigenValsAndVecs&#xff0c;对于每个像素 (…

如何将生物序列tokenization为token?

原理讲解 tokenization是自然语言处理领域非常成熟的一项技术&#xff0c;tokenization就是把我们研究的语言转换成计算机能够识别的数字——token。 在生物领域&#xff0c;如何把核苷酸或氨基酸序列tokenization成token呢&#xff1f; 我们可以使用k-mer技术&#xff1a; k-m…

网络设备登录——《路由与交换技术》实验报告

目录 一、实验目的 二、实验设备和环境 三、实验记录 1.通过 Console 登录 步骤1:连接配置电缆。 步骤2:启动PC,运行超级终端。 步骤3:进入Console 配置界面 2.通过 Telnet 登录 步骤1:通过 Console 接口配置 Telnet 用户。 步骤2:配置 super 口令 步骤3:配置登录欢迎…

神经网络构建原理(以MINIST为例)

神经网络构建原理(以MINIST为例) 在 MNIST 手写数字识别任务中&#xff0c;构建神经网络并训练模型来进行分类是经典的深度学习应用。MNIST 数据集包含 28x28 像素的手写数字图像&#xff08;0-9&#xff09;&#xff0c;任务是构建一个神经网络&#xff0c;能够根据输入的图像…

吉首大学--23级题目讲解

7-1 单链表基本操作 在 C/C 中&#xff0c;.&#xff08;点&#xff09;和 ->&#xff08;箭头&#xff09;运算符用于访问结构体或类的成员&#xff0c;但它们的使用场景不同。 1. . 运算符 . 运算符用于访问结构体或类的成员&#xff0c;通过对象或结构体变量直接访问。…

es的封装

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、类和接口介绍0.封装思想1.es的操作分类 二、创建索引1.成员变量2.构造函数2.添加字段3.发送请求4.创建索引总体代码 三.插入数据四.删除数据五.查询数据 前…

Element Plus 中Input输入框

通过鼠标或键盘输入字符 input为受控组件&#xff0c;他总会显示Vue绑定值&#xff0c;正常情况下&#xff0c;input的输入事件会正常被响应&#xff0c;他的处理程序应该更新组件的绑定值&#xff08;或使用v-model&#xff09;。否则&#xff0c;输入框的值将不会改变 不支…

windows环境下配置MySQL主从启动失败 查看data文件夹中.err发现报错unknown variable ‘log‐bin=mysql‐bin‘

文章目录 问题解决方法 问题 今天在windows环境下配置MySQL主从同步&#xff0c;在修改my.ini文件后发现MySQL启动失败了 打开my.ini检查参数发现没有问题 [mysqld] #开启二进制日志&#xff0c;记录了所有更改数据库数据的SQL语句 log‐bin mysql‐bin #设置服务id&#x…

[数据集][目标检测]不同颜色的安全帽检测数据集VOC+YOLO格式7574张5类别

重要说明&#xff1a;数据集里面有2/3是增强数据集&#xff0c;请仔细查看图片预览&#xff0c;确认符合要求在下载&#xff0c;分辨率均为640x640 数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件…

Python 二级考试

易错点 电脑基础知识 定义学生关系模式如下&#xff1a;Student &#xff08;S#&#xff0c; Sn&#xff0c; Ssex&#xff0c;class&#xff0c;monitorS#&#xff09;&#xff08;其属性分别为学号、学生名、性别、班级和班长学号&#xff09; 在关系模式中&#xff0c;如果…

python-SZ斐波那契数列/更相减损数

一&#xff1a;SZ斐波那契数列题目描述 你应该很熟悉斐波那契数列&#xff0c;不是吗&#xff1f;现在小理不知在哪里搞了个山寨版斐波拉契数列&#xff0c;如下公式&#xff1a; F(n) { $\ \ \ \ \ \ \ \ \ \ \ \ $ a,( n1) $\ \ \ \ \ \ \ \ \ \ \ \ $ b,( n2) $\ \ \ \ \ \ …

【优选算法之双指针】No.2--- 经典双指针算法(下)

文章目录 前言一、双指针示例&#xff1a;1.1 ⽔果成篮1.2 和为s的两个数字1.3 三数之和1.4 四数之和 二、双指针总结&#xff1a; 前言 &#x1f467;个人主页&#xff1a;小沈YO. &#x1f61a;小编介绍&#xff1a;欢迎来到我的乱七八糟小星球&#x1f31d; &#x1f4cb;专…

安装黑群晖系统,并使用NAS公网助手访问教程(好文)

由于正版群晖系统的价格不菲&#xff0c;对于预算有限的用户来说&#xff0c;安装黑群晖系统成为了一个不错的选择&#xff08;如果您预算充足&#xff0c;建议选择白群晖&#xff09;。如您对宅系科技比较感兴趣&#xff0c;欢迎查看本文&#xff0c;将详细介绍如何安装黑群晖…