Leetcode热题100道
👏作者简介:大家好,我是 枫度柚子🍁,Java摆烂选手,很高兴认识大家 👀
📕CSDN/掘金/B站: 枫吹过的柚 🍁
🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
🛰微信群: 加微信 **QaQ-linv **
🐧QQ群: 995832569
哈希
两数之和_1
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
class Solution {public int[] twoSum(int[] nums, int target) {if (nums == null || nums.length == 0)return new int[] {};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
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:
输入: strs = [""]
输出: [[""]]
示例 3:
输入: strs = ["a"]
输出: [["a"]]
- 将每串字符串的字符进行排序后作为key,然后放到对应的value集合里
class Solution {public List<List<String>> groupAnagrams(String[] strs) {if (strs == null || strs.length == 0)return new ArrayList<>();Map<String, List<String>> map = new HashMap<>();for (String str : strs) {char[] chs = str.toCharArray();Arrays.sort(chs);String key = new String(chs);// 根据key获取value集合List<String> values = map.getOrDefault(key, new ArrayList<>());values.add(str);map.put(key, values);}return new ArrayList<>(map.values());}
}
最长连续序列_128
给定一个未排序的整数数组 nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输出:9
- 将所有nums存入numset去重,遍历numset,用过的num存入另外一个usedset,然后持续遍历set
class Solution {public int longestConsecutive(int[] nums) {if (nums == null || nums.length == 0)return 0;Set<Integer> numset = new HashSet<>();for (int num : nums) {numset.add(num);}Set<Integer> usedset = new HashSet<>();int longest = 0;// 注意遍历的是numsetfor (int num : numset) {if (usedset.contains(num))continue;// 下一个连续的numint nextnum = num + 1;int curlongest = 1;while (numset.contains(nextnum)) {curlongest++;nextnum++;usedset.add(nextnum);}longest = Math.max(longest, curlongest);}return longest;}
}
双指针
移动零_283
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
// 去除多余,再补上0
class Solution {public void moveZeroes(int[] nums) {if (nums == null || nums.length == 0)return;// 返回去除0的索引位置int index = remmoveZero(nums);while (index < nums.length) {nums[index] = 0;index++;}}int remmoveZero(int[] nums) {int slow = 0, fast = 0;while (fast < nums.length) {if (nums[fast] != 0) {nums[slow] = nums[fast];slow++;}fast++;}return slow;}}
盛最多水的容器_11
给定一个长度为 n
的整数数组 height
。有 n
条垂线,第 i
条线的两个端点是 (i, 0)
和 (i, height[i])
。
找出其中的两条线,使得它们与 x
轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
**说明:**你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入:height = [1,1]
输出:1
- area = min(height[l … r]) * min(r-l)
class Solution {public int maxArea(int[] height) {if (height == null || height.length == 0)return 0;// 采用双指针的方式// area = Math.min(height[i]) * Math.max(r-l)int l = 0, r = height.length - 1;int max = 0;while (l < r) {int area = (r - l) * Math.min(height[l], height[r]);max = Math.max(max, area);if (height[l] < height[r])l++;elser--;}return max;}
}
三数之和_15
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
**注意:**答案中不可以包含重复的三元组。
示例 1:
输入: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] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
- 保证数组升序
- 少遍历一次
- 先确定一个再确认另外两个
- 不可以包含重复的三元组
class Solution {public List<List<Integer>> threeSum(int[] nums) {if (nums == null || nums.length == 0)return new ArrayList<>();int len = nums.length;// 先保证升序,后续使用指针Arrays.sort(nums);List<List<Integer>> res = new ArrayList<>();// 先确定好一个,再确定另外两个for (int i = 0; i < len - 1; i++) {// 答案中不可以包含重复的三元组if (i > 0 && nums[i - 1] == nums[i])continue;int target = -nums[i];// 采用双指针int l = i + 1, r = len - 1;while (l < r) {int sum = nums[l] + nums[r];if (sum > target)r--;else if (sum < target)l++;else {// 将三个数字放入结果集合res.add(Arrays.asList(nums[i], nums[l], nums[r]));l++;r--;// 答案中不可以包含重复的三元组while (l < r && nums[l - 1] == nums[l])l++;while (l < r && nums[r] == nums[r + 1])r--;}}}return res;}
}
接雨水_42
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入:height = [4,2,0,3,2,5]
输出:9
- water[i] = min( max(左边最高的柱子, max(右边最高的柱子) ) - height[i]
class Solution {public int trap(int[] height) {if (height == null || height.length == 0)return 0;int len = height.length;// 左边的高度int[] lmax = new int[len];// 右边的高度int[] rmax = new int[len];int res = 0;lmax[0] = height[0];rmax[len - 1] = height[len - 1];// 处理 left , left从1开始for (int i = 1; i < len; i++) {// 比较前面一个 和 当前的lmax[i] = Math.max(lmax[i - 1], height[i]);}// 处理right ,left从len - 2for (int i = len - 2; i > 0; i--) {rmax[i] = Math.max(rmax[i + 1], height[i]);}for (int i = 1; i < len - 1; i++) {res += Math.min(lmax[i], rmax[i]) - height[i];}return res;}
}
滑动窗口
无重复字符的最长子串_3
给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
- 用set存储上一次出现的字符
- 右边先删,左边再存
class Solution {public int lengthOfLongestSubstring(String s) {if (s == null || s.length() == 0)return 0;Set<Character> winset = new HashSet<>();int len = s.length();int l = 0, r = 0, max = 0;while (l < len && r < len) {// 右边的存在了,说明左边的已经有了if (winset.contains(s.charAt(r))) {winset.remove(s.charAt(l));l++;} else {winset.add(s.charAt(r));;r++;max = Math.max(r - l, max);}}return max;}
}
找到字符串中所有字母异位_438
给定两个字符串 s
和 p
,找到 s
中所有 p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
示例 1:
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
示例 2:
输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。
- 找出现的起始位置
- 2个26字母的数组,比较两个窗口里的字符出现次数
class Solution {public List<Integer> findAnagrams(String s, String p) {List<Integer> res = new ArrayList<>();if (s == null || s.length() == 0)return res;if (p == null || p.length() == 0)return res;int slen = s.length(), plen = p.length();// p的长度不能大于s的长度if (plen > slen)return res;// 26个字母int[] parr = new int[26];int[] sarr = new int[26];// 创建窗口 同时填充pArr// 先初始化好第一次的窗口for (int i = 0; i < plen; i++) {sarr[s.charAt(i) - 'a']++;parr[p.charAt(i) - 'a']++;}// 每次窗口往前移动一格// 窗口起始位置和终止位置int l = 0, r = plen - 1;// r不能超过sLenwhile (r < slen) {// 比较窗口内的内容, 如果窗口内容相等,就代表是异位词if (Arrays.equals(parr, sarr)) {res.add(l);}// 终止位置++,新的字符放入sArray数组r++;// 在终止位置不等于sLen时,可能会刚好等于sLenif (r != slen) {sarr[s.charAt(r) - 'a']++;}sarr[s.charAt(l) - 'a']--;l++;}return res;}
}
子串
和为K的子数组_560
给你一个整数数组 nums
和一个整数 k
,请你统计并返回 该数组中和为 k
的子数组的个数 。
子数组是数组中元素的连续非空序列。
示例 1
输入:nums = [1,1,1], k = 2
输出:2
示例 2:
输入:nums = [1,2,3], k = 3
输出:2
- 采用前缀和,k = pre + num
class Solution {// 前缀和// k = pre + num// 看map中key为pre - k的个数即可public int subarraySum(int[] nums, int k) {if (nums == null || nums.length == 0)return 0;Map<Integer, Integer> presumMap = new HashMap<>();// 对于下标为 0 的元素,前缀和为 0,个数为 1presumMap.put(0, 1);int presum = 0, count = 0;for (int num : nums) {presum += num;// 先获得前缀和为 presum - k 的个数,加到计数变量里if (presumMap.containsKey(presum - k)) {count += presumMap.get(presum - k);}presumMap.put(presum, presumMap.getOrDefault(presum, 0) + 1);}return count;}
}
滑动窗口最大值_239
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入: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 31 [3 -1 -3] 5 3 6 7 31 3 [-1 -3 5] 3 6 7 51 3 -1 [-3 5 3] 6 7 51 3 -1 -3 [5 3 6] 7 61 3 -1 -3 5 [3 6 7] 7
示例 2:
输入:nums = [1], k = 1
输出:[1]
- n - k + 1
class Solution {public int[] maxSlidingWindow(int[] nums, int k) {if(nums == null || nums.length == 0) return new int[]{};int n = nums.length;// n - k + 1个窗口int[] res = new int[n - k + 1];LinkedList<Integer> queue = new LinkedList<>();for(int i = 0;i < n; i++){while(!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]){queue.pollLast();}queue.addLast(i);if(queue.peek() < i - k + 1){queue.poll();}if(i - k + 1 >= 0){res[i - k + 1] = nums[queue.peek()];}}return res;}
}
最小覆盖子串_76
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
示例 2:
输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串。
示例 3:
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
class Solution {public String minWindow(String s, String t) {// 记录t中每个字符出现的次数Map<Character, Integer> need = new HashMap<>();// 作为窗口记录每次字符出现的次数Map<Character, Integer> window = new HashMap<>();for (char c : t.toCharArray()) {need.put(c, need.getOrDefault(c, 0) + 1);}int left = 0, right = 0;int valid = 0;// 记录最小覆盖子串的起始索引及长度int start = 0, len = Integer.MAX_VALUE;while (right < s.length()) {// c 是将移入窗口的字符char c = s.charAt(right);// 右移窗口right++;// 进行窗口内数据的一系列更新if (need.containsKey(c)) {window.put(c, window.getOrDefault(c, 0) + 1);// 窗口中已出现字符的次数与当前需要的出现一致,说明匹配字符okif (window.get(c).equals(need.get(c))) {valid++;}}// 判断左侧窗口是否要收缩,如果已经找到了一个满足条件的子串while (valid == need.size()) {int gap = right - left;// 在这里更新最小覆盖子串if (gap < len) {start = left;len = gap;}// d 是将移出窗口的字符char d = s.charAt(left);// 左 移窗口 为了下一次前进 去寻找满足条件的子串left++;// 进行窗口内数据的一系列更新if (need.containsKey(d)) {// 如果之前出现相同的并且将前一次出现次数减少1if (window.get(d).equals(need.get(d))) {valid--;}window.put(d, window.get(d) - 1);}}}// 返回最小覆盖子串return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);}
}
普通数组
最大子数组和_53
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
class Solution {public int maxSubArray(int[] nums) {if(nums == null || nums.length == 0) return 0;int len = nums.length;int[] dp = new int[len + 1];dp[0] = nums[0];int res = dp[0];for(int i = 1; i < len; i++){dp[i] = Math.max(dp[i - 1] + nums[i],nums[i]);res = Math.max(res,dp[i]);} return res;}
}
合并区间_56
以数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
class Solution {public int[][] merge(int[][] intervals) {if(intervals == null || intervals.length == 0) return new int[][]{};int len = intervals.length;Arrays.sort(intervals,Comparator.comparingInt(x->x[0]));LinkedList<int[]> res = new LinkedList<>();res.addLast(intervals[0]);for(int i = 1; i < len; i++){int[] cur = intervals[i];int[] last = res.getLast();if(cur[0] <= last[1]){last[1] = Math.max(last[1],cur[1]);}else{res.addLast(cur);}}return res.toArray(new int[0][0]);}
}
轮转数组_189
给定一个整数数组 nums
,将数组中的元素向右轮转 k
个位置,其中 k
是非负数。
示例 1:
输入: 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]
示例 2:
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]
class Solution {public void rotate(int[] nums, int k) {int n = nums.length;int[] newArr = new int[n];for (int i = 0; i < n; i++) {newArr[(i + k) % n] = nums[i];}System.arraycopy(newArr, 0, nums, 0, n);}
}
除自身以外数组的乘积_238
给你一个整数数组 nums
,返回 数组 answer
,其中 answer[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积 。
题目数据 保证 数组 nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请 **不要使用除法,**且在 O(*n*)
时间复杂度内完成此题。
示例 1:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
class Solution {public int[] productExceptSelf(int[] nums) {if(nums == null || nums.length == 0) return new int[]{};int pre = 1,suf = 1;int len = nums.length;int[] answer = new int[len];for(int i = 0; i < len; i++){answer[i] = pre;pre *= nums[i];}for(int i = len - 1; i >= 0; i--){answer[i] *= suf;suf *= nums[i];}return answer;}
}
缺失的第一个正数_41
给你一个未排序的整数数组 nums
,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n)
并且只使用常数级别额外空间的解决方案。
示例 1:
输入:nums = [1,2,0]
输出:3
示例 2:
输入:nums = [3,4,-1,1]
输出:2
示例 3:
输入:nums = [7,8,9,11,12]
输出:1
class Solution {// 遍历一遍将大于0的数放进一个比原数组大2的新数组中。在遍历新数组,找出第一个是0的下标// 遍历原数组,将大于零且小于新数组长度的值作为下标存储值为1。然后从1开始遍历新数组,第一个为0的下标即为最小正数public int firstMissingPositive(int[] nums) {int[] res = new int[nums.length + 2];// 遍历原数组,将大于零且小于新数组长度的值作为下标存储值为1for (int num : nums) {if (num < res.length && num > 0) {res[num] = 1;}}// 从1开始遍历新数组,第一个为0的下标即为最小正数for (int i = 1; i < res.length; i++) {if (res[i] == 0) {return i;}}return 1;}
}
矩阵
矩阵置零_73
给定一个 *m* x *n*
的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法**。**
示例 1:
输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]
示例 2:
输入:matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
输出:[[0,0,0,0],[0,4,5,0],[0,3,1,0]]
class Solution {
// 两遍扫matrix,第一遍用集合记录哪些行,哪些列有0;第二遍置0public void setZeroes(int[][] matrix) {// 横int m = matrix.length;// 纵int n = matrix[0].length;// 横-0Set<Integer> mZero = new HashSet<>();// 纵-0Set<Integer> nZero = new HashSet<>();for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (matrix[i][j] == 0) {mZero.add(i);nZero.add(j);}}}for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (mZero.contains(i) || nZero.contains(j)) {matrix[i][j] = 0;}}}}
}
螺旋矩阵_54
给你一个 m
行 n
列的矩阵 matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
class Solution {public List<Integer> spiralOrder(int[][] matrix) {if (matrix.length == 0) return new ArrayList<>();// 纵int m = matrix.length;// 横int n = matrix[0].length;// 左、右、上、下int l = 0, r = n - 1, t = 0, b = m - 1, x = 0;Integer[] res = new Integer[m * n];// 从左向右、从上向下、从右向左、从下向上while (true) {// 顶部// 左 => 右for (int i = l; i <= r; i++) {res[x++] = matrix[t][i];}if (++t > b) break;// 右侧// 上 => 下for (int i = t; i <= b; i++) {// 行变列不变res[x++] = matrix[i][r];}if (l > --r) break;// 底部// 右 => 左for (int i = r; i >= l; i--) {res[x++] = matrix[b][i];}if (t > --b) break;// 左侧// 下 => 上for (int i = b; i >= t; i--) {res[x++] = matrix[i][l];}if (++l > r) break;}return Arrays.asList(res);}
}
旋转图像_48
给定一个 n × n 的二维矩阵 matrix
表示一个图像。请你将图像顺时针旋转 90 度。
你必须在** 原地** 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
示例 2:
输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]
class Solution {// [[1,2,3],[4,5,6],[7,8,9]]// => [[7,4,1],[8,5,2],[9,6,3]]public void rotate(int[][] matrix) {int n = matrix.length;// 对于矩阵中第i行的第j个元素,在旋转后,它出现在倒数第i列的第j个位置// 由于矩阵中的行列从0开始计数,// 因此对于矩阵中的元素matrix[row][col],在旋转过后,它的新位置为matrix_new[col][n-row-1]// 4x4 =>// 0,0 -> 0,3 = 0,4-0-1// 0,1 -> 1,3 = 1,4-0-1// 0,2 -> 2,3 = 2,4-0-1// 0,3 -> 3,3 = 3,4-0-1int[][] matrixNew = new int[n][n];for (int row = 0; row < n; row++) {for (int col = 0; col < n; col++) {matrixNew[col][n - row - 1] = matrix[row][col];}}// 再将对应的i,j的值赋值给matrixfor (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {matrix[i][j] = matrixNew[i][j];}}}
}
搜索二维矩阵2_240
编写一个高效的算法来搜索 *m* x *n*
矩阵 matrix
中的一个目标值 target
。该矩阵具有以下特性:
- 每行的元素从左到右升序排列。
- 每列的元素从上到下升序排列。
示例 1:
输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true
示例 2:
输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
输出:false
class Solution {// 1.从矩阵 matrix 左下角元素(索引设为 (i, j) )开始遍历,并与目标值对比// 当 matrix[i][j] > target 时,执行 i-- ,即消去第 i 行元素。// 当 matrix[i][j] < target 时,执行 j++ ,即消去第 j 列元素。// 当 matrix[i][j] = target 时,返回 true ,代表找到目标值// 2.若行索引或列索引越界,则代表矩阵中无目标值,返回 falsepublic boolean searchMatrix(int[][] matrix, int target) {if (matrix == null || matrix.length == 0) return false;// 行int m = matrix.length;// 列int n = matrix[0].length;// 行,列int i = m - 1, j = 0;while (i >= 0 && j < n) {// 当前值 > 目标值,往上移动if (matrix[i][j] > target) {i--;}// 当前值 < 目标值,右边移动else if (matrix[i][j] < target) {j++;} else {return true;}}return false;}
}
链表
相交链表_160
图示两个链表在节点 c1
开始相交**:**
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal
- 相交的起始节点的值。如果不存在相交节点,这一值为0
listA
- 第一个链表listB
- 第二个链表skipA
- 在listA
中(从头节点开始)跳到交叉节点的节点数skipB
- 在listB
中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA
和 headB
传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
示例 2:
输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
/*** 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) {if(headA == null || headB == null) return null;// 指针 pA 指向 A 链表,指针 pB 指向 B 链表,依次往后遍历ListNode pA = headA,pB = headB;while(pA != pB){// 如果 pA 到了末尾,则 pA = headB 继续遍历pA = pA == null ? headB : pA.next;// 如果 pB 到了末尾,则 pB = headA 继续遍历pB = pB == null ? headA : pB.next;} return aNode;}
}
反转链表_206
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
/*** 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) {if(head == null) return null;// 当前节点cur,前一个节点ListNode cur = head,pre = null;while(cur != null){// 暂存后继节点 cur.nextListNode next = cur.next;// # 修改 next 引用指向cur.next = pre;// pre 暂存 curpre = cur;// cur 访问下一节点cur = next;}return pre;}
}
回文链表_234
给你一个单链表的头节点 head
,请你判断该链表是否为回文链表。如果是,返回 true
;否则,返回 false
。
示例 1:
输入:head = [1,2,2,1]
输出:true
示例 2:
输入:head = [1,2]
输出:false
/*** 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) {if(head == null) return false;ListNode slow = middleNode(head);ListNode lNode = head;ListNode rNode = reverse(slow);while(rNode != null){if(lNode.val != rNode.val) return false;lNode = lNode.next;rNode = rNode.next;}return true;}ListNode middleNode(ListNode head){if(head == null) return null;ListNode fast = head,slow = head;while(fast != null && fast.next != null){fast = fast.next.next;slow = slow.next;}if(fast != null){slow = slow.next;}return slow;}ListNode reverse(ListNode head){if(head == null) return null;ListNode cur = head,pre = null;while(cur != null){ListNode next = cur.next;cur.next = pre;pre = cur;cur = next;}return pre;}
}
环形链表_141
给你一个链表的头节点 head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true
。 否则,返回 false
。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
/*** 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) {if(head == null) return false;ListNode fast = head,slow = head;while(fast != null && fast.next != null){fast = fast.next.next;slow = slow.next;if(fast == slow) return true;}return false;}
}
环形链表II_142
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
/*** Definition for singly-linked list.* class ListNode {* int val;* ListNode next;* ListNode(int x) {* val = x;* next = null;* }* }*/
public class Solution {// slow 走 k,fast 走 2k,要找到环的位置,只要先找到相遇的时候停下,让fast从头走slow剩下的kpublic ListNode detectCycle(ListNode head) {if(head == null) return null;ListNode fast = head,slow = head;while(fast != null && fast.next != null){fast = fast.next.next;slow = slow.next;if(fast == slow) break;}// fast 遇到空指针说明没有环if(fast == null || fast.next == null) return null;fast = head;// slow走k,fast也走kwhile(fast != slow){fast = fast.next;slow = slow.next;}return fast;}
}
合并两个有序链表_21
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
/*** 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) {if(list1 == null) return list2;if(list2 == null) return list1;ListNode l1 = list1,l2 = list2,newNode = new ListNode(-1), p = newNode;while(l1 != null && l2 != null){if(l1.val < l2.val){p.next = l1;l1 = l1.next;}else{p.next = l2;l2 = l2.next;}p = p.next;}if(l1 != null) p.next = l1;if(l2 != null) p.next = l2;return newNode.next;}
}
两数相加_2
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
/*** 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) {if(l1 == null) return l2;if(l2 == null) return l1;ListNode newNode = new ListNode(-1),p = newNode;int carry = 0;while(l1 != null || l2 != null){int n1 = l1 != null ? l1.val : 0;int n2 = l2 != null ? l2.val : 0;int sum = n1 + n2 + carry;p.next = new ListNode(sum % 10);carry = sum / 10;p = p.next;if(l1 != null) l1 = l1.next;if(l2 != null) l2 = l2.next;}if(carry > 0) p.next = new ListNode(carry);return newNode.next;}
}
删除链表的倒数第N个结点_19
相关标签
相关企业
提示
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
/*** 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 {// 要删除倒数第N个节点,就得获取倒数第N+1个节点的引用// 获取单链表的倒数第k个节点,利用双指针 只需要遍历一次,就算出导出第k个节点public ListNode removeNthFromEnd(ListNode head, int n) {if(head == null) return null;ListNode newNode = new ListNode(-1);// 让fast 比 slow快一步ListNode fast = head, slow = newNode;slow.next = head;// 先让fast走nfor(int i = 0; i < n; i++){fast = fast.next;}// 当fast到达空结点时停止循环,此时的slow正好指向倒数第k个节点// fast 剩余 n - k,走完,就刚好是slow的倒数k,也就是要求slow走n-kwhile(fast != null){fast = fast.next;slow = slow.next;}// 删除第k个if(slow.next != null){slow.next = slow.next.next;}return newNode.next;}
}
两两交换链表中的节点_24
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
/*** 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) {// 终止条件if (head == null || head.next == null) return head;// 假设链表是 1->2->3->4// 这句就先保存节点2ListNode tmp = head.next;// 继续递归,处理节点3->4// 当递归结束返回后,就变成了4->3// 于是head节点就指向了4,变成1->2->4->3head.next = swapPairs(tmp.next);// 将2->1 => 2->1->4->3tmp.next = head;return tmp;}
}
K个一组翻转链表_25
给你链表的头节点 head
,每 k
个节点一组进行翻转,请你返回修改后的链表。
k
是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k
的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 1:
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
示例 2:
输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]
/*** 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 reverseKGroup(ListNode head, int k) {if (head == null) return null;// 区间 [a, b) 包含 k 个待反转元素ListNode a = head, b = head;// 不足 k 个,不需要反转,base casefor (int i = 0; i < k; i++) {if (b == null) return head;b = b.next;}// 反转前 k 个元素ListNode newHead = reverse(a, b);// 从第k个b开始继续反复执行a.next = reverseKGroup(b, k);return newHead;}// 反转区间 [a, b) 的元素,注意是左闭右开public ListNode reverse(ListNode a, ListNode b) {ListNode pre = null;ListNode cur = a;// while 终止的条件改一下就行了while (cur != b) {// 暂存后继节点 先接一下当前节点的下一个节点ListNode tmp = cur.next;// 引用指向更换 当前的下一个节点指向precur.next = pre;// 暂存当前节点 pre指针右移pre = cur;// 访问下一个节点 curr指针右移cur = tmp;}// 返回反转后的头结点return pre;}
}
随机链表的复制_138
给你一个长度为 n
的链表,每个节点包含一个额外增加的随机指针 random
,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n
个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next
指针和 random
指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X
和 Y
两个节点,其中 X.random --> Y
。那么在复制链表中对应的两个节点 x
和 y
,同样有 x.random --> y
。
返回复制链表的头节点。
用一个由 n
个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index]
表示:
val
:一个表示Node.val
的整数。random_index
:随机指针指向的节点索引(范围从0
到n-1
);如果不指向任何节点,则为null
。
你的代码 只 接受原链表的头节点 head
作为传入参数。
示例 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]]
/*
// 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);}
}
排序链表_148
给你链表的头结点 head
,请将其按 升序 排列并返回 排序后的链表 。
示例 1:
输入:head = [4,2,1,3]
输出:[1,2,3,4]
示例 2:
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
示例 3:
输入:head = []
输出:[]
/*** 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 {// 归并方式// 1.找到链表的中点,以中点为分界,将链表拆分成两个子链表。// 寻找链表的中点可以使用快慢指针的做法,快指针每次移动 2 步,慢指针每次移动 1 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点// 2.对两个子链表分别排序// 3.将两个排序后的子链表合并,得到完整的排序后的链表public ListNode sortList(ListNode head) {if (head == null || head.next == null) return head;// 第一步,将链表平均分成两半,所以需要找中点,这里利用快慢指针ListNode slow = middleNode(head);// 这里获取到第二个链表的头结点,从中点断开ListNode newHead = slow.next;slow.next = null;// 分为两边 左边 / 右边ListNode lNode = sortList(head);ListNode rNode = sortList(newHead);// 用新节点串起来ListNode dummy = new ListNode(0);ListNode res = dummy;// 左右两边都不能为nullwhile (lNode != null && rNode != null) {if (lNode.val < rNode.val) {dummy.next = lNode;lNode = lNode.next;} else {dummy.next = rNode;rNode = rNode.next;}dummy = dummy.next;}// 剩下的dummy.next = lNode != null ? lNode : rNode;return res.next;}private ListNode middleNode(ListNode head) {ListNode fast = head.next, slow = head;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;}return slow;}
}
合并K个升序链表_23
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[1->4->5,1->3->4,2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
/*** 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 mergeKLists(ListNode[] lists) {// 判断链表是否为Nullif (lists == null) return null;// 优先级队列 升序处理Queue<ListNode> queue = new PriorityQueue<>(Comparator.comparingInt(x -> x.val));// 把node全部添加到最小堆里去了for (ListNode head : lists) {while (head != null) {// 入队queue.offer(head);head = head.next;}}ListNode dummy = new ListNode();ListNode head = dummy;// 队列不为空,就出队while (!queue.isEmpty()) {// 把最小的元素先拿到,然后移除ListNode minNode = queue.poll();head.next = minNode;head = head.next;if (queue.isEmpty()) {head.next = null;}}return dummy.next;}
}
LRU缓存_146
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache
类:
LRUCache(int capacity)
以 正整数 作为容量capacity
初始化 LRU 缓存int get(int key)
如果关键字key
存在于缓存中,则返回关键字的值,否则返回-1
。void put(int key, int value)
如果关键字key
已经存在,则变更其数据值value
;如果不存在,则向缓存中插入该组key-value
。如果插入操作导致关键字数量超过capacity
,则应该 逐出 最久未使用的关键字。
函数 get
和 put
必须以 O(1)
的平均时间复杂度运行。
示例:
输入
["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
class LRUCache {private LinkedHashMap<Integer, Integer> map;private Integer capacity;public LRUCache(int capacity) {this.map = new LinkedHashMap<>(capacity + 1, 0.75f, true);this.capacity = capacity;}public int get(int key) {if (!map.containsKey(key)) return -1;return map.get(key);}public void put(int key, int value) {map.put(key, value);if (map.size() > capacity){// 链表头部就是最久未使用的 keymap.remove(map.keySet().iterator().next());}}
}/*** 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);*/
二叉树
二叉树的中序遍历_94
给定一个二叉树的根节点 root
,返回 它的 中序 遍历 。
示例 1:
输入:root = [1,null,2,3]
输出:[1,3,2]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {// 中序遍历,左根右public List<Integer> inorderTraversal(TreeNode root) {List<Integer> res = new ArrayList<>();if (root == null) return res;return traverse(root, res);}private List<Integer> traverse(TreeNode root, List<Integer> res) {if (root.left != null) {res = traverse(root.left, res);}res.add(root.val);if (root.right != null) {res = traverse(root.right, res);}return res;}
}
二叉树的最大深度_104
给定一个二叉树 root
,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:3
示例 2:
输入:root = [1,null,2]
输出:2
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {// 输入一个节点,返回以该节点为根的二叉树的最大深度// 根据左右子树的最大深度推出原二叉树的最大深度public int maxDepth(TreeNode root) {if (root == null) return 0;int lMax = maxDepth(root.left);int rMax = maxDepth(root.right);return Math.max(lMax, rMax) + 1;}
}
翻转二叉树_226
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
示例 1:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
示例 2:
输入:root = [2,1,3]
输出:[2,3,1]
示例 3:
输入:root = []
输出:[]
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {// 左右子树,位置调换// 其实就是交换一下左右节点,然后再递归的交换左节点// 终止条件: 当前节点 = null时返回// 交换当前节点的左右节点,再递归的交换当前节点的左节点,递归的交换当前节点的右节点public TreeNode invertTree(TreeNode root) {if (root == null) return null;// 下面三句是将当前节点的左右子树交换TreeNode tmp = root.right;root.right = root.left;root.left = tmp;// 递归交换当前节点的 左子树invertTree(root.left);// 递归交换当前节点的 右子树invertTree(root.right);// 函数返回时就表示当前这个节点,以及它的左右子树// 都已经交换完了return root;}
}
对称二叉树_101
给你一个二叉树的根节点 root
, 检查它是否轴对称。
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {// 判断是否对称public boolean isSymmetric(TreeNode root) {if (root == null) return true;// 检查两棵子树是否对称return check(root.left, root.right);}private boolean check(TreeNode left, TreeNode right) {if (left == null && right == null) return true;if (left == null || right == null) return false;// 两个根节点需要相同if (left.val != right.val) return false;// 左右子节点需要对称相同return check(left.left, right.right) && check(left.right, right.left);}
}
二叉树的直径_543
给你一棵二叉树的根节点,返回该树的 直径 。
二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root
。
两节点之间路径的 长度 由它们之间边数表示。
示例 1:
输入:root = [1,2,3,4,5]
输出:3
解释:3 ,取路径 [4,2,1,3] 或 [5,2,1,3] 的长度。
示例 2:
输入:root = [1,2]
输出:1
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {/*** 所谓二叉树的直径,就是左右子树的最大深度之和,那么直接的想法是对每个节点计算左右子树的最大高度,* 得出每个节点的直径,从而得出最大的那个直径。*/int max = Integer.MIN_VALUE;public int diameterOfBinaryTree(TreeNode root) {depth(root);return max;}int depth(TreeNode root) {// 访问到空节点了,返回0if (root == null) return 0;// 左儿子为根的子树的深度int lDepth = depth(root.left);// 右儿子为根的子树的深度int rDepth = depth(root.right);//将每个节点最大直径(左子树深度+右子树深度)当前最大值比较并取大者max = Math.max(max, lDepth + rDepth);// 返回该节点为根的子树的深度return Math.max(lDepth, rDepth) + 1;}}
二叉树的层序遍历_102
给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {List<List<Integer>> res = new LinkedList<>();// 即逐层地,从左到右访问所有节点// BFSpublic List<List<Integer>> levelOrder(TreeNode root) {if (root == null) return res;Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);// while 循环控制从上向下一层层遍历while (!queue.isEmpty()) {int size = queue.size();// 记录这一层的节点值List<Integer> level = new LinkedList<>();// for 循环控制每一层从左向右遍历for (int i = 0; i < size; i++) {// 出队TreeNode cur = queue.poll();if (cur != null) {level.add(cur.val);if (cur.left != null) queue.offer(cur.left);if (cur.right != null) queue.offer(cur.right);}}res.add(level);}return res;}
}
将有序数组转换为二叉搜索树_108
给你一个整数数组 nums
,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
示例 1:
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:
示例 2:
输入:nums = [1,3]
输出:[3,1]
解释:[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。
class Solution {public TreeNode sortedArrayToBST(int[] nums) {return helper(nums, 0, nums.length - 1);}public TreeNode helper(int[] nums, int left, int right) {if (left > right) {return null;}// 总是选择中间位置左边的数字作为根节点int mid = (left + right) / 2;TreeNode root = new TreeNode(nums[mid]);root.left = helper(nums, left, mid - 1);root.right = helper(nums, mid + 1, right);return root;}
}
验证二叉搜索树_98
给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3]
输出:true
示例 2:
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {/*** 节点的左子树只包含 小于 当前节点的数。* 节点的右子树只包含 大于 当前节点的数。* 所有左子树和右子树自身必须也是二叉搜索树。*/public boolean isValidBST(TreeNode root) {return valid(root, null, null);}private boolean valid(TreeNode root, TreeNode left, TreeNode right) {if (root == null) return true;if ((left != null && left.val >= root.val) ||(right != null && right.val <= root.val)) return false;return valid(root.left, left, root) &&valid(root.right, root, right);}
}
二叉搜索树中第K小的元素_230
给定一个二叉搜索树的根节点 root
,和一个整数 k
,请你设计一个算法查找其中第 k
个最小元素(从 1 开始计数)。
示例 1:
输入:root = [3,1,4,null,2], k = 1
输出:1
示例 2:
输入:root = [5,3,6,2,4,null,null,1], k = 3
输出:3
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {private int res = 0;private int rank = 0;public int kthSmallest(TreeNode root, int k) {// 中序遍历traverse(root,k);return res;}public void traverse(TreeNode root,int k){if(root == null) return;traverse(root.left,k);rank++;if(k == rank) {res = root.val;return;}traverse(root.right,k);}}
二叉树的右视图_199
给定一个二叉树的 根节点 root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
示例 1:
输入: [1,2,3,null,5,null,4]
输出: [1,3,4]
示例 2:
输入: [1,null,3]
输出: [1,3]
示例 3:
输入: []
输出: []
class Solution {public List<Integer> rightSideView(TreeNode root) {Map<Integer, Integer> rightmostValueAtDepth = new HashMap<Integer, Integer>();int max_depth = -1;Deque<TreeNode> nodeStack = new LinkedList<TreeNode>();Deque<Integer> depthStack = new LinkedList<Integer>();nodeStack.push(root);depthStack.push(0);while (!nodeStack.isEmpty()) {TreeNode node = nodeStack.pop();int depth = depthStack.pop();if (node != null) {// 维护二叉树的最大深度max_depth = Math.max(max_depth, depth);// 如果不存在对应深度的节点我们才插入if (!rightmostValueAtDepth.containsKey(depth)) {rightmostValueAtDepth.put(depth, node.val);}nodeStack.push(node.left);nodeStack.push(node.right);depthStack.push(depth + 1);depthStack.push(depth + 1);}}List<Integer> rightView = new ArrayList<Integer>();for (int depth = 0; depth <= max_depth; depth++) {rightView.add(rightmostValueAtDepth.get(depth));}return rightView;}
}
二叉树展开为链表_114
给你二叉树的根结点 root
,请你将它展开为一个单链表:
- 展开后的单链表应该同样使用
TreeNode
,其中right
子指针指向链表中下一个结点,而左子指针始终为null
。 - 展开后的单链表应该与二叉树 先序遍历 顺序相同。
示例 1:
输入:root = [1,2,5,3,4,null,6]
输出:[1,null,2,null,3,null,4,null,5,null,6]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [0]
输出:[0]
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {// 其中 right 子指针指向链表中下一个结点,而左子指针始终为 null// 将 root 的左子树和右子树拉平// 将 root 的右子树接到左子树下方,然后将整个左子树作为右子树public void flatten(TreeNode root) {if (root == null) return;// 先递归拉平左右子树flatten(root.left);flatten(root.right);// 后序遍历位置// 左右子树已经被拉平成一条链表TreeNode left = root.left;TreeNode right = root.right;// 将左子树作为右子树root.left = null;root.right = left;// 将原先的右子树接到当前右子树的末端TreeNode p = root;while (p.right != null) {p = p.right;}p.right = right;}
}
从前序与中序遍历序列构造二叉树_105
给定两个整数数组 preorder
和 inorder
,其中 preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
示例 1:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
示例 2:
输入: preorder = [-1], inorder = [-1]
输出: [-1]
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {// 前序: 根左右// 中序: 左根右public TreeNode buildTree(int[] preorder, int[] inorder) {if (preorder == null ||inorder == null ||preorder.length == 0 ||inorder.length == 0) return null;// 前序遍历的长度int preLen = preorder.length;// 中序遍历的长度int inLen = inorder.length;if (preLen != inLen) return null;// 谦虚遍历TreeNode root = new TreeNode(preorder[0]);for (int i = 0; i < preLen; i++) {// 找到根节点if (inorder[i] == root.val) {// 1 = 左 i = 根 i+1=右// 【0,i)是左root.left = buildTree(Arrays.copyOfRange(preorder, 1, i + 1),Arrays.copyOfRange(inorder, 0, i));// (i,preLen)是右root.right = buildTree(Arrays.copyOfRange(preorder, i + 1, preLen), Arrays.copyOfRange(inorder, i + 1, preLen));}}return root;}
}
路径总和3_437
给定一个二叉树的根节点 root
,和一个整数 targetSum
,求该二叉树里节点值之和等于 targetSum
的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
示例 1:
输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。
示例 2:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:3
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {// 记录前缀和// 定义:从二叉树的根节点开始,路径和为 pathSum 的路径有 preSumCount.get(pathSum) 个Map<Integer, Integer> preSumCount;int res = 0;int targetSum;public int pathSum(TreeNode root, int targetSum) {this.targetSum = targetSum;preSumCount = new HashMap<>();preSumCount.put(0, 1);backtrack(0, root);return res;}private void backtrack(int sum, TreeNode root) {if (root == null) return;sum += root.val;if (preSumCount.containsKey(sum - targetSum) && preSumCount.get(sum - targetSum) >= 1) {res += preSumCount.get(sum - targetSum);}preSumCount.put(sum, preSumCount.getOrDefault(sum, 0) + 1);backtrack(sum, root.left);backtrack(sum, root.right);preSumCount.put(sum, preSumCount.get(sum) - 1);}
}
二叉树的最近公共祖先_236
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
示例 3:
输入:root = [1,2], p = 1, q = 2
输出:1
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode(int x) { val = x; }* }*/
class Solution {// 两个节点p、q,分为两种情况// p和q在相同子树中// p和q在不同子树中// 从根节点遍历,递归向左右子树查询节点信息// 递归终止条件:如果当前节点为空或等于 p 或 q,则返回当前节点// 递归遍历左右子树,如果左右子树查到节点都不为空,则表明 p 和 q 分别在左右子树中,因此,当前节点即为最近公共祖先// 如果左右子树其中一个不为空,则返回非空节点。public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if (root == null || root == p || root == q) {return root;}TreeNode lNode = lowestCommonAncestor(root.left, p, q);TreeNode rNode = lowestCommonAncestor(root.right, p, q);if (lNode != null && rNode != null) return root;return lNode != null ? lNode : rNode;}
}
二叉树中的最大路径和_124
二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root
,返回其 最大路径和 。
示例 1:
输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6
示例 2:
输入:root = [-10,9,20,null,null,15,7]
输出:42
解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {int max = Integer.MIN_VALUE;// 二叉树 abc,a 是根结点(递归中的 root),// bc 是左右子结点(代表其递归后的最优解)// 返回经过root的单边分支最大和, 即Math.max(root, root+left, root+right)public int maxPathSum(TreeNode root) {if (root == null) return 0;dfs(root);return max;}private int dfs(TreeNode root) {if (root == null) return 0;// 计算左边分支最大值,左边分支如果为负数还不如不选择int lMax = Math.max(0, dfs(root.left));//计算右边分支最大值,右边分支如果为负数还不如不选择int rMax = Math.max(0, dfs(root.right));// left->root->right 作为路径与已经计算过历史最大值做比较max = Math.max(max, root.val + lMax + rMax);// 返回经过root的单边最大分支给当前root的父节点计算使用return root.val + Math.max(lMax, rMax);}
}
图论
岛屿数量_200
给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]
]
输出:1
示例 2:
输入:grid = [["1","1","0","0","0"],["1","1","0","0","0"],["0","0","1","0","0"],["0","0","0","1","1"]
]
输出:3
class Solution {public int numIslands(char[][] grid) {if(grid == null || grid.length == 0) return 0;int m = grid.length;int n = grid[0].length;int res = 0;for(int i = 0;i < m;i++){for(int j = 0;j < n;j++){if(grid[i][j] == '1'){res += 1;dfs(grid,i,j,m,n);}}}return res;}void dfs(char[][] grid,int x,int y,int m,int n){// 边界问题if(x < 0 || y < 0 || x >= m || y >= n || grid[x][y] == '0') return;// 将之前的淹没grid[x][y] = '0';dfs(grid,x-1,y,m,n);dfs(grid,x+1,y,m,n);dfs(grid,x,y-1,m,n);dfs(grid,x,y+1,m,n);}}
腐烂的橘子_994
在给定的 m x n
网格 grid
中,每个单元格可以有以下三个值之一:
- 值
0
代表空单元格; - 值
1
代表新鲜橘子; - 值
2
代表腐烂的橘子。
每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。
返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1
。
示例 1:
输入:grid = [[2,1,1],[1,1,0],[0,1,1]]
输出:4
示例 2:
输入:grid = [[2,1,1],[0,1,1],[1,0,1]]
输出:-1
解释:左下角的橘子(第 2 行, 第 0 列)永远不会腐烂,因为腐烂只会发生在 4 个方向上。
示例 3:
输入:grid = [[0,2]]
输出:0
解释:因为 0 分钟时已经没有新鲜橘子了,所以答案就是 0 。
class Solution {// 主函数,计算岛屿数量public int numIslands(char[][] grid) {int res = 0;int m = grid.length, n = grid[0].length;// 遍历 gridfor (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (grid[i][j] == '1') {// 每发现一个岛屿,岛屿数量加一res++;// 然后使用 DFS 将岛屿淹了dfs(grid, i, j);}}}return res;}// 从 (i, j) 开始,将与之相邻的陆地都变成海水void dfs(char[][] grid, int i, int j) {int m = grid.length, n = grid[0].length;if (i < 0 || j < 0 || i >= m || j >= n) {// 超出索引边界return;}if (grid[i][j] == '0') {// 已经是海水了return;}// 将 (i, j) 变成海水grid[i][j] = '0';// 淹没上下左右的陆地dfs(grid, i + 1, j);dfs(grid, i, j + 1);dfs(grid, i - 1, j);dfs(grid, i, j - 1);}
}
课程表_207
你这个学期必须选修 numCourses
门课程,记为 0
到 numCourses - 1
。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites
给出,其中 prerequisites[i] = [ai, bi]
,表示如果要学习课程 ai
则 必须 先学习课程 bi
。
- 例如,先修课程对
[0, 1]
表示:想要学习课程0
,你需要先完成课程1
。
请你判断是否可能完成所有课程的学习?如果可以,返回 true
;否则,返回 false
。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
class Solution {// 拓扑排序public boolean canFinish(int numCourses, int[][] prerequisites) {// 用来进行记录前者和后者的关系,后者指向前者所以记录前者的入度List<List<Integer>> edges = new ArrayList<>();// 用来记录上家的入度int[] inorder = new int[numCourses];for(int i = 0; i < numCourses;i++){edges.add(new ArrayList<>());}for(int[] edge: prerequisites){edges.get(edge[1]).add(edge[0]);inorder[edge[0]]++;}Queue<Integer> queue = new LinkedList<>();// 将入度为0的加入到queue中for(int i = 0; i < numCourses;i++){if(inorder[i] == 0){queue.add(i);}}int finish = 0;while(!queue.isEmpty()){Integer poll = queue.poll();List<Integer> edge = edges.get(poll);for(Integer next:edge){// 入度--inorder[next]--;if(inorder[next] == 0){queue.add(next);}}finish++;}return finish == numCourses;}
}
实现Trie前缀树_208
Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
请你实现 Trie 类:
Trie()
初始化前缀树对象。void insert(String word)
向前缀树中插入字符串word
。boolean search(String word)
如果字符串word
在前缀树中,返回true
(即,在检索之前已经插入);否则,返回false
。boolean startsWith(String prefix)
如果之前已经插入的字符串word
的前缀之一为prefix
,返回true
;否则,返回false
。
示例:
输入
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
输出
[null, null, true, false, true, null, true]解释
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // 返回 True
trie.search("app"); // 返回 False
trie.startsWith("app"); // 返回 True
trie.insert("app");
trie.search("app"); // 返回 True
class Trie {// 指向子节点的指针数组children,数组长度为26,即小写英文字母的数量,此时children[0]对应的是a...children[25]对应的是zprivate Trie[] children;// 表示该节点是否为字符串的结尾private boolean isEnd;public Trie() {children = new Trie[26];isEnd = false;}public void insert(String word) {Trie node = this;for (int i = 0; i < word.length(); i++) {char ch = word.charAt(i);int index = ch - 'a';// index位置无值,存放if (node.children[index] == null) {node.children[index] = new Trie();}// 下一个node = node.children[index];}node.isEnd = true;}public boolean search(String word) {Trie node = searchPrefix(word);return node != null && node.isEnd;}public boolean startsWith(String prefix) {return searchPrefix(prefix) != null;}Trie searchPrefix(String prefix) {Trie node = this;for (int i = 0; i < prefix.length(); i++) {char ch = prefix.charAt(i);int index = ch - 'a';// 子节点不存在。说明字典树中不包含该前缀,返回空指针if (node.children[index] == null) {return null;}// 子节点存在。沿着指针移动到子节点,继续搜索下一个字符node = node.children[index];}return node;}}
回溯
全排列_46
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]
class Solution {// 结果List<List<Integer>> pathList = new LinkedList<>();boolean[] used;List<Integer> path = new LinkedList<>();public List<List<Integer>> permute(int[] nums) {int len = nums.length;if (len == 0) return pathList;used = new boolean[len];backTrace(nums);return pathList;}// 路径:记录在 path 中// 选择列表:nums 中不存在于 path 的那些元素(used[i] 为 false)// 结束条件:nums 中的元素全都在 path 中出现private void backTrace(int[] nums) {if (path.size() == nums.length) {pathList.add(new LinkedList<>(path));return;}for (int i = 0; i < nums.length; i++) {if (used[i]) continue;path.add(nums[i]);used[i] = true;// 进入下一次backTrace(nums);path.remove(path.size() - 1);used[i] = false;}}
}
子集_78
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
class Solution {List<List<Integer>> pathList = new LinkedList<>();List<Integer> path = new LinkedList<>();public List<List<Integer>> subsets(int[] nums) {backtrack(nums, 0, nums.length);return pathList;}private void backtrack(int[] nums, int l, int r) {// 添加路径数组到结果数组中pathList.add(new LinkedList<>(path));while (l < r) {// 做选择,将选择添加到路径数组中path.add(nums[l]);// 回溯法backTrace(nums, l + 1, r);// 撤销选择,将选择从路径中删除path.remove(path.size() - 1);l++;}}
}
电话号码的字母组合_17
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = ""
输出:[]
示例 3:
输入:digits = "2"
输出:["a","b","c"]
class Solution {/*** 初始化对应所有的数字,为了直接对应2-9,新增了两个无效的字符串*/private String[] lettersOnDigit ={"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};/*** 结果*/private List<String> pathList = new LinkedList<>();public List<String> letterCombinations(String digits) {if (digits == null || digits.length() == 0) {return pathList;}// 深度优先遍历dfs(new StringBuilder(), digits);return pathList;}private void dfs(StringBuilder path, String digits) {// 判断是否走到了叶子节点->判断path的长度是否和digits的长度一样// 代表所有情况都达到了if (path.length() == digits.length()) {pathList.add(path.toString());return;}// 当前数字int curDigit = digits.charAt(path.length()) - '0';// 当前数字 对应的字符串 -> lettersOnDigit[curDigit]String curLetters = lettersOnDigit[curDigit];// 遍历字符串for (char c : curLetters.toCharArray()) {// 加入到当前pathpath.append(c);// 进行下一次dfs(path, digits);// 当从递归中走出来时,删除末尾的继续尝试path.deleteCharAt(path.length() - 1);}}
}
组合总和_39
给你一个 无重复元素 的整数数组 candidates
和一个目标整数 target
,找出 candidates
中可以使数字和为目标数 target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于 150
个。
示例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
示例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
示例 3:
输入: candidates = [2], target = 1
输出: []
class Solution {// 记录集List<List<Integer>> pathList = new ArrayList<>();// 记录路径List<Integer> path = new ArrayList<>();// 记录sumint sum = 0;public List<List<Integer>> combinationSum(int[] candidates, int target) {int len = candidates.length;dfs(candidates, target, 0, len - 1);return pathList;}private void dfs(int[] candidates, int target, int start, int end) {// sum > targetif (sum > target) {return;}if (sum == target) {pathList.add(new ArrayList<>(path));return;}while (start <= end) {int candidate = candidates[start];path.add(candidate);sum += candidate;// 为什么从start开始,而不是start + 1 因为还可以可以继续取用之前的值,可以重复使用dfs(candidates, target, start, end);// 减去上一个sum -= candidate;// 移除之前的path.remove(path.size() - 1);start++;}}
}
括号生成_22
数字 n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:
输入:n = 1
输出:["()"]
class Solution {// 记录结果private List<String> pathList = new LinkedList<>();// 括号对数private int maxCount = 0;public List<String> generateParenthesis(int n) {if (n == 0) return pathList;// 设置括号对数maxCount = n;// 回溯法backtrack("", 0, 0);return pathList;}// lCount: 左括号数量 rCount: 右括号数量private void backtrack(String path, int lCount, int rCount) {// 判断括号对数是否达到if (path.length() == maxCount * 2) {pathList.add(path);return;}// 如果左括号没有达到if (lCount < maxCount) {backtrack(path + "(", lCount + 1, rCount);}// 如果右括号的数量小于左括号数量if (rCount < lCount) {backtrack(path + ")", lCount, rCount + 1);}}
}
单词搜索_79
给定一个 m x n
二维字符网格 board
和一个字符串单词 word
。如果 word
存在于网格中,返回 true
;否则,返回 false
。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"
输出:true
示例 3:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCB"
输出:false
class Solution {int m, n, wl;char[] letters;char[][] board;boolean[][] visited;// 回溯法public boolean exist(char[][] board, String word) {// 宽this.m = board.length;// 长this.n = board[0].length;// 单词长度this.wl = word.length();this.letters = word.toCharArray();this.board = board;this.visited = new boolean[m][n];for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {// 0 是word的indexboolean res = backtrack(i, j, 0);if (res) return true;}}return false;}private boolean backtrack(int i, int j, int wi) {// word的index >= w 说明找到了if (wi >= wl) {return true;}// 说明没找到|或者已经访问过了if (i < 0 || j < 0 || i >= m || j >= n || letters[wi] != board[i][j] || visited[i][j]) return false;// 访问过了就不能再去看了visited[i][j] = true;// 单词前进wi++;boolean res = backtrack(i + 1, j, wi) ||backtrack(i, j + 1, wi ) ||backtrack(i - 1, j, wi) ||backtrack(i, j - 1, wi);visited[i][j] = false;return res;}
}
分割回文串_131
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回 s
所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a"
输出:[["a"]]
class Solution {boolean[][] f;List<List<String>> ret = new ArrayList<List<String>>();List<String> ans = new ArrayList<String>();int n;public List<List<String>> partition(String s) {n = s.length();f = new boolean[n][n];for (int i = 0; i < n; ++i) {Arrays.fill(f[i], true);}for (int i = n - 1; i >= 0; --i) {for (int j = i + 1; j < n; ++j) {f[i][j] = (s.charAt(i) == s.charAt(j)) && f[i + 1][j - 1];}}dfs(s, 0);return ret;}public void dfs(String s, int i) {if (i == n) {ret.add(new ArrayList<String>(ans));return;}for (int j = i; j < n; ++j) {if (f[i][j]) {ans.add(s.substring(i, j + 1));dfs(s, j + 1);ans.remove(ans.size() - 1);}}}
}
N皇后_61
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
示例 1:
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
示例 2:
输入:n = 1
输出:[["Q"]]
class Solution {public List<List<String>> solveNQueens(int n) {List<List<String>> solutions = new ArrayList<List<String>>();int[] queens = new int[n];Arrays.fill(queens, -1);Set<Integer> columns = new HashSet<Integer>();Set<Integer> diagonals1 = new HashSet<Integer>();Set<Integer> diagonals2 = new HashSet<Integer>();backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2);return solutions;}public void backtrack(List<List<String>> solutions, int[] queens, int n, int row, Set<Integer> columns, Set<Integer> diagonals1, Set<Integer> diagonals2) {if (row == n) {List<String> board = generateBoard(queens, n);solutions.add(board);} else {for (int i = 0; i < n; i++) {if (columns.contains(i)) {continue;}int diagonal1 = row - i;if (diagonals1.contains(diagonal1)) {continue;}int diagonal2 = row + i;if (diagonals2.contains(diagonal2)) {continue;}queens[row] = i;columns.add(i);diagonals1.add(diagonal1);diagonals2.add(diagonal2);backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2);queens[row] = -1;columns.remove(i);diagonals1.remove(diagonal1);diagonals2.remove(diagonal2);}}}public List<String> generateBoard(int[] queens, int n) {List<String> board = new ArrayList<String>();for (int i = 0; i < n; i++) {char[] row = new char[n];Arrays.fill(row, '.');row[queens[i]] = 'Q';board.add(new String(row));}return board;}
}
二分查找
- int[][] matrix = {{1, 3, 5, 7}, {10, 11, 16, 20}, {23, 30, 34, 60}}
- int m = matrix.length; 行数
- int n = matrix[0].length; 列数
搜索插入位置_35
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
class Solution {public int searchInsert(int[] nums, int target) {if (nums == null || nums.length == 0)return -1;int l = 0, r = nums.length - 1;while (l <= r) { int mid = l + (r - l) / 2;if(target > nums[mid]) l = mid + 1;else r = mid - 1;}// 没找到就插入 程序退出条件应该是l==n了,所以返回l和nreturn l;}
}
搜索二维矩阵_74
给你一个满足下述两条属性的 m x n
整数矩阵:
- 每行中的整数从左到右按非严格递增顺序排列。
- 每行的第一个整数大于前一行的最后一个整数。
给你一个整数 target
,如果 target
在矩阵中,返回 true
;否则,返回 false
。
示例 1:
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出:true
示例 2:
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
输出:false
class Solution {public boolean searchMatrix(int[][] matrix, int target) {// m为行数、n为列数int m = matrix.length,n = matrix[0].length;// 把二维数组映射到一维int l = 0,r = m * n - 1;while(l <= r){int mid = l + (r - l) / 2;if(target == get(matrix,mid)) return true;else if(target < get(matrix,mid)) r = mid - 1;else if(target > get(matrix,mid)) l = mid + 1;}return false;}// 通过一维坐标访问二维数组的坐标的元素int get(int[][] matrix,int index){int n = matrix[0].length;int i = index / n, j = index % n;return matrix[i][j];}
}
在排序数组中查找元素的第一个和最后一个位置_34
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
- 分为两个二分查找对应前、后下标,但是需要注意first前面的值可能等于mid 或 second后面的值等于mid
class Solution {public int[] searchRange(int[] nums, int target) {if (nums == null || nums.length == 0)return new int[] { -1, -1 };// first => 0 -> len - 1 ,没找到返回-1int first = findFirst(nums, target, 0, nums.length - 1);// second => first -> len - 1,没找到返回-1int second = findSecond(nums, target, Math.max(first, 0), nums.length - 1);return new int[] { first, second };}int findFirst(int[] nums, int target, int l, int r) {while (l <= r) {int mid = l + (r - l) / 2;if (target == nums[mid]) {// 说明mid之前就出现了if (mid - 1 >= 0 && nums[mid - 1] == nums[mid]) {r = mid - 1;} else {return mid;}} else if (target < nums[mid]) {r = mid - 1;} else {l = mid + 1;}}return -1;}int findSecond(int[] nums, int target, int l, int r) {while (l <= r) {int mid = l + (r - l) / 2;if (target == nums[mid]) {// 说明mid之前就出现了if (mid + 1 <= r && nums[mid + 1] == nums[mid]) {l = mid + 1;} else {return mid;}} else if (target < nums[mid]) {r = mid - 1;} else {l = mid + 1;}}return -1;}
}
搜索旋转排序数组_33
整数数组 nums
按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums
在预先未知的某个下标 k
(0 <= k < nums.length
)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
(下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7]
在下标 3
处经旋转后可能变为 [4,5,6,7,0,1,2]
。
给你 旋转后 的数组 nums
和一个整数 target
,如果 nums
中存在这个目标值 target
,则返回它的下标,否则返回 -1
。
你必须设计一个时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:
输入:nums = [1], target = 0
输出:-1
- 数组经过旋转会保证两块有序,分别进行处理
class Solution {public int search(int[] nums, int target) {if (nums == null || nums.length == 0)return -1;int l = 0, r = nums.length - 1;while (l <= r) {int mid = l + (r - l) / 2;if (target == nums[mid])return mid;// 如果左边有序else if (nums[mid] >= nums[l]) { // 注意if (target >= nums[l] && target < nums[mid]) {r = mid - 1;} else {l = mid + 1;}}// 如果右边有序else {if (target <= nums[r] && target > nums[mid]) {l = mid + 1;} else {r = mid - 1;}}}return -1;}
}
寻找旋转排序数组中的最小值_153
已知一个长度为 n
的数组,预先按照升序排列,经由 1
到 n
次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7]
在变化后可能得到:
- 若旋转
4
次,则可以得到[4,5,6,7,0,1,2]
- 若旋转
7
次,则可以得到[0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]]
旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]
。
给你一个元素值 互不相同 的数组 nums
,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须设计一个时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
示例 2:
输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 3 次得到输入数组。
示例 3:
输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。
- 由于数组不包含重复元素,并且只要当前的区间长度不为 1,mid就不会与r重合
class Solution {public int findMin(int[] nums) {if (nums == null || nums.length == 0)return -1;int l = 0, r = nums.length - 1;while (l < r) { // 注意int mid = l + (r - l) / 2;if (nums[mid] < nums[r]) {r = mid;} else {l = mid + 1;}}return nums[l];}
}
寻找两个正序数组的中位数_4
给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n))
。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {// 用 len 表示合并后数组的长度,// 如果是奇数,我们需要知道第 (len+1)/2 个数就可以了,如果遍历的话需要遍历 int(len/2 ) + 1 次。// 如果是偶数,我们需要知道第 len/2和 len/2+1 个数// 也是需要遍历 len/2+1 次。所以遍历的话,奇数和偶数都是 len/2+1 次// 返回中位数的话,奇数需要最后一次遍历的结果就可以了,偶数需要最后一次和上一次遍历的结果// 所以我们用两个变量 left 和 right,right 保存当前循环的结果,在每次循环前将 right 的值赋给 left// 这样在最后一次循环的时候,left 将得到 right 的值,也就是上一次循环的结果,接下来 right 更新为最后一次的结果int m = nums1.length, n = nums2.length;int len = m + n;int left = -1, right = -1;// 用 num1_start 和 num2_start 分别表示当前指向 A 数组和 B 数组的位置int num1_start = 0, num2_start = 0;for (int i = 0; i < (len / 2) + 1; i++) {left = right;// ( 如果left < right 说明在left的数组里 或者越界了)if (num1_start < m && (num2_start >= n || nums1[num1_start] < nums2[num2_start])) {right = nums1[num1_start++];} else {// 如果left > right 说明在right的数组里right = nums2[num2_start++];}}// 如果是偶数if ((len & 1) == 0) {return (left + right) / 2.0;} else {return right;}}
}
栈
- Stack
- push 入栈
- pop 出栈并返回元素
有效的括号_20
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
- 遍历每个字符,如果是左括号就入栈,否则就进行左出栈判断当前右括号与它是否匹配,不匹配就false,最终栈为空说明满足
class Solution {public boolean isValid(String s) {if (s == null || s.length() == 0)return false;Stack<Character> stack = new Stack<>();for (char c : s.toCharArray()) {if (c == '(' || c == '{' || c == '[') {stack.push(c);} else {if (!stack.isEmpty()) {Character top = stack.pop();if (!( (c == ')' && top == '(') ||(c == ']' && top == '[') ||(c == '}' && top == '{'))) {return false;}} else {return false;}}}return stack.isEmpty();}}
最小栈_155
设计一个支持 push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack
类:
MinStack()
初始化堆栈对象。void push(int val)
将元素val推入堆栈。void pop()
删除堆栈顶部的元素。int top()
获取堆栈顶部的元素。int getMin()
获取堆栈中的最小元素。
示例 1:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]输出:
[null,null,null,null,-3,null,0,-2]解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
- 通过两个Stack完成
- 一个Stack正常存
- 一个minStack先判断是否为空,为空就先存,不为空时取当前值和栈顶元素小的那个存
class MinStack {Stack<Integer> stack;Stack<Integer> minStack;public MinStack() {stack = new Stack<>();minStack = new Stack<>();}public void push(int val) {stack.push(val);// 如果为空就存当前值if (minStack.isEmpty()) {minStack.push(val);} else {minStack.push(Math.min(minStack.peek(), val));}}public void pop() {if (stack.isEmpty())return;stack.pop();minStack.pop();}public int top() {return stack.peek();}public int getMin() {return minStack.peek();}
}/*** Your MinStack object will be instantiated and called as such:* MinStack obj = new MinStack();* obj.push(val);* obj.pop();* int param_3 = obj.top();* int param_4 = obj.getMin();*/
字符串解码_394
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string]
,表示其中方括号内部的 encoded_string
正好重复 k
次。注意 k
保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k
,例如不会出现像 3a
或 2[4]
的输入。
示例 1:
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
示例 2:
输入:s = "3[a2[c]]"
输出:"accaccacc"
示例 3:
输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"
示例 4:
输入:s = "abc3[cd]xyz"
输出:"abccdcdcdxyz"
- 一个Stack存倍数 numStack
- 一个Stack存上次结果 resStack
- 每次出栈用来追加后面的字符,需要注意的是数字可能不是个位数,当为左括号时,先预留一串空的,当为右括号时就要开始乘法得到一串字符串segment,resStack栈顶出栈 + segment 即为当前res
class Solution {public String decodeString(String s) {if (s == null || s.length() == 0)return "";Stack<Integer> numStack = new Stack<>();Stack<String> resStack = new Stack<>();int num = 0;StringBuilder res = new StringBuilder();for (char c : s.toCharArray()) {if (c >= '0' && c <= '9') {num = num * 10 + c - '0';} else if (c == '[') {numStack.push(num);resStack.push(res.toString());num = 0;res = new StringBuilder();} else if (c == ']') {StringBuilder segment = new StringBuilder();Integer curNum = numStack.pop();for (int i = 0; i < curNum; i++) {segment.append(res.toString());}// 上次的出栈 + 本段字符res = new StringBuilder(resStack.pop() + segment);} else {// 将括号中的字符拼出来res.append(c);}}return res.toString();}
}
每日温度_739
给定一个整数数组 temperatures
,表示每天的温度,返回一个数组 answer
,其中 answer[i]
是指对于第 i
天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0
来代替。
示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
示例 2:
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
示例 3:
输入: temperatures = [30,60,90]
输出: [1,1,0]
- 用Stack(存比当前值小的下标)
- 遍历数组,判断当前值是否比Stack栈顶元素对应的值小,如果小就Stack栈顶元素出栈,存当前下标
- 最终结果res通过 当前值下标 - 栈顶元素的值(下标)
class Solution {public int[] dailyTemperatures(int[] temperatures) {if (temperatures == null || temperatures.length == 0)return new int[] {};Stack<Integer> stack = new Stack<>();int[] res = new int[temperatures.length];for (int i = 0; i < temperatures.length; i++) {// 保证出栈非空,有大的就出栈一个,没有默认都为0while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {Integer curTopIdx = minStack.pop();res[curTopIdx] = i - curTopIdx;}stack.push(i);}return res;}
}
柱状图中最大的矩形_84
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:
输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10
示例 2:
输入: heights = [2,4]
输出: 4
- 用Stack(存入比当前值大的下标),计算面积 = width(右边下标 - 左边下标 - 1) * 较低高度(下标对应的值)
class Solution {public int largestRectangleArea(int[] heights) {if (heights == null || heights.length == 0)return 0;Stack<Integer> stack = new Stack<>();int max = 0;// 将元素放入新数组居中int[] newHeights = new int[heights.length + 2];newHeights[0] = 0;newHeights[newHeights.length - 1] = 0;for (int i = 1; i < heights.length + 1; i++) {newHeights[i] = heights[i - 1];}for (int i = 0; i < newHeights.length; i++) {// 栈顶元素对应的值 > 当前元素的值while (!stack.isEmpty() && newHeights[i] < newHeights[stack.peek()]) {// 栈顶元素,之前最小的高度int height = newHeights[stack.pop()];int left = stack.peek();int width = i - left - 1;int area = width * height;max = Math.max(max, area);}stack.push(i);}return max;}
}
堆
- PriorityQueue
- offer 新增
- poll 删除头元素并返回
- peek 获取头
数组中的第K个最大元素_215
给定整数数组 nums
和整数 k
,请返回数组中第 k
个最大的元素。
请注意,你需要找的是数组排序后的第 k
个最大的元素,而不是第 k
个不同的元素。
你必须设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 1:
输入: [3,2,1,5,6,4], k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4
- 采用最小堆,元素入堆,剔除前K大的元素
class Solution {public int findKthLargest(int[] nums, int k) {if(nums == null || nums.length == 0) return 0;// 使用最小堆PriorityQueue<Integer> minHeap = new PriorityQueue<>();for (int num : nums) {minHeap.offer(num);if (minHeap.size() > k) {minHeap.poll();}}return minHeap.peek();}
}
前K个高频元素_347
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
- 采用Map存放出现频率,再使用最小堆,遍历频率Map的key,剔除前K大的元素
class Solution {public int[] topKFrequent(int[] nums, int k) {if (nums == null || nums.length == 0)return new int[] {};Map<Integer, Integer> freqMap = new HashMap<>();for (int num : nums) {freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);}// 使用最小堆PriorityQueue<Integer> minHeap = new PriorityQueue<>((a, b) -> freqMap.get(a) - freqMap.get(b));// 遍历频率Mapfor (Integer num : freqMap.keySet()) {minHeap.offer(num);if (minHeap.size() > k) {minHeap.poll();}}int[] res = new int[k];for (int i = 0; i < k; i++) {res[i] = minHeap.poll();}return res;}
}
数据流的中位数_295
中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
- 例如
arr = [2,3,4]
的中位数是3
。 - 例如
arr = [2,3]
的中位数是(2 + 3) / 2 = 2.5
。
实现 MedianFinder 类:
MedianFinder()
初始化MedianFinder
对象。void addNum(int num)
将数据流中的整数num
添加到数据结构中。double findMedian()
返回到目前为止所有元素的中位数。与实际答案相差10-5
以内的答案将被接受。
示例 1:
输入
["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"]
[[], [1], [2], [], [3], []]
输出
[null, null, null, 1.5, null, 2.0]解释
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1); // arr = [1]
medianFinder.addNum(2); // arr = [1, 2]
medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3); // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0
- 借助两个堆,长度不相等
- 最小堆放元素,然后放最小堆的poll
- 最大堆放元素,然后放最大堆的poll
class MedianFinder {PriorityQueue<Integer> small;PriorityQueue<Integer> large;public MedianFinder() {// 最小堆 保存较大的一半small = new PriorityQueue<>();// 最大堆 保存较小的一半large = new PriorityQueue<>((a, b) -> b - a);}public void addNum(int num) {if (small.size() != large.size()) {small.offer(num);// 存小的large.offer(small.poll());} else {large.offer(num);// 存大的small.offer(large.poll());}}public double findMedian() {if (small.size() > large.size()) {return small.peek();} else if (small.size() < large.size()) {return large.peek();} else {return (small.peek() + large.peek()) / 2.0;}}
}/*** Your MedianFinder object will be instantiated and called as such:* MedianFinder obj = new MedianFinder();* obj.addNum(num);* double param_2 = obj.findMedian();*/
贪心算法
买卖股票的最佳时机_121
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
class Solution {public int maxProfit(int[] prices) {if (prices == null || prices.length == 0) return 0;// 最大值暂定为0,最小值默认用首元素int max = 0, min = prices[0];for (int i = 1; i < prices.length; i++) {min = Math.min(min, prices[i]);max = Math.max(max, prices[i] - min);}return max;}
}
跳跃游戏_55
给你一个非负整数数组 nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true
;否则,返回 false
。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
class Solution {public boolean canJump(int[] nums) {if(nums == null || nums.length == 0) return false;int len = nums.length;int max_position = 0;for(int i = 0; i < len - 1; i++){max_position = Math.max(max_position,nums[i] + i);if(max_position <= i){return false;}}return max_position >= len - 1;}
}
跳跃游戏II_45
给定一个长度为 n
的 0 索引整数数组 nums
。初始位置为 nums[0]
。
每个元素 nums[i]
表示从索引 i
向前跳转的最大长度。换句话说,如果你在 nums[i]
处,你可以跳转到任意 nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]
。
示例 1:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
示例 2:
输入: nums = [2,3,0,1,4]
输出: 2
class Solution {public int jump(int[] nums) {if(nums == null || nums.length == 0) return 0;int len = nums.length;int end = 0;int max_position = 0;int steps = 0;for(int i = 0; i < len - 1; i++){max_position = Math.max(max_position,nums[i] + i);if(end == i){end = max_position;steps++; }}return steps;}
}
划分字母区间_763
给你一个字符串 s
。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s
。
返回一个表示每个字符串片段的长度的列表。
示例 1:
输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。
示例 2:
输入:s = "eccbbbbdec"
输出:[10]
class Solution {public List<Integer> partitionLabels(String s) {if(s == null || s.length() == 0) return new ArrayList<>();int[] last = new int[26];int len = s.length();// 记录每个字母出现的最后一个下表for(int i = 0; i < len; i++){last[s.charAt(i) - 'a'] = i;}int start = 0,end = 0;List<Integer> res = new ArrayList<>();for(int i = 0; i < len; i++){end = Math.max(end, last[s.charAt(i) - 'a']);if(i == end){res.add(end - start + 1);start = end + 1; }}return res;}
}
动态规划
爬楼梯_70
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
class Solution {public int climbStairs(int n) {int[] dp = new int[n + 1];dp[0] = 1;dp[1] = 1;for (int i = 2; i <= n; i++) {dp[i] = dp[i - 1] + dp[i - 2];}return dp[n];}
}
杨辉三角_118
给定一个非负整数 *numRows
,*生成「杨辉三角」的前 numRows
行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
示例 1:
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
示例 2:
输入: numRows = 1
输出: [[1]]
class Solution {public List<List<Integer>> generate(int numRows) {List<List<Integer>> res = new ArrayList<>();if(numRows < 1){return res;}// 先把第一层装进去作为base caseList<Integer> firstRow = new ArrayList<>();firstRow.add(1);res.add(firstRow);// 从一开始一层一层生成,载入resfor(int i = 2; i <= numRows; i++){// 获取前一层rowList<Integer> prevRow = res.get(res.size()-1);res.add(genNextRow(prevRow));}return res;}// 输入上一层的元素,生成并返回下一层的元素List<Integer> genNextRow(List<Integer> prevRow){List<Integer> curRow = new ArrayList<>();// 首个元素为1curRow.add(1);// 遍历上层for(int i = 0; i < prevRow.size() - 1; i++){curRow.add(prevRow.get(i) + prevRow.get(i+1));}// 最后一个元素curRow.add(1);return curRow;}}
打家劫舍_198
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。偷窃到的最高金额 = 2 + 9 + 1 = 12 。
class Solution {public int rob(int[] nums) {if(nums == null || nums.length == 0) return 0;int n = nums.length;// 奇数int odd_sum = 0;// 偶数int even_sum = 0;for(int i = 0; i < n; i++){if(i % 2 == 0) {even_sum += nums[i];even_sum = Math.max(even_sum,odd_sum);}else{odd_sum += nums[i];odd_sum = Math.max(even_sum,odd_sum);}}return Math.max(odd_sum,even_sum);}
}
完全平方数_279
给你一个整数 n
,返回 和为 n
的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1
、4
、9
和 16
都是完全平方数,而 3
和 11
不是。
示例 1:
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
示例 2:
输入:n = 13
输出:2
解释:13 = 4 + 9
class Solution {public int numSquares(int n) {int[] dp = new int[n+1];Arrays.fill(dp,Integer.MAX_VALUE);dp[0] = 0;for(int i = 1; i <= n; i++){// 最坏的情况dp[i] = i;int j = 1;while(i - j * j >= 0){dp[i] = Math.min(dp[i],dp[i-j*j] + 1);j++;}}return dp[n];}
}
零钱兑换_322
给你一个整数数组 coins
,表示不同面额的硬币;以及一个整数 amount
,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1
。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
class Solution {public int coinChange(int[] coins, int amount) {if(coins == null || coins.length == 0) return -1;// 表示的凑成总金额为n所需的最少的硬币个数int[] dp = new int[amount + 1];Arrays.fill(dp,Integer.MAX_VALUE);dp[0] = 0;for(int cur_amount = 1; cur_amount <= amount; cur_amount++){for(int coin : coins){int left_amount = cur_amount - coin;if(left_amount < 0 || dp[left_amount] == Integer.MAX_VALUE) continue;dp[cur_amount] = Math.min(dp[left_amount] + 1, dp[cur_amount]);}}return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];}
}
单词拆分_139
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s
则返回 true
。
**注意:**不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。注意,你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
class Solution {public boolean wordBreak(String s, List<String> wordDict) {Set<String> word_set = new HashSet<>(wordDict);int n = s.length();boolean[] dp = new boolean[n+1];// 空串dp[0] = true;for(int i = 1; i <= n; i++){for(int j = 0; j < i; j++){if(dp[j] && word_set.contains(s.substring(j,i))){dp[i] = true;break;}}}return dp[n];}
}
最长递增子序列_300
给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
class Solution {public int lengthOfLIS(int[] nums) {if(nums == null || nums.length == 0) return 0;int[] dp = new int[nums.length];int max = 1;dp[0] = 1;for(int i = 1; i < nums.length; i++){dp[i] = 1;for(int j = 0; j < i; j++){if(nums[j] < nums[i]){dp[i] = Math.max(dp[i],dp[j] + 1);}}max = Math.max(dp[i], max);}return max;}
}
乘积最大子数组_152
给你一个整数数组 nums
,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
子数组 是数组的连续子序列。
示例 1:
输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
class Solution {public int maxProduct(int[] nums) { if(nums == null || nums.length == 0) return 0;int n = nums.length;int[] min_dp = new int[n];int[] max_dp = new int[n];max_dp[0] = nums[0];for(int i = 1; i < n; i++){min_dp[i] = Math.min(nums[i],Math.min(max_dp[i-1]*nums[i],min_dp[i-1]*nums[i]));max_dp[i] = Math.max(nums[i],Math.max(max_dp[i-1]*nums[i],min_dp[i-1]*nums[i]));}int res = Integer.MIN_VALUE;for(int num : max_dp){res = Math.max(res,num);}return res;}
}
分割等和子集_416
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
class Solution {public boolean canPartition(int[] nums) {// 先判断是否是偶数int sum = 0;for(int num : nums){sum+=num;}if(sum % 2 != 0) return false;sum /= 2;int n = nums.length;boolean[][] dp = new boolean[n+1][sum+1];for(int i = 0; i <= n; i++){dp[i][0] = true;}for(int i = 1; i <= n; i ++){for(int j = 1; j <= sum; j++){// 放不下if(j < nums[i-1]){dp[i][j] = dp[i-1][j];}else{dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];}}}return dp[n][sum];}
}
最长有效括号_32
给你一个只包含 '('
和 ')'
的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"
示例 2:
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
示例 3:
输入:s = ""
输出:0
class Solution {public int longestValidParentheses(String s) {if(s == null || s.length() == 0) return 0;Stack<Integer> stack = new Stack<>();stack.push(-1);int max = 0;for(int i = 0;i < s.length(); i++){if(s.charAt(i) == '('){stack.push(i);} else {// 说明碰到了)stack.pop();// 说明这个)没有匹配到if(stack.isEmpty()){stack.push(i);}else{max = Math.max(max, i - stack.peek());}}}return max;}
}
多维动态规划
不同路径_62
一个机器人位于一个 m x n
网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7
输出:28
示例 2:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下
示例 3:
输入:m = 7, n = 3
输出:28
示例 4:
输入:m = 3, n = 3
输出:6
class Solution {// M X N// 动态规划// dp[i][j] 是到达 i,j最多路径// dp[i][j] = dp[i-1][j] + dp[i][j-1]// 对于第一行dp[0][j] 或者 第一列dp[i][0] 由于都是在边界,所以只能为1public int uniquePaths(int m, int n) {int[][] dp = new int[m][n];for(int i = 0; i < m; i++){dp[i][0] = 1;}for(int i = 0; i < n; i ++){dp[0][i] = 1;}for(int i = 1; i < m; i++){for(int j = 1; j < n; j++){dp[i][j] = dp[i-1][j]+ dp[i][j-1];}}return dp[m-1][n-1];}
}
最小路径和_64
给定一个包含非负整数的 *m* x *n*
网格 grid
,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
**说明:**每次只能向下或者向右移动一步。
示例 1:
输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
示例 2:
输入:grid = [[1,2,3],[4,5,6]]
输出:12
class Solution {// M X N// 从左上角(0,0)->(i,j)的最小路径和// 走到当前单元格(i,j)的最小路径和 = 从左方单元格(i-1,j)与从上方单元格(i,j-1)走来的两个最小路径和较小的 + 当前单元格值grid[i][j]// 当左边和上边都不是矩阵边界时,也就是当i!=0 && j!=0 -> dp[i][j] = min(dp[i-1][j],dp[i][j-1] + grid[i][j])// 当只有左边是矩阵边界时,也就是当i=0,j!=0时, dp[i][j] = dp[i][j-1] + grid[i][j]// 当只有上边是矩阵边界时,也就是当i!=0,j=0时, dp[i][j] = dp[i-1][j] + grid[i][j]// 当左边和上边都是边界时,也就是当i=0,j=0时,dp[i][j] = grid[i][j]// 其实我们完全不需要建立 dp矩阵浪费额外空间,直接遍历 grid[i][j] 修改即可。// 这是因为:grid[i][j] = min(grid[i - 1][j], grid[i][j - 1])) + grid[i][j]public int minPathSum(int[][] grid) {// xint m = grid.length;// yint n = grid[0].length;int[][] dp = new int[m][n];dp[0][0] = grid[0][0];// 初始化X轴for (int i = 1; i < m; i++) {dp[i][0] = dp[i - 1][0] + grid[i][0];}// 初始化y轴for (int i = 1; i < n; i++) {dp[0][i] = dp[0][i - 1] + grid[0][i];}for (int i = 1; i < m; i++) {for (int j = 1; j < n; j++) {dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];}}return dp[m - 1][n - 1];}
}
最长回文子串_5
给你一个字符串 s
,找到 s
中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
class Solution {public String longestPalindrome(String s) {String res = "";if (s == null || s.length() == 0) {return res;}// s = "babad"// 中心扩散法for (int i = 0; i < s.length(); i++) {// 以 s[i] 为中心的最长回文子串 也就是 aba这种 b一个字符为中心的情况String s1 = sub(s, i, i);// 以 s[i] 和 s[i+1] 为中心的最长回文子串 也就是cabbak 这种以 bb为中心的情况String s2 = sub(s, i, i + 1);// res = longest(res, s1, s2)res = res.length() > s1.length() ? res : s1;res = res.length() > s2.length() ? res : s2;}return res;}private String sub(String s, int l, int r) {// 防止索引越界while (l >= 0 &&r < s.length() &&s.charAt(l) == s.charAt(r)) {// 向两边展开l--;r++;}return s.substring(l + 1, r);}
}
最长公共子序列_1143
给定两个字符串 text1
和 text2
,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0
。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,
"ace"
是"abcde"
的子序列,但"aec"
不是"abcde"
的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。
示例 2:
输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。
示例 3:
输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0 。
class Solution {public int longestCommonSubsequence(String text1, String text2) {int m = text1.length(), n = text2.length();int[][] dp = new int[m+1][n+1];for(int i = 1; i <= m; i++){for(int j = 1; j <= n; j++){// 两个子字符串的最后一位相等if(text1.charAt(i-1) == text2.charAt(j - 1)){dp[i][j] = dp[i-1][j-1] + 1;}else {dp[i][j] = Math.max(dp[i][j-1],dp[i-1][j]);}}}return dp[m][n];}
}
编辑距离_72
给你两个单词 word1
和 word2
, 请返回将 word1
转换成 word2
所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
示例 1:
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
示例 2:
输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')
class Solution {public int minDistance(String word1, String word2) {int n1 = word1.length();int n2 = word2.length();int[][] dp = new int[n1 + 1][n2 + 1];for(int i = 1; i <= n1; i++){dp[i][0] = dp[i-1][0] + 1;}for(int j = 1; j <= n2; j++){dp[0][j] = dp[0][j-1] + 1;}for(int i = 1; i <= n1; i++){for(int j = 1; j <= n2; 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],Math.min(dp[i][j-1],dp[i-1][j-1])) + 1;}}}return dp[n1][n2];}
}
技巧
只出现一次的数字_136
给你一个 非空 整数数组 nums
,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
示例 1 :
输入:nums = [2,2,1]
输出:1
示例 2 :
输入:nums = [4,1,2,1,2]
输出:4
示例 3 :
输入:nums = [1]
输出:1
class Solution {// 一个数和它本身做异或运算结果为 0,// 即 a ^ a = 0;一个数和 0 做异或运算的结果为它本身,即 a ^ 0 = a。public int singleNumber(int[] nums) {int ret = 0;for(int num : nums){ret ^= num;}return ret;}
}
多数元素_169
给定一个大小为 n
的数组 nums
,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋
的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入:nums = [3,2,3]
输出:3
示例 2:
输入:nums = [2,2,1,1,1,2,2]
输出:2
class Solution {// 选出投票数最多的public int majorityElement(int[] nums) {if (nums == null || nums.length == 0) return 0;int cnt = 0;int target = 0;// 遍历循环找出投票次数最多的for (int num : nums) {if (cnt == 0) {target = num;cnt++;} else {cnt += target == num ? 1 : -1;}}return target;}
}
颜色分类_75
给定一个包含红色、白色和蓝色、共 n
个元素的数组 nums
,**原地**对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0
、 1
和 2
分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]
class Solution {public void sortColors(int[] nums) {if(nums == null || nums.length == 0) return;int n = nums.length;int p = 0;// 第一次遍历的时候,将数组的0全部放到头部for(int i = 0; i < n; i++){if(nums[i] == 0){swap(nums,i,p);p++;}}// 第二次遍历将1放到之后for(int i = p;i < n; i++){if(nums[i] == 1){swap(nums,i,p);p++;}}}void swap(int[] nums, int i,int j) {int tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}}
下一个排列_31
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
- 例如,
arr = [1,2,3]
,以下这些都可以视作arr
的排列:[1,2,3]
、[1,3,2]
、[3,1,2]
、[2,3,1]
。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
- 例如,
arr = [1,2,3]
的下一个排列是[1,3,2]
。 - 类似地,
arr = [2,3,1]
的下一个排列是[3,1,2]
。 - 而
arr = [3,2,1]
的下一个排列是[1,2,3]
,因为[3,2,1]
不存在一个字典序更大的排列。
给你一个整数数组 nums
,找出 nums
的下一个排列。
必须** 原地 **修改,只允许使用额外常数空间。
示例 1:
输入:nums = [1,2,3]
输出:[1,3,2]
示例 2:
输入:nums = [3,2,1]
输出:[1,2,3]
示例 3:
输入:nums = [1,1,5]
输出:[1,5,1]
class Solution {public void nextPermutation(int[] nums) {if(nums == null || nums.length == 0) return;// 要保证至少有两个数字int i = nums.length - 2;// 如果左 >= 右,直到遇到 左 < 右的,此时 i+1,n肯定为降序while(i >= 0 && nums[i] >= nums[i+1]) i--;if(i >= 0){int j = nums.length - 1;// 在 i+1,n找到最大数 j满足 a[i] >= a[j] 这样较大数为a[i]while(j >= 0 && nums[i] >= nums[j]){j--;}// 然后此时得到a[j..n]中最小的值a[j],将两个值交换位置swap(nums,i,j);}// 上面交换完之后,此时 [i+1,n)必是降序,// 剩下的就是全部进行翻转 我们可以直接使用双指针反转区间 [i+1,n) 使其变为升序,而无需对该区间进行排序reverse(nums,i+1,nums.length - 1);}void reverse(int[] nums,int start,int end) {while(start < end){swap(nums,start,end);start++;end--;}}void swap(int[] nums, int i, int j) {int tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}}
寻找重复数_287
给定一个包含 n + 1
个整数的数组 nums
,其数字都在 [1, n]
范围内(包括 1
和 n
),可知至少存在一个重复的整数。
假设 nums
只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums
且只用常量级 O(1)
的额外空间。
示例 1:
输入:nums = [1,3,4,2,2]
输出:2
示例 2:
输入:nums = [3,1,3,4,2]
输出:3
提示:
1 <= n <= 105
nums.length == n + 1
1 <= nums[i] <= n
nums
中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次
class Solution {public int findDuplicate(int[] nums) {if(nums == null || nums.length == 0) return 0;while(true){int index = nums[0];if(nums[index] == nums[0]) return nums[0];else {int tmp = nums[0];nums[0] = nums[index];nums[index] = tmp;}}}
}