Hot100刷题记录
规划每日总结
10.14
70. 爬楼梯 - 力扣(LeetCode)
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
简单的一维线性dp
class Solution {
public int climbStairs(int n) {
int[] dp = new int[n + 1];
dp[1] = 1;
dp[0] = 1;
for(int i = 2; i <= n;i++){
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
198. 打家劫舍 - 力扣(LeetCode)
简单的一维线性dp
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
明确dp:dp[i]:前0...i套房能获取的最大金额
状态转换方程:偷当前房子能获得的最大为dp[i-2] + nums[i],不偷当前房子获取最大为dp[i - 1];
Base case: dp[0] = nums[0],dp[1] = Math.max(nums[0],nums[1])
class Solution {
public int rob(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
dp[0] = nums[0];
if(n == 1){
return dp[0];
}
dp[1] = Math.max(nums[0],nums[1]);
for(int i = 2;i < n;i++){
dp[i] = Math.max(dp[i -2] + nums[i],dp[i - 1]);
}
return dp[n - 1];
}
}
213. 打家劫舍 II - 力扣(LeetCode)
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
一维环形dp
确定dp及下标含义:从0-i间房偷得的最大价值
状态转换方程dp[i] = Math.max(dp[i - 2] + nums[i],dp[i - 1]);
Base case dp[0] = nums[0];
分三种情况
三种情况,第一种价值最小
class Solution {
public int rob(int[] nums) {
int n = nums.length;
if(n == 1){
return nums[0];
}
return Math.max(robs(nums,0,n - 2),robs(nums,1,n - 1));
}
int robs(int[] nums,int left,int right){
int n = right - left + 1;
int[] dp = new int[n];
dp[0] = nums[left];
if(n == 1){
return dp[0];
}
dp[1] = Math.max(nums[left],nums[left+1]);
for(int i = 2; i < n;i++){
dp[i] = Math.max(dp[i - 2] + nums[i + left],dp[i - 1]);
}
return dp[n - 1];
}
}
10.15
300. 最长递增子序列 - 力扣(LeetCode)
给你一个整数数组 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
经典动态规划
确认dp[i]及下标含义:以nums[i]结尾的最大递增子序列长度
状态转换方程 dp[i] = Math.max(d[j] + 1,dp[i]);
Base case Arrays.fill(dp,1);
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
//以nums[i]结尾的最大递增子序列长度
int[][] dp = new int[n][n];
Arrays.fill(dp,1);
int max = 1;
for(int i = 1; i < n;i++){
for(int j = 0; j < i;j++){
if(nums[i] > nums[j]){
dp[i] = Math.max(dp[j] + 1,dp[i]);
}
max = Math.max(dp[i],max);
}
}
return max;
}
}
152. 乘积最大子数组 - 力扣(LeetCode)
给你一个整数数组 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) {
//以nums[i]结尾的最大乘积子数组
int n = nums.length;
int[] maxF = new int[n];
int[] minF = new int[n];
maxF[0] = minF[0] = nums[0];
int max = nums[0];
for(int i = 1; i < n;i++){
maxF[i] = Math.max(Math.max(maxF[i-1] * nums[i],minF[i - 1] * nums[i]),nums[i]);
minF[i] = Math.min(Math.min(maxF[i-1] * nums[i],minF[i - 1] * nums[i]),nums[i]);
max = Math.max(maxF[i],max);
}
return max;
}
}
322. 零钱兑换 - 力扣(LeetCode)
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11输出:3 解释:11 = 5 + 5 + 1示例 2:
输入:coins = [2], amount = 3输出:-1
完全背包问题,分为两种:
外层循环是物品 → 不计顺序 → 组合型
外层循环是容量 → 计顺序 → 排列型
确定dp[j]含义:凑成j所需的最少的硬币数量dp[j]
状态转换方程 dp[j] = Math.min(dp[j-coins[i]]+ 1,dp[j]);
Arrays.fill(dp,Integer.MAX_VALUE);
class Solution {
public int coinChange(int[] coins, int amount) {
int n = coins.length;
int[] dp = new int[amount + 1];
int max = Integer.MAX_VALUE;
Arrays.fill(dp,max);
dp[0] = 0;
for(int i = 0; i < n;i++){
for(int j = coins[i]; j <= amount;j++){
if(dp[j-coins[i]] != max){
dp[j] = Math.min(dp[j],dp[j-coins[i]] + 1);
}
}
}
return dp[amount] == max ? -1 : dp[amount];
}
}
10.16/11.17
1. 两数之和 - 力扣(LeetCode)
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0; i < nums.length;i++){
int temp = target - nums[i];
if(map.containsKey(nums[i])){
return new int[]{map.get(nums[i]),i};
}
map.put(temp,i);
}
return new int[]{-1,-1};
}
}
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
解释:
在 strs 中没有字符串可以通过重新排列来形成 "bat"。
字符串 "nat" 和 "tan" 是字母异位词,因为它们可以重新排列以形成彼此。
字符串 "ate" ,"eat" 和 "tea" 是字母异位词,因为它们可以重新排列以形成彼此
可以使用数组,核心思路是字符的ASCII大小的排序,排序结果相同就是异位词
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String,List<String>> map = new HashMap<>();
for(String s : strs){
char[] chs = s.toCharArray();
Arrays.sort(chs);
String key = new String(chs);
map.putIfAbsent(key,new ArrayList<>());
map.get(key).add(s);
}
return new ArrayList<>(map.values());
}
}
128. 最长连续序列 - 力扣(LeetCode)
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
class Solution {
public int longestConsecutive(int[] nums) {
if(nums.length == 0){
return 0;
}
Set<Integer> set = new HashSet<Integer>();
for (int num : nums) {
set.add(num);
}
int res = 1;
for (int num : set) {
int t = 1;
int p = num;
while (set.contains(num - 1)) {
t++;
num--;
}
num = p;
while (set.contains(num + 1)) {
t++;
num++;
}
res = Math.max(t, res);
}
return res;
}
}
这个做法超时了,原因是重复计算。正确做法应该是找到序列中的值左边间断的数然后向右寻找
class Solution {
public int longestConsecutive(int[] nums) {
if(nums.length == 0){
return 0;
}
Set<Integer> set = new HashSet<Integer>();
for (int num : nums) {
set.add(num);
}
int res = 1;
for (int num : set) {
int t = 1;
int p = num;
while (set.contains(num - 1)) {
t++;
num--;
}
num = p;
while (set.contains(num + 1)) {
t++;
num++;
}
res = Math.max(t, res);
}
return res;
}
}
哈希总结:
哈希有三种,使用数组的、使用Set、使用Map的。分别对应三种不同的场景。
当题目是计数且键范围有限(0-100/26个英文字母)的就使用数组,比Map快
当题目是只需判断是否存在、去重,就使用Set
频率 + 排序、计数范围无限使用Map
10.17/11.18
⭐283. 移动零 - 力扣(LeetCode)
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]输出: [1,3,12,0,0]
class Solution {
public void moveZeroes(int[] nums) {
int l = 0, r = 1;
int n = nums.length;
while (r < n) {
if (nums[l] == 0) {
int t = nums[r];
nums[r] = nums[l];
nums[l] = t;
}
if (nums[l] != 0) {
l++;
}
r++;
}
}
}
⭐11. 盛最多水的容器 - 力扣(LeetCode)
给定一个长度为 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。
暴力会超时:
class Solution {
public int maxArea(int[] height) {
int res = 0;
int n = height.length;
int l = 0, r = 1;
for (l = 0; l < n;l++) {
for(r = l + 1;r < n;r++){
int h = Math.min(height[l], height[r]);
int len = r - l;
res = Math.max(res, h * len);
}
}
return res;
}
}
双指针
class Solution {
public int maxArea(int[] height) {
int res = 0;
int n = height.length;
int l = 0, r = n - 1;
while (l <= r) {
int h = Math.min(height[l], height[r]);
int len = r - l;
res = Math.max(res, h * len);
if(height[l] >= height[r]){
r--;
}else{
l++;
}
}
return res;
}
}
⭐15. 三数之和 - 力扣(LeetCode)
给你一个整数数组 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] 。
注意,输出的顺序和三元组的顺序并不重要。
思路:遍历nums[i],对nums[j]和nums[k]使用双指针,j = i + 1; k = n - 1; 记得跳过重复元素
import java.util.*;
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums); // 1. 先排序
int n = nums.length;
for (int i = 0; i < n - 2; i++) {
// 2. 跳过重复的第一个数
if (i > 0 && nums[i] == nums[i - 1]) continue;
int j = i + 1, k = n - 1;
while (j < k) {
int sum = nums[i] + nums[j] + nums[k];
if (sum == 0) {
res.add(Arrays.asList(nums[i], nums[j], nums[k]));
j++;
k--;
// 3. 跳过重复的第二个和第三个数
while (j < k && nums[j] == nums[j - 1]) j++;
while (j < k && nums[k] == nums[k + 1]) k--;
} else if (sum < 0) {
j++; // 总和小了 -> 增大左指针
} else {
k--; // 总和大了 -> 减小右指针
}
}
}
return res;
}
}
18. 四数之和 - 力扣(LeetCode)
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]示例 2:
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]
⚠️List.of(nums[i],nums[left],nums[right])和Arrays.asList(nums[i],nums[left],nums[right])生成的List无法改变元素
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
int n = nums.length;
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < n - 3; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
List<List<Integer>> list = threeSum(nums, i + 1, target - nums[i]);
for (List<Integer> t : list) {
List<Integer> l = new ArrayList<>(t);
l.add(nums[i]);
res.add(l);
}
}
return res;
}
List<List<Integer>> threeSum(int[] nums, int start, long target) {
int n = nums.length;
List<List<Integer>> ans = new ArrayList<>();
for (int i = start; i < n - 2; i++) {
if (i > start && nums[i] == nums[i - 1])
continue;
int left = i + 1;
int right = n - 1;
while (left < right) {
long sum = (long)nums[i] + nums[left] + nums[right];
if (sum == target) {
ans.add(List.of(nums[i],nums[left],nums[right]));
while (left < right && nums[left] == nums[left + 1])
left++;
while (left < right && nums[right] == nums[right - 1])
right--;
left++;
right--;
}else if(sum > target){
right--;
}else{
left++;
}
}
}
return ans;
}
}
k数之和
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
return kSum(nums, 0, 4, (long) target);
}
List<List<Integer>> kSum(int[] nums, int start, int k, long target) {
List<List<Integer>> res = new ArrayList<>();
int n = nums.length;
if (n < k || k < 2) return res;
if (k == 2) {
return twoSum(nums, start, target);
}
for (int i = start; i < n; i++) {
// 去重:同层不能重复
if (i > start && nums[i] == nums[i - 1])
continue;
long newTarget = target - nums[i];
List<List<Integer>> next = kSum(nums, i + 1, k - 1, newTarget);
for (List<Integer> row : next) {
row.add(nums[i]);
res.add(row);
}
}
return res;
}
List<List<Integer>> twoSum(int[] nums, int start, long target) {
List<List<Integer>> res = new ArrayList<>();
int left = start, right = nums.length - 1;
while (left < right) {
long sum = (long) nums[left] + nums[right];
if (sum == target) {
res.add(new ArrayList<>(Arrays.asList(nums[left], nums[right])));
int lv = nums[left], rv = nums[right];
while (left < right && nums[left] == lv) left++;
while (left < right && nums[right] == rv) right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
return res;
}
}
⭐42. 接雨水 - 力扣(LeetCode)
给定 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 个单位的雨水(蓝色部分表示雨水)。
双指针总结
对撞双指针:一般都是排序好的,没有排序好可能需要根据题意进行排序。求和、区间
快慢双指针:去重、原地修改
滑动窗口:字串、子数组
10.18/11.25
⭐438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
示例 1:
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
定长滑动窗口
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> res = new ArrayList<>();
int[] cnt = new int[26];
for(char c : p.toCharArray()){
cnt[c - 'a']++;
}
int l = 0;
int[] windows = new int[26];
for(int r = 0; r < s.length();r++){
windows[s.charAt(r) - 'a']++;
while(r - l + 1 >= p.length()){
if(Arrays.equals(windows,cnt)){
res.add(l);
}
windows[s.charAt(l) - 'a']--;
l++;
}
}
return res;
}
}
3. 无重复字符的最长子串 - 力扣(LeetCode)
给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。
示例 1:
输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。注意 "bca" 和 >"cab" 也是正确答案。
经典的不定长滑动窗口
class Solution {
public int lengthOfLongestSubstring(String s) {
Set<Character> set = new HashSet<>();
int res = 0;
int l = 0,r = 0;
for(r = 0; r < s.length(); r++){
char c = s.charAt(r);
while(set.contains(c)){
char ch = s.charAt(l);
set.remove(ch);
l++;
}
set.add(c);
res = Math.max(res,r - l + 1);
}
return res;
}
}
⭐⭐560. 和为 K 的子数组 - 力扣(LeetCode)
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。
子数组是数组中元素的连续非空序列。
示例 1:
输入:nums = [1,1,1], k = 2
输出:2示例 2:
输入:nums = [1,2,3], k = 3
输出:2
这道题是子串问题,常见的解题思路有滑动窗口和前缀和,该题数字有负数,所以只能用前缀和
一眼就想着滑动窗口秒了
class Solution {
public int subarraySum(int[] nums, int k) {
if(nums.length == 1){
return nums[0] == k ? 1 : 0;
}
int cnt = 0;
int l = 0;
int total = 0;
for(int r = 0; r < nums.length;r++){
total += nums[r];
while(total >= k){
if(total == k){
cnt++;
}
total -= nums[l];
l++;
}
}
return cnt;
}
}
我去,有负数。左思右想,欸嘿嘿,前缀和
class Solution {
public int subarraySum(int[] nums, int k) {
int n = nums.length;
int res = 0;
int[] preSum = new int[n + 1];
for(int i = 1; i <= n;i++){
preSum[i] = preSum[i - 1] + nums[i - 1];
}
for(int i = 0; i < n;i++){
for(int j = i + 1; j <= n;j++){
if(preSum[j] - preSum[i] == k){
res++;
}
}
}
return res;
}
}
咦,击败5.02%
问问GPT怎么优化
喔,击败100%,学会了。
优化思路:哈希集合来记录所有出现过的前缀和就不需要再一次遍历了
这里提前加入(0,1)而不是(0,0)太妙了
class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
int pre = 0, res = 0;
for (int num : nums) {
pre += num;
if (map.containsKey(pre - k)) {
res += map.get(pre - k);
}
map.put(pre, map.getOrDefault(pre, 0) + 1);
}
return res;
}
}
滑动窗口总结
⭐滑动窗口内的数字必须是正数
字符统计和统计区间一般要配合哈希表来解题
10.19
给你一个整数数组 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 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
一眼滑动窗口+优先队列秒了,提交超时
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>((a,b)->(b - a));
List<Integer> memo = new ArrayList<>();
int right = 0;
int left = 0;
int n = nums.length;
while(right < n){
pq.add(nums[right]);
while(right - left + 1 == k){
memo.add(pq.peek());
pq.remove(nums[left]);
left++;
}
right++;
}
int size = memo.size();
int[] res = new int[size];
for(int i = 0; i < size;i++){
res[i] = memo.get(i);
}
return res;
}
}
正确写法:滑动窗口+单调队列
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length == 0 || k == 0) return new int[0];
Deque<Integer> deque = new LinkedList<>();
int[] res = new int[nums.length - k + 1];
int n = nums.length;
for(int i = 0; i < n;i++){
if(!deque.isEmpty() && deque.peekFirst() <= i - k ){
deque.removeFirst();
}
while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]){
deque.removeLast();
}
deque.addLast(i);
if(i >= k - 1){
res[i - k + 1] = nums[deque.peekFirst()];
}
}
return res;
}
}
ST(稀疏表)
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int m = (int)(Math.log(n)/Math.log(2)) + 1;//区间长度的上界
int[][] st = new int[n][m + 1];
//st[i][j] 表示从i,i + 2 ^ j -1区间的最值,区间长度为2^j
int[] log2 = new int[n + 1];
log2[1] = 0;
for(int i = 2; i <= n;i++){
log2[i] = log2[i >> 1] + 1;
}
for(int i = 0; i < n;i++){
st[i][0] = nums[i];
}
//i + 2 ^j的区间最值
//st[i][j-1]是将st[i][j]分成大小相等的两半
for(int j = 1; j < m;j++){
int len = 1 << j;
int half = len >> 1;
for(int i = 0; i + len <= n;i++){
st[i][j] = Math.max(st[i][j-1],st[i+half][j-1]);
}
}
//结果的大小为n - k + 1
//i<=size - 1即i <= n - k + 1 - 1 即i + k<= n
int[] res = new int[n - k + 1];
for(int i = 0; i + k <= n;i++){
int len = k;
int j = log2[len];
int left = st[i][j];
int right = st[i + len - (1 << j)][j];
//查询公式ans = max(st[L][j], st[R - (1 << j) + 1][j])
res[i] = Math.max(left,right);
}
return res;
}
}
给你一个字符串 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) {
if (s.length() < t.length()) {
return "";
}
Map<Character, Integer> window = new HashMap<>();
Map<Character, Integer> need = new HashMap<>();
for (char c : t.toCharArray()) {
need.put(c, need.getOrDefault(c, 0) + 1);
}
int valid = 0;
int left = 0, right = 0;
int start = 0, len = Integer.MAX_VALUE;
while (right < s.length()) {
char cr = s.charAt(right);
right++;
if (need.containsKey(cr)) {
window.put(cr, window.getOrDefault(cr, 0) + 1);
if (window.get(cr).equals(need.get(cr))) {
valid++;
}
}
while (valid == need.size()) {
if(right - left < len){
start = left;
len = right - left;
}
char cl = s.charAt(left);
left++;
if (window.containsKey(cl)) {
if (window.get(cl).equals(need.get(cl))) {
valid--;
}
window.put(cl, window.get(cl) - 1);
}
}
}
return len == Integer.MAX_VALUE ? "" : s.substring(start,start+len);
}
}
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组是数组中的一个连续部分。
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
滑动窗口+前缀最小值
class Solution {
public int maxSubArray(int[] nums) {
if(nums.length == 1){
return nums[0];
}
int max = Integer.MIN_VALUE;
int n = nums.length;
int[] preSum = new int[n + 1];
for(int i = 1; i <= n;i++){
preSum[i] = preSum[i - 1] + nums[i - 1];
}
int min = Integer.MAX_VALUE;
for(int i = 0; i <= n;i++){
max = Math.max(max,preSum[i] - min);
min = Math.min(min,preSum[i]);
}
return max;
}
}
前缀和+最小前缀
class Solution {
public int maxSubArray(int[] nums) {
int preSum = 0;
int res = Integer.MIN_VALUE;
int preMin = 0;
for (int num : nums) {
preSum += num;
res = Math.max(res,preSum - preMin);
preMin = Math.min(preMin,preSum);
}
return res;
}
}
贪心:局部最优解就是全局最优解
Kadane算法
class Solution {
public int maxSubArray(int[] nums) {
int curSum = 0;
int maxSum = Integer.MIN_VALUE;
for(int i = 0; i < nums.length;i++){
curSum = Math.max(nums[i],curSum + nums[i]);
maxSum = Math.max(maxSum,curSum);
}
return maxSum;
}
}
10.20
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [start(i), end(i)] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]。
left和right表示端点:
class Solution {
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals,(a,b) -> a[0] - b[0]);
List<int[]> res = new ArrayList<>();
int n = intervals.length;
for(int i = 0; i < n;i++){
int left = i,right = i + 1;
while(right < n && intervals[left][1] >= intervals[right][0]){
intervals[left][1] = Math.max(intervals[left][1], intervals[right][1]);
right++;
}
res.add(new int[]{intervals[left][0],intervals[left][1]});
i = right - 1;
}
return res.toArray(new int[res.size()][]);
}
}
left和right表示端点变量
class Solution {
public int[][] merge(int[][] intervals) {
if (intervals == null || intervals.length == 0) {
return new int[0][];
}
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
List<int[]> res = new ArrayList<>();
int n = intervals.length;
int i = 0;
while (i < n) {
int left = intervals[i][0];
int right = intervals[i][1];
// 吸收所有与当前区间重叠的区间,并更新右端
while (i + 1 < n && intervals[i + 1][0] <= right) {
i++;
right = Math.max(right, intervals[i][1]);
}
res.add(new int[]{left, right});
i++; // 继续处理下一个未合并的区间
}
return res.toArray(new int[res.size()][]);
}
}
用端点值表示,更简洁的写法
class Solution {
public int[][] merge(int[][] intervals) {
if (intervals == null || intervals.length == 0) return new int[0][];
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
List<int[]> res = new ArrayList<>();
int[] cur = intervals[0];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] <= cur[1]) {
cur[1] = Math.max(cur[1], intervals[i][1]);
} else {
res.add(cur);
cur = intervals[i];
}
}
res.add(cur);
return res.toArray(new int[res.size()][]);
}
}
给定一个整数数组 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]空间复杂度O(n)
class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length;
k = k % n;
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
for(int i = 0; i < n - k;i++){
list1.add(nums[i]);
}
for(int i = n - k; i < n;i++){
list2.add(nums[i]);
}
list2.addAll(list1);
for (int i = 0; i < n; i++) {
nums[i] = list2.get(i);
}
}
}
空间复杂度O(1)
class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length;
k = k % n;
reverse(nums, 0, n - 1);
reverse(nums, 0, k - 1);
reverse(nums, k , n - 1);
}
public void reverse(int[] nums, int left, int right) {
while (left < right) {
int t = nums[right];
nums[right] = nums[left];
nums[left] = t;
left++;
right--;
}
}
}
238. 除自身以外数组的乘积 - 力扣(LeetCode)
给你一个整数数组 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]前后缀乘积,前缀乘积会有1的偏量
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] pre = new int[n + 1];
int[] res = new int[n];
pre[0] = 1;
for(int i = 1; i <= n;i++){
pre[i] = pre[i - 1] * nums[i - 1];
}
int[] suf = new int[n];
suf[n - 1] = 1;
for(int i = n - 2; i >= 0;i--){
suf[i] = suf[i + 1] * nums[i + 1];
}
for(int i = 0; i < n;i++){
res[i] = pre[i] * suf[i];
}
return res;
}
}
10.21
给定一个 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]]
时隔四个月第二次写这道题看到题就有思路了,记录行和列+重放设为零
class Solution {
public void setZeroes(int[][] matrix) {
Set<Integer> rows = new HashSet<>();
Set<Integer> cols = new HashSet<>();
int m = matrix.length, n = matrix[0].length;
for(int i = 0; i < m;i++){
for(int j = 0; j < n;j++){
if(matrix[i][j] == 0){
rows.add(i);
cols.add(j);
}
}
}
for(int row : rows){
for(int i = 0; i < n;i++){
matrix[row][i] = 0;
}
}
for(int col : cols){
for(int i = 0; i < m;i++){
matrix[i][col] = 0;
}
}
}
}
这个时间复杂度比较低,优化下,使用两个boolean类型的数组记录
class Solution {
public void setZeroes(int[][] matrix) {
int rows = matrix.length;
int clos = matrix[0].length;
boolean[] row = new boolean[rows];
boolean[] clo = new boolean[clos];
for(int i = 0; i < rows;i++){
for(int j = 0; j < clos; j++){
if(matrix[i][j] == 0){
row[i] = true;
clo[j] = true;
}
}
}
for(int i = 0; i < rows;i++){
for(int j = 0; j < clos; j++){
if(row[i] || clo[j]){
matrix[i][j] = 0;
}
}
}
}
}
原地算法:
用第一行和第一列记录每一行或者每一列是否有0,firstRow和firstCol记录第一行和第一列是否有0
class Solution {
public void setZeroes(int[][] matrix) {
int n = matrix.length;
int m = matrix[0].length;
boolean firstRow = false;
boolean firstCol = false;
for(int i = 0; i < n;i++){
if(matrix[i][0] == 0){
firstCol = true;
break;
}
}
for(int i = 0;i < m;i++){
if(matrix[0][i] == 0){
firstRow = true;
break;
}
}
for(int i = 1;i < n;i++){
for(int j = 1;j < m;j++){
if(matrix[i][j] == 0){
matrix[i][0] = 0;
matrix[0][j] = 0;
}
}
}
for(int i = 1;i < n;i++){
for(int j = 1;j < m;j++){
if(matrix[i][0] == 0 || matrix[0][j]==0){
matrix[i][j] = 0;
}
}
}
if(firstRow){
for(int j = 0; j < m;j++){
matrix[0][j] = 0;
}
}
if(firstCol){
for(int i = 0; i < n;i++){
matrix[i][0] = 0;
}
}
}
}
54. 螺旋矩阵 - 力扣(LeetCode)
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]思路:四个指针,不断向中间压缩
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
List<Integer> res = new ArrayList<>();
int top = 0, bottom = m - 1,left = 0, right = n-1;
int size = m * n;
while(res.size() < size){
for(int i = left; i <= right && res.size() < size;i++){
res.add(matrix[top][i]);
}
top++;
for(int i = top; i <= bottom && res.size() < size;i++){
res.add(matrix[i][right]);
}
right--;
for(int i = right;i >= left && res.size() < size; i--){
res.add(matrix[bottom][i]);
}
bottom--;
for(int i = bottom; i >= top && res.size() < size;i--){
res.add(matrix[i][left]);
}
left++;
}
return res;
}
}
41. 缺失的第一个正数 - 力扣(LeetCode)
给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。
示例 1:
输入:nums = [1,2,0]
输出:3
解释:范围 [1,2] 中的数字都在数组中。空间复杂度是O(n)
class Solution {
public int firstMissingPositive(int[] nums) {
Set<Integer> set = new HashSet<>();
for(int i = 0; i < nums.length;i++){
set.add(nums[i]);
}
for(int i = 1; i <= nums.length;i++ ){
if(!set.contains(i)){
return i;
}
}
return nums.length + 1;
}
}
空间复杂度为O(1)
将数组看作哈希表,nums[nums[i]-1] != nums[i]的就换位,使得最后nums[i]在i+1的位置上
class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;
for(int i = 0; i < n;i++){
while(nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]){
swap(nums,nums[i] - 1,i);
}
}
for(int i = 0; i < n;i++){
if(nums[i] != i + 1){
return i+1;
}
}
return n + 1;
}
public void swap(int[] nums,int a,int b){
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
}
10.22
⭐240. 搜索二维矩阵 II - 力扣(LeetCode)
编写一个高效的算法来搜索 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
每层都二分一次效率较低
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length;
int n = matrix[0].length;
boolean flag = false;
for(int[] nums : matrix){
int res = binarySerach(nums,target);
if(res != -1){
flag = true;
}
}
return flag;
}
public int binarySerach(int[] nums,int target){
int left = 0, right = nums.length - 1;
while(left <= right){
int mid = (left + right) >>> 1;
if(nums[mid] == target){
return mid;
}else if(nums[mid] > target){
right = mid - 1;
}else{
left = mid + 1;
}
}
return -1;
}
}
从右上角开始的二叉搜索树
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int row = 0,col = matrix[0].length - 1;
while(row < matrix.length && col >= 0){
if(target == matrix[row][col]){
return true;
}else if(matrix[row][col] > target){
col--;
}else{
row++;
}
}
return false;
}
}
48. 旋转图像 - 力扣(LeetCode)
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
先对折再将列逆序
class Solution {
public void rotate(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
for(int i = 0; i < m;i++){
for(int j = i + 1; j < n;j++){
int t = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = t;
}
}
for(int[] row : matrix){
swap(row);
}
}
public void swap(int[] row){
int i = 0, j = row.length - 1;
while(i < j){
int t = row[i];
row[i] = row[j];
row[j] = t;
i++;
j--;
}
}
}
160. 相交链表 - 力扣(LeetCode)
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 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 中第四个节点) 在内存中指向相同的位置。
使用哈希表记录
最先想到的是记录遍历两个链表的节点
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
HashSet<ListNode> set = new HashSet<>();
ListNode p1 = headA;
ListNode p2 = headB;
while(p1 != null){
set.add(p1);
p1 = p1.next;
}
while(p2 != null){
if(set.contains(p2)){
break;
}
p2 = p2.next;
}
return p2;
}
}
观察双指针,一长一短终会相遇
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode cur1 = headA;
ListNode cur2 = headB;
while(cur1 != cur2){
cur1 = cur1 == null ? headB : cur1.next;
cur2 = cur2 == null ? headA : cur2.next;
}
return cur1;
}
}
10.23/12.2
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while(cur != null){
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
递归
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode last = reverseList(head.next);
head.next.next = head;
head.next = null;
return last;
}
}
234. 回文链表 - 力扣(LeetCode)
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
示例 1:
输入:head = [1,2,2,1]
输出:trueO(n)的解法,容易想到
class Solution {
public boolean isPalindrome(ListNode head) {
if(head == null || head.next == null){
return true;
}
ListNode cur = head;
List<Integer> list = new ArrayList();
while(cur != null){
list.add(cur.val);
cur = cur.next;
}
int left = 0, right = list.size() - 1;
while(left < right){
if(list.get(left) != list.get(right)){
return false;
}
left++;
right--;
}
return true;
}
}
我的思路是遍历一遍链表并用List记录val,再用Collections.reverse(list),根据val创建个一样的链表,如果两个链表的val不同返回false,但和第一种解法是一样的。并且比解法1用到的空间更多
O(1)的解法思路:获取head的中点,反转中点开始的链表,比较前段和后段链表的val是否相同
class Solution {
public boolean isPalindrome(ListNode head) {
ListNode mid = mid(head);
ListNode head2 = reverse(mid);
while(head2 != null){
if(head.val != head2.val){
return false;
}
head = head.next;
head2 = head2.next;
}
return true;
}
ListNode mid(ListNode head){
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
ListNode reverse(ListNode mid){
ListNode cur = mid;
ListNode next = mid.next;
cur.next = null;
while(next != null){
ListNode t = next.next;
next.next = cur;
cur = next;
next = t;
}
return cur;
}
}
class Solution {
public boolean isPalindrome(ListNode head) {
int cnt = 0;
ListNode cur = head;
while(cur != null){
cur = cur.next;
cnt++;
}
cur = head;
cnt /= 2;
while(cnt > 1){
cur = cur.next;
cnt--;
}
ListNode head2 = reverse(cur.next);
ListNode head1 = head;
while(head2!=null){
if(head1.val != head2.val){
return false;
}
head1 = head1.next;
head2 = head2.next;
}
return true;
}
public ListNode reverse(ListNode head){
if(head == null){
return head;
}
ListNode cur = head;
ListNode pre = null;
while(cur != null){
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
141. 环形链表 - 力扣(LeetCode)
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例 1:
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null || head.next == null){
return false;
}
ListNode slow = head, fast = head.next;
while(slow != fast && fast != null&&fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
return slow == fast;
}
}
10.24
给定一个链表的头节点 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
解释:链表中没有环。提示:
链表中节点的数目范围在范围 [0, 10(4)] 内
-10(5) <= Node.val <= 10(5)
pos 的值为 -1 或者链表中的一个有效索引
弗洛伊德判环
东哥解释
原理也简单说下吧,我们假设快慢指针相遇时,慢指针 slow 走了 k 步,那么快指针 fast 一定走了 2k 步:
fast 一定比 slow 多走了 k 步,这多走的 k 步其实就是 fast 指针在环里转圈圈,所以 k 的值就是环长度的「整数倍」。
假设相遇点距环的起点的距离为 m,那么结合上图的 slow 指针,环的起点距头结点 head 的距离为 k - m,也就是说如果从 head 前进 k - m 步就能到达环起点。
巧的是,如果从相遇点继续前进 k - m 步,也恰好到达环起点:
所以,只要我们把快慢指针中的任一个重新指向 head,然后两个指针同速前进,k - m 步后一定会相遇,相遇之处就是环的起点了。
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head == null||head.next == null){
return null;
}
ListNode slow = head,fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){
break;
}
}
if (fast == null || fast.next == null) {
return null;
}
fast = head;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
图论环检测法207. 课程表 - 力扣(LeetCode)
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [a(i), b(i)] ,表示如果要学习课程 a(i) 则 必须 先学习课程 b(i)( )。
例如,先修课程对 [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 。这是不可能的。
提示:
1 <= numCourses <= 2000
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= a(i), b(i) < numCourses
prerequisites[i] 中的所有课程对 互不相同
⭐
class Solution {
int[] visited;
List<List<Integer>> edges;
boolean flag = true;
public boolean canFinish(int numCourses, int[][] prerequisites) {
visited = new int[numCourses];
edges = new ArrayList<>();
for(int i = 0; i < numCourses;i++){
edges.add(new ArrayList<>());
}
for(int[] edge : prerequisites){
edges.get(edge[1]).add(edge[0]);
}
for(int i = 0; i < numCourses;i++){
if(visited[i] == 0){
dfs(i);
}
}
return flag;
}
void dfs(int i){
visited[i] = 1;
for(int v : edges.get(i)){
if(visited[v] == 0){
dfs(v);
if(!flag){
return;
}
}else if(visited[v] == 1){
flag = false;
return;
}
}
visited[i] = 2;
}
}
25. K 个一组翻转链表 - 力扣(LeetCode)
给你链表的头节点 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]提示:
链表中的节点数目为 n
1 <= k <= n <= 5000
0 <= Node.val <= 1000
**进阶:**你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?
10.25
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
给你一个链表,删除链表的倒数第 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]
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if (head == null) return null;
ListNode dummy = new ListNode(0);
dummy.next = head;
int size = 0;
ListNode cur = dummy;
while (cur.next != null) {
size++;
cur = cur.next;
}
int steps = size - n;
ListNode prev = dummy;
for (int i = 0; i < steps; i++) {
prev = prev.next;
}
prev.next = prev.next.next;
return dummy.next;
}
}
24. 两两交换链表中的节点 - 力扣(LeetCode)
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]示例 2:
输入:head = []
输出:[]示例 3:
输入:head = [1]
输出:[1]这里写题的时候不要纠结怎么递归,只需要记住swapPairs(second.next)会返回两个反转的链表节点即可,first.next = swapPairs(second.next);再慢慢去实现递归
class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode first = head;
ListNode second = head.next;
first.next = swapPairs(second.next);
second.next = first;
return second;
}
}
23. 合并 K 个升序链表 - 力扣(LeetCode)
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 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 = [[]]
输出:[]
class Solution {
public ListNode sortList(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode fast = head;
ListNode slow = head;
ListNode prev = null;
while(fast != null && fast.next != null){
prev = slow;
fast = fast.next.next;
slow = slow.next;
}
prev.next = null;
ListNode left = sortList(head);
ListNode right = sortList(slow);
return merge(left,right);
}
public ListNode merge(ListNode l1,ListNode l2){
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
while(l1 != null && l2 != null){
if(l1.val < l2.val){
cur.next = l1;
l1 = l1.next;
}else{
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 == null ? l2 : l1;
return dummy.next;
}
}
10.26
10.27
TODO:链表总结
226. 翻转二叉树 - 力扣(LeetCode)
给你一棵二叉树的根节点 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 = []
输出:[]
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null){
return root;
}
TreeNode t = root.right;
root.right = invertTree(root.left);
root.left = invertTree(t);
return root;
}
}
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null){
return root;
}
TreeNode right = invertTree(root.left);
TreeNode left = invertTree(root.right);
root.left = left;
root.right = right;
return root;
}
}
104. 二叉树的最大深度 - 力扣(LeetCode)
给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
示例 1:
[该类型的内容暂不支持下载]
输入:root = [3,9,20,null,null,15,7]
输出:3示例 2:
输入:root = [1,null,2]
输出:2
class Solution {
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}
int l = maxDepth(root.left);
int r = maxDepth(root.right);
return Math.max(l,r) + 1;
}
}
94. 二叉树的中序遍历 - 力扣(LeetCode)
给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
示例 1:
输入:root = [1,null,2,3]
输出:[1,3,2]示例 2:
输入:root = []
输出:[]示例 3:
输入:root = [1]
输出:[1]
class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
dfs(root);
return res;
}
void dfs(TreeNode root){
if(root == null){
return;
}
dfs(root.left);
res.add(root.val);
dfs(root.right);
}
}
101. 对称二叉树 - 力扣(LeetCode)
给你一个二叉树的根节点 root , 检查它是否轴对称。
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
root.left 和 root.right分别dfs
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null){
return true;
}
TreeNode left = root.left;
TreeNode right = root.right;
return dfs(left,right);
}
boolean dfs(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;
}
boolean l = dfs(left.left,right.right);
boolean r = dfs(left.right,right.left);
return l && r;
}
}
543. 二叉树的直径 - 力扣(LeetCode)
给你一棵二叉树的根节点,返回该树的 直径 。
二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。
两节点之间路径的 长度 由它们之间边数表示。
示例 1:
输入:root = [1,2,3,4,5]
输出:3
解释:3 ,取路径 [4,2,1,3] 或 [5,2,1,3] 的长度。示例 2:
输入:root = [1,2]
输出:1
class Solution {
int max = 0;
public int diameterOfBinaryTree(TreeNode root) {
if(root == null){
return 0;
}
dfs(root);
return max;
}
int dfs(TreeNode root){
if(root == null){
return 0;
}
int l = dfs(root.left);
int r = dfs(root.right);
max = Math.max(max,l + r);
return Math.max(l,r) + 1;
}
}
10.28
102. 二叉树的层序遍历 - 力扣(LeetCode)
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]示例 2:
输入:root = [1]
输出:[[1]]示例 3:
输入:root = []
输出:[]
BFS写法:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
Queue<TreeNode> q = new ArrayDeque<>();
if(root == null){
return res;
}
q.offer(root);
while(!q.isEmpty()){
int n = q.size();
List<Integer> list = new ArrayList<>();
while(n-- > 0){
TreeNode cur = q.poll();
list.add(cur.val);
if(cur.left != null){
q.offer(cur.left);
}
if(cur.right != null){
q.offer(cur.right);
}
}
res.add(list);
}
return res;
}
}
DFS写法:
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> levelOrder(TreeNode root) {
dfs(root,0);
return res;
}
void dfs(TreeNode root,int index){
if(root == null){
return;
}
if(index == res.size()){
res.add(new ArrayList<>());
}
res.get(index).add(root.val);
dfs(root.left,index + 1);
dfs(root.right,index + 1);
}
}
⭐108. 将有序数组转换为二叉搜索树 - 力扣(LeetCode)
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。
示例 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) {
int n = nums.length;
return dfs(nums,0,n - 1);
}
TreeNode dfs(int[] nums,int left,int right){
if(left > right){
return null;
}
int mid = (left + right) >>> 1;
TreeNode root = new TreeNode(nums[mid]);
//左区域的重点就是左子树的根节点
root.left = dfs(nums,left,mid-1);
//右区域的重点就是右子树的根节点
root.right = dfs(nums,mid + 1, right);
return root;
}
}
98. 验证二叉搜索树 - 力扣(LeetCode)
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 严格小于 当前节点的数。
节点的右子树只包含 严格大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3]
输出:true示例 2:
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。两个解法,最好想到的是中序遍历二叉树并将中序遍历结果加入到ArrayList中,遍历ArrayList中的元素,前一个元素大于等于后一个元素返回false
class Solution {
List<Integer> res = new ArrayList<>();
public boolean isValidBST(TreeNode root) {
if(root==null){
return true;
}
dfs(root);
for(int i = 1; i < res.size();i++){
if(res.get(i - 1) >= res.get(i)){
return false;
}
}
return true;
}
void dfs(TreeNode root){
if(root == null){
return;
}
dfs(root.left);
res.add(root.val);
dfs(root.right);
}
}
第二种解法,根据二叉搜索树中序遍历是有序的,前一个大于等于后一个返回false
class Solution {
long pre = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if (root == null) {
return true;
}
boolean l = isValidBST(root.left);
if(pre >= root.val){
return false;
}
pre = root.val;
boolean r = isValidBST(root.right);
return l && r;
}
}
230. 二叉搜索树中第 K 小的元素 - 力扣(LeetCode)
给定一个二叉搜索树的根节点 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两个写法:遍历+记录
class Solution {
List<Integer> list = new ArrayList<>();
public int kthSmallest(TreeNode root, int k) {
if(root == null){
return 0;
}
dfs(root);
return list.get(k - 1);
}
void dfs(TreeNode root){
if(root == null){
return;
}
dfs(root.left);
list.add(root.val);
dfs(root.right);
}
}
递归+计数
class Solution {
int res = 0;
int cnt = 1;
public int kthSmallest(TreeNode root, int k) {
if(root == null){
return 0;
}
dfs(root,k);
return res;
}
void dfs(TreeNode root,int k){
if(root == null){
return;
}
dfs(root.left,k);
if(cnt == k){
res = root.val;
}
cnt++;
dfs(root.right,k);
}
}
10.29
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
示例 1:
输入:root = [1,2,3,null,5,null,4]
输出:[1,3,4]
解释:
示例 2:
输入:root = [1,2,3,4,null,null,null,5]
输出:[1,3,4,5]
解释:
示例 3:
输入:root = [1,null,3]
输出:[1,3]
示例 4:
输入:root = []
输出:[]
层级遍历,先左后右或者先右后左都可以
先左后右
class Solution {
public List<Integer> rightSideView(TreeNode root) {
if(root == null){
return new ArrayList<>();
}
List<Integer> res = new ArrayList<>();
ArrayDeque<TreeNode> q= new ArrayDeque<>();
q.offer(root);
while(!q.isEmpty()){
int n = q.size();
int first = q.peekLast().val;
while(n-- > 0){
TreeNode cur = q.poll();
int val = cur.val;
if(cur.left != null){
q.offer(cur.left);
}
if(cur.right != null){
q.offer(cur.right);
}
}
res.add(first);
}
return res;
}
}
先右后左
class Solution {
public List<Integer> rightSideView(TreeNode root) {
if(root == null){
return new ArrayList<>();
}
List<Integer> res = new ArrayList<>();
Queue<TreeNode> q= new ArrayDeque<>();
q.offer(root);
while(!q.isEmpty()){
int n = q.size();
int first = q.peek().val;
while(n-- > 0){
TreeNode cur = q.poll();
int val = cur.val;
if(cur.right != null){
q.offer(cur.right);
}
if(cur.left != null){
q.offer(cur.left);
}
}
res.add(first);
}
return res;
}
}
递归联想到之前写的层级遍历
102. 二叉树的层序遍历
时间复杂度和BFS写法一样
class Solution {
List<List<Integer>> list = new ArrayList<>();
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
dfs(root,0);
for(List<Integer> l : list){
res.add(l.get(0));
}
return res;
}
void dfs(TreeNode root,int index){
if(root == null){
return;
}
if(index == list.size() ){
list.add(new ArrayList<>());
}
list.get(index).add(root.val);
dfs(root.right,index + 1);
dfs(root.left,index + 1);
}
}
最后特别妙的写法
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
dfs(root,0,res);
return res;
}
void dfs(TreeNode root,int index,List<Integer> res){
if(root == null){
return;
}
if(index == res.size() ){
res.add(root.val);
}
dfs(root.right,index + 1,res);
dfs(root.left,index + 1,res);
}
}
给你二叉树的根结点 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]
思路:先前序遍历,记录下左右子节点,左节点设为null,右节点清设为左节点,遍历链表,将右子树接到链表末尾的右子节点
class Solution {
public void flatten(TreeNode root) {
if(root == null){
return;
}
flatten(root.left);
flatten(root.right);
TreeNode left = root.left;
TreeNode right = root.right;
root.right = left;
root.left = null;
TreeNode cur = root;
while(cur.right != null){
cur = cur.right;
}
cur.right = right;
}
}
⭐105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)
给定两个整数数组 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]
解法一类似108. 将有序数组转换为二叉搜索树 - 力扣(LeetCode)
class Solution {
Map<Integer,Integer> map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
for(int i = 0; i < preorder.length;i++){
map.put(inorder[i],i);
}
return build(preorder,0,preorder.length - 1,inorder,0,inorder.length - 1);
}
TreeNode build(int[] preorder,int preL,int preR,int[] inorder,int inL,int inR){
if(preL > preR){
return null;
}
TreeNode root = new TreeNode(preorder[preL]);
int index = map.get(root.val);
int size = index - inL;
root.left = build(preorder,preL + 1,preL + size,inorder,inL,index - 1);
root.right = build(preorder,preL + size + 1,preR,inorder,index + 1,inR);
return root;
}
}
解法二特别妙
class Solution {
int preP;
int inP;
public TreeNode buildTree(int[] preorder, int[] inorder) {
return recursion(preorder,inorder,Integer.MAX_VALUE);
}
TreeNode recursion(int[] preorder, int[] inorder,int stop) {
// 前序遍历到头了,整体结束
if (preP == preorder.length) return null;
// 当前子树的中序范围已经结束了
if (inorder[inP] == stop) {
inP++;
return null;
}
// 从前序取出根节点
TreeNode root = new TreeNode(preorder[preP++]);
// 构建左子树时,中序遍历是左-根-右,左子树的边界就是当前节点
root.left = recursion(preorder,inorder,root.val);
// 构建右子树,中序遍历是左-根-右,右子树没有边界
root.right = recursion(preorder,inorder,stop);
return root;
}
}
10.30
124. 二叉树中的最大路径和 - 力扣(LeetCode)
二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 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为什么只返回一个子树的最大值?
如果返回的是l + r+ root.val就是图中的路径,不是一条合法的路径
class Solution {
int max = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return max;
}
int dfs(TreeNode root){
if(root == null){
return 0;
}
//负数就不选
int l = Math.max(dfs(root.left),0);
int r = Math.max(dfs(root.right),0);
//全局最大的路径
max = Math.max(max,l + r + root.val);
return root.val + Math.max(l,r);
}
}
⭐236. 二叉树的最近公共祖先 - 力扣(LeetCode)
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科 中最近公共祖先的定义为:“对于有根树 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
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null){
return root;
}
if(p == root || q == root){
return root;
}
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left != null && right != null){
return root;
}
return left != null ? left : right;
}
}
⭐⭐437. 路径总和 III - 力扣(LeetCode)
给定一个二叉树的根节点 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提示:
二叉树的节点个数的范围是 [0,1000]
-10(9) <= Node.val <= 10(9)
-1000 <= targetSum <= 1000
DFS暴力枚举
class Solution {
public int pathSum(TreeNode root, int targetSum) {
if(root == null){
return 0;
}
long res = dfs(root,(long)targetSum);
res += pathSum(root.left,targetSum);
res += pathSum(root.right,targetSum);
return (int)res;
}
long dfs(TreeNode root,long targetSum){
long res = 0;
if(root == null){
return 0;
}
if(root.val == targetSum){
res++;
}
res += dfs(root.left,targetSum - root.val);
res += dfs(root.right,targetSum - root.val);
return res;
}
}
前缀和 + 回溯
class Solution {
Map<Long,Integer> map = new HashMap<>();
int targetSum;
public int pathSum(TreeNode root, int targetSum) {
this.targetSum = targetSum;
map.put(0L,1);
return dfs(root,0L);
}
int dfs(TreeNode root,Long curSum){
if(root == null){
return 0;
}
curSum += root.val;
int cnt = map.getOrDefault(curSum - targetSum,0);
map.put(curSum,map.getOrDefault(curSum,0) + 1);
cnt += dfs(root.left,curSum);
cnt += dfs(root.right,curSum);
map.put(curSum,map.getOrDefault(curSum,0) - 1);
if(map.get(curSum) == 0){
map.remove(curSum);
}
return cnt;
}
}
TODO:总结二叉树
二叉树总结
递归先确定这个函数是做什么的,然后再去确定返回值,最后考虑结束条件。
特别注意437. 路径总和 III - 力扣(LeetCode)这道题的前缀和+回溯
二叉树层级遍历的DFS和BFS
⭐DFS
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> levelOrder(TreeNode root) {
dfs(root,0);
return res;
}
void dfs(TreeNode root,int depth){
if(root == null){
return;
}
if(res.size() == depth){
res.add(new ArrayList<>());
}
res.get(depth).add(root.val);
dfs(root.left,depth + 1);
dfs(root.right,depth + 1);
}
}
⭐BFS
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> levelOrder(TreeNode root) {
if(root == null){
return new ArrayList<>();
}
Queue<TreeNode> q = new ArrayDeque<>();
q.offer(root);
while(!q.isEmpty()){
int size = q.size();
List<Integer> list = new ArrayList<>();
while(size-- > 0){
TreeNode cur = q.poll();
int val = cur.val;
list.add(val);
if(cur.left != null){
q.offer(cur.left);
}
if(cur.right != null){
q.offer(cur.right);
}
}
res.add(list);
}
return res;
}
}
10.31
⭐207. 课程表 - 力扣(LeetCode)
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [a(i), b(i)] ,表示如果要学习课程 a(i) 则 必须 先学习课程 b(i)( )。
例如,先修课程对 [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 {
List<List<Integer>> edges = new ArrayList<>();
int[] visited;
boolean flag = true;
public boolean canFinish(int numCourses, int[][] prerequisites) {
visited = 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]);
}
/*构建邻接表 [[1,0]]
0:[]
1:[0]
*/
// 遍历所有节点(防止图不连通)
for (int i = 0; i < numCourses; i++) {
// 如果某个节点未被访问,则对其执行 DFS
if (visited[i] == 0) {
dfs(i);
}
}
return flag;
}
void dfs(int i) {
visited[i] = 1;
//遍历当前节点的所有出度节点
for (int v : edges.get(i)) {
if (visited[v] == 0) {
dfs(v);
if (!flag) {
return;
}
}else if(visited[v] == 1){
flag = false;
return;
}
}
visited[i] = 2;
}
}
⭐994. 腐烂的橘子 - 力扣(LeetCode)
在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。
返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1 。
示例 1:
输入:grid = [[2,1,1],[1,1,0],[0,1,1]]
输出:4只能用BFS,DFS无法计数
⭐200. 岛屿数量 - 力扣(LeetCode)
给你一个由 '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']
]
输出:3DFS或者并查集
DFS
class Solution {
boolean[][] visited;
public int numIslands(char[][] grid) {
int m = grid.length;
int n = grid[0].length;
int res = 0;
visited = new boolean[m][n];
for(int i = 0; i < m;i++){
for(int j = 0; j < n;j++){
if(!visited[i][j] && grid[i][j] == '1'){
res++;
dfs(grid,i,j);
}
}
}
return res;
}
void dfs(char[][] grid,int i,int j){
if(i < 0 || j < 0 || i >= grid.length|| j >= grid[0].length||grid[i][j] == '0' || visited[i][j]){
return;
}
visited[i][j] = true;
dfs(grid,i-1,j);
dfs(grid,i+1,j);
dfs(grid,i,j-1);
dfs(grid,i,j+1);
}
}
并查集(parent初始化为自己)
class Solution {
int count;
int[] parent;
public int numIslands(char[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dirs = {{1,0},{0,1}};
count = 0;
parent = new int[m * n];
for(int i = 0; i < m;i++){
for(int j = 0; j < n;j++){
int index = i * n + j;
if(grid[i][j] == '1'){
parent[index] = index;
count++;
}else{
parent[index] = -1;
}
}
}
for(int i = 0; i < m;i++){
for(int j = 0; j < n;j++){
if(grid[i][j] == '1'){
int index1 = i * n + j;
for(int[] dir : dirs){
int ni = dir[0] + i;
int nj = dir[1] + j;
int index2 = ni * n +nj;
union(index1,index2);
}
}
}
}
}
return getCount();
}
void union(int p,int q){
int rootP = find(p);
int rootQ = find(q);
if(rootP == rootQ){
return;
}
parent[rootQ]= rootP;
count--;
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
int getCount() {
return count;
}
}
11.1
78. 子集 - 力扣(LeetCode)
给你一个整数数组 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>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
dfs(nums,0);
return res;
}
void dfs(int[] nums,int start){
res.add(new ArrayList<>(path));
if(path.size() == nums.length){
return;
}
for(int i = start;i < nums.length;i++){
path.add(nums[i]);
dfs(nums,i + 1);
path.remove(path.size() - 1);
}
}
}
⭐17. 电话号码的字母组合 - 力扣(LeetCode)
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]示例 2:
输入:digits = "2"
输出:["a","b","c"]
class Solution {
static final String[] menu = new String[] {
" ", //0
"", //1
"abc", //2
"def", //3
"ghi", //4
"jkl", //5
"mno", //6
"pqrs", //7
"tuv", //8
"wxyz" //9
};
List<String> res = new ArrayList<>();
StringBuilder path = new StringBuilder();
public List<String> letterCombinations(String digits) {
int n = digits.length();
dfs(digits,0);
return res;
}
void dfs(String digits,int idx){
if(path.length() == digits.length()){
res.add(path.toString());
return;
}
int index = digits.charAt(idx) - '0';
String s = menu[index];
for(char c : s.toCharArray()){
path.append(c);
dfs(digits,idx + 1);
path.deleteCharAt(path.length() - 1);
}
}
}
46. 全排列 - 力扣(LeetCode)
给定一个不含重复数字的数组 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>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] visited;
public List<List<Integer>> permute(int[] nums) {
int n = nums.length;
visited = new boolean[n];
dfs(nums);
return res;
}
void dfs(int[] nums) {
if (path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length ; i++) {
if (visited[i]) {
continue;
}
path.add(nums[i]);
visited[i] = true;
dfs(nums);
path.remove(path.size() - 1);
visited[i] = false;
}
}
}
排列组合总结
1)当组合中允许元素重复:则迭代的起始值就是i,每次递增
2)当组合中不允许重复:迭代的起始值是i + 1,每次递增
3)当这是排列不是组合:迭代的起始值是传进去的start(初始位),每次都从头开始遍历所有元素
11.2
给你一个 无重复元素 的整数数组 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>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
dfs(candidates,target,0);
return res;
}
void dfs(int[] candidates,int target,int start){
if(target == 0){
res.add(new ArrayList<>(path));
return;
}
if(target < 0){
return;
}
for(int i = start; i < candidates.length;i++){
path.add(candidates[i]);
dfs(candidates,target - candidates[i],i);
path.remove(path.size() - 1);
}
}
}
⭐79. 单词搜索 - 力扣(LeetCode)
给定一个 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 {
StringBuilder path = new StringBuilder();
boolean[][] visited;
boolean found = false;
public boolean exist(char[][] board, String word) {
visited = new boolean[board.length][board[0].length];
for(int i = 0; i < board.length;i++){
for(int j = 0; j < board[0].length;j++){
dfs(board,i,j,word);
if(found){
return true;
}
}
}
return false;
}
void dfs(char[][] board,int i, int j,String word){
if(path.length() == word.length()){
found = true;
return;
}
if(found){
return;
}
if(i < 0 || j < 0 || i >= board.length || j >= board[0].length || visited[i][j]){
return;
}
int idx = path.length();
if(board[i][j] != word.charAt(idx)){
return;
}
path.append(board[i][j]);
visited[i][j] = true;
dfs(board,i - 1,j,word);
dfs(board,i + 1,j,word);
dfs(board,i,j - 1,word);
dfs(board,i,j + 1,word);
visited[i][j] = false;
path.deleteCharAt(path.length() - 1);
}
}
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:
输入:n = 1
输出:["()"]
class Solution {
List<String> res = new ArrayList<>();
StringBuilder path = new StringBuilder();
public List<String> generateParenthesis(int n) {
len = 2 * n;
dfs(n,n);
return res;
}
void dfs(int left,int right){
if(right < left){
return;
}
if(left < 0 || right < 0){
return;
}
if(left == 0 && right == 0){
res.add(path.toString());
return;
}
path.append('(');
dfs(left - 1,right);
path.deleteCharAt(path.length() - 1);
path.append(')');
dfs(left,right - 1);
path.deleteCharAt(path.length() - 1);
}
}
10.3
给你一个字符串 s,请你将 s 分割成一些 子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a"
输出:[["a"]]
class Solution {
List<List<String>> res = new ArrayList<>();
List<String> path = new ArrayList<>();
public List<List<String>> partition(String s) {
dfs(s,0);
return res;
}
void dfs(String s,int start){
if(start == s.length()){
res.add(new ArrayList<String>(path));
}
for(int i = start; i < s.length();i++){
String str = s.substring(start,i + 1);
if(!f(str)){
continue;
}
path.add(str);
dfs(s,i + 1);
path.remove(path.size() - 1);
}
}
boolean f(String s){
if(s.length() == 1){
return true;
}
int l = 0,r = s.length() - 1;
while(l < r){
if(s.charAt(l) != s.charAt(r)){
return false;
}
l++;
r--;
}
return true;
}
}
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋
class Solution {
List<List<String>> res = new ArrayList<>();
char[][] board;
public List<List<String>> solveNQueens(int n) {
board = new char[n][n];
for(char[] row : board){
Arrays.fill(row,'.');
}
dfs(n,0);
return res;
}
void dfs(int n,int row){
if(row == n){
res.add(build(board));
return;
}
for(int i = 0; i < n;i++){
if(valid(board,row,i)){
board[row][i] = 'Q';
dfs(n,row + 1);
board[row][i] = '.';
}
}
}
boolean valid(char[][] board,int row,int col){
int n = board[0].length;
//同列
for(int i = 0; i < row;i++){
if(board[i][col] == 'Q') return false;
}
//左上
for(int i = row - 1, j = col - 1; i >= 0 && j >= 0;i--,j--){
if(board[i][j] == 'Q') return false;
}
//右上
for(int i = row - 1, j = col + 1; i >= 0 && j < n;i--,j++){
if(board[i][j] == 'Q') return false;
}
return true;
}
List<String> build(char[][] board){
List<String> list = new ArrayList<>();
for(int i = 0; i < board.length;i++){
String s = new String(board[i]);
list.add(s);
}
return list;
}
}
最简
class Solution {
List<List<String>> res = new ArrayList<>();
int n;
int[] queen;
public List<List<String>> solveNQueens(int n) {
this.n = n;
queen = new int[n];
Arrays.fill(queen,-1);
dfs(0);
return res;
}
void dfs(int row){
if(row == n){
res.add(build());
return;
}
for(int i = 0; i < n;i++){
if(valid(row,i)){
queen[row] = i;
dfs(row + 1);
queen[row] = -1;
}
}
}
boolean valid(int row, int col){
for(int r = 0; r < row;r++){
int c = queen[r];
//同列,同对角线
if(c == col || Math.abs(r - row) == Math.abs(c - col)){
return false;
}
}
return true;
}
List<String> build(){
List<String> board = new ArrayList<>();
for(int i = 0; i < n;i++){
char[] chs = new char[n];
Arrays.fill(chs,'.');
chs[queen[i]] = 'Q';
board.add(new String(chs));
}
return board;
}
}
35. 搜索插入位置 - 力扣(LeetCode)
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 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
从图中可以得知,闭区间的二分查找最后r和l分别在mid的左侧和右侧
left = 第一个 大于等于 target 的位置
right = 最后一个 小于 target 的位置
class Solution {
public int searchInsert(int[] nums, int target) {
int l = 0,r = nums.length - 1;
while(l <= r){
int mid = (l + r) >>> 1;
if(nums[mid] == target){
return mid;
}else if(nums[mid] > target){
r = mid - 1;
}else{
l = mid + 1;
}
}
return l;
}
}
11.4
74. 搜索二维矩阵 - 力扣(LeetCode)
给你一个满足下述两条属性的 m x n 整数矩阵:
每行中的整数从左到右按非严格递增顺序排列。
每行的第一个整数大于前一行的最后一个整数。
给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false 。
示例 1:
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出:true简单做法:调用API
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
for (int i = 0; i < matrix.length; i++) {
int t = Arrays.binarySearch(matrix[i], target);
if(t >= 0 ){
return true;
}
}
return false;
}
}
二分
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
for (int i = 0; i < matrix.length; i++) {
int l = 0,r = matrix[i].length - 1;
while(l <=r){
int mid = (l + r) >>> 1;
if(matrix[i][mid] == target){
return true;
}else if(matrix[i][mid] < target){
l = mid + 1;
}else{
r = mid - 1;
}
}
}
return false;
}
}
34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
给你一个按照非递减顺序排列的整数数组 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]
先二分找到一个target位置,然后再向左右寻找
class Solution {
public int[] searchRange(int[] nums, int target) {
int l = 0, r = nums.length - 1;
int t = -1;
while (l <= r) {
int mid = (l + r) >>> 1;
if (nums[mid] == target) {
t = mid;
break;
} else if (nums[mid] < target) {
l = mid + 1;
} else {
r = mid - 1;
}
}
if (t == -1) {
return new int[] { -1, -1 };
}
l = t;
r = t;
while (r < nums.length - 1 && nums[r + 1] == nums[t])
r++;
while (l > 0 && nums[l - 1] == nums[t])
l--;
return new int[] { l, r };
}
}
整数数组 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) 的算法解决此问题。
nlog n的做法
class Solution {
public int search(int[] nums, int target) {
HashMap<Integer,Integer> map = new HashMap<>();
int[] t = Arrays.copyOfRange(nums,0,nums.length);
Arrays.sort(t);
for(int i = 0; i < nums.length;i++){
map.put(nums[i],i);
}
int k = map.get(t[0]);
int l = 0, r = t.length - 1,m = -1;
while(l <= r){
int mid = (l + r) >>> 1;
if(t[mid] == target){
m = mid;
break;
}else if(t[mid] > target){
r = mid - 1;
}else{
l = mid + 1;
}
}
if(m == -1){
return - 1;
}
return (m + k) % nums.length;
}
}
log n的做法
class Solution {
public int search(int[] nums, int target) {
int l = 0, r = nums.length - 1;
while(l <= r){
int mid = (l + r) >>> 1;
if(nums[mid] == target){
return mid;
}
//右边有序
if(nums[mid] < nums[r]){
//target在有序侧
if(target > nums[mid] && target <= nums[r]){
//向有序侧靠近
l = mid + 1;
}else{
//target在无序侧,
r = mid - 1;
}
}else{
//左部分有序
if(target < nums[mid] && target >= nums[l]){
r = mid - 1;
}else{
l = mid + 1;
}
}
}
return -1;
}
}
11.5
⭐153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)
已知一个长度为 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) 的算法解决此问题。
class Solution {
public int findMin(int[] nums) {
int left = 0, right = nums.length - 1;
while(left < right){
int mid = (left + right) >>> 1;
if(nums[mid] > nums[right]){
// 最小值在右半部分
left = mid + 1;
} else {
// 最小值在左半部分,包括 mid
right = mid;
}
}
return nums[left];
}
}
误打误撞写的不知道正确不正确,能通过
class Solution {
public int findMin(int[] nums) {
int min = Integer.MAX_VALUE;
int left = 0,right = nums.length - 1;
while(left <= right){
int mid = (left + right) >>> 1;
if(nums[mid] < min){
min = nums[mid];
}else if(nums[mid] < nums[right]){
right = mid - 1;
}else{
left = mid + 1;
}
}
return min;
}
}
4. 寻找两个正序数组的中位数 - 力扣(LeetCode)
给定两个大小分别为 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[] a, int[] b) {
if(a.length > b.length){
int[] temp = a;
a = b;
b = temp;
}
int m = a.length;
int n = b.length;
int left = -1;
int right = m;
while(left + 1 < right){
int i = (left + right) >>> 1;
int j = (m + n + 1) / 2 - i - 2;
if(a[i] <= b[j + 1]){
left = i;
}else{
right = i;
}
}
int i = left;
int j = (m + n + 1) /2 - i - 2;
int ai = i >= 0 ? a[i] : Integer.MIN_VALUE;
int bj = j >= 0 ? b[j] : Integer.MIN_VALUE;
int ai1 = i + 1 < m ? a[i + 1] : Integer.MAX_VALUE;
int bj1 = j + 1 < n ? b[j + 1] : Integer.MAX_VALUE;
int max1 = Math.max(ai, bj);
int min2 = Math.min(ai1, bj1);
return (m + n) % 2 > 0 ? max1 : (max1 + min2) / 2.0;
}
}
20. 有效的括号 - 力扣(LeetCode)
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for(int i = 0; i < s.length();i++){
if(!stack.isEmpty() && s.charAt(i) == get(stack.peek())){
stack.pop();
}else{
stack.push(s.charAt(i));
}
}
return stack.isEmpty();
}
char get(char c){
if(c == '('){
return ')';
}else if(c == '{'){
return '}';
}else if(c == '['){
return ']';
}else{
return '*';
}
}
}
可读性最高的版本
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '{' || c == '[') {
stack.push(c);
} else if (!stack.isEmpty() && c == get(stack.peek())) {
stack.pop();
} else {
return false; // 遇到右括号不匹配
}
}
return stack.isEmpty();
}
char get(char c) {
if (c == '(') return ')';
if (c == '{') return '}';
if (c == '[') return ']';
return '?'; // 永远不会用到
}
}
11.6
739. 每日温度 - 力扣(LeetCode)
给定一个整数数组 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]
class Solution {
public int[] dailyTemperatures(int[] t) {
int[] res = new int[t.length];
Stack<Integer> stack = new Stack<>();
for(int i = 0; i < t.length;i++){
while(!stack.isEmpty() && t[i] > t[stack.peek()]){
int pre = stack.pop();
res[pre] = i - pre;
}
stack.push(i);
}
return res;
}
}
155. 最小栈 - 力扣(LeetCode)
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。
最先想到用两个栈,但是实现这块没有实现出来,先写出来个nlog(n)的
class MinStack {
Stack<Integer> stack;
List<Integer> list;
public MinStack() {
stack = new Stack<>();
list = new ArrayList();
}
public void push(int val) {
stack.push(val);
list.add(val);
Collections.sort(list);
}
public void pop() {
int t = stack.pop();
int idx = 0;
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == t) {
idx = i;
break;
}
}
list.remove(idx);
}
public int top() {
return stack.peek();
}
public int getMin() {
return list.get(0);
}
}
双栈
class MinStack {
Deque<Integer> stack;
Deque<Integer> minStack;
public MinStack() {
stack = new ArrayDeque();
minStack = new ArrayDeque();
minStack.push(Integer.MAX_VALUE);
}
public void push(int val) {
stack.push(val);
minStack.push(Math.min(minStack.peek(),val));
}
public void pop() {
stack.pop();
minStack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
测试用例保证输出的长度不会超过 10(5)。
示例 1:
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
示例 2:
输入:s = "3[a2[c]]"
输出:"accaccacc"
示例 3:
输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"
递归
class Solution {
public String decodeString(String s) {
return decode(s.toCharArray());
}
int i = 0;
String decode(char[] s){
StringBuilder res = new StringBuilder();
int k = 0;
while(i < s.length){
char c = s[i];
i++;
if(Character.isLetter(c)){
res.append(c);
}else if(Character.isDigit(c)){
k = k * 10 + (c - '0');
}else if(c == '['){
String t = decode(s);
res.repeat(t,k);
k = 0;
}else{
break;
}
}
return res.toString();
}
}
栈总结
双栈法、单调栈
11.7
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:
输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10
示例 2:
输入: heights = [2,4]
输出: 4
栈
class Solution {
public int largestRectangleArea(int[] heights) {
Deque<Integer> stack = new ArrayDeque<>();
int max = 0, n = heights.length;
for (int i = 0; i <= n; i++) {
int h = (i == n ? 0 : heights[i]); // 在末尾加一个0,保证栈清空
while (!stack.isEmpty() && heights[stack.peek()] > h) {
int height = heights[stack.pop()];
int width = stack.isEmpty() ? i : i - stack.peek() - 1;
max = Math.max(max, height * width);
}
stack.push(i);
}
return max;
}
}
347. 前 K 个高频元素 - 力扣(LeetCode)
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入:nums = [1,1,1,2,2,3], k = 2
输出:[1,2]
示例 2:
输入:nums = [1], k = 1
输出:[1]
示例 3:
输入:nums = [1,2,1,2,1,2,3,1,3,2], k = 2
输出:[1,2]
堆
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> b[1] - a[1]);
for(Map.Entry<Integer,Integer> entry : map.entrySet()){
pq.offer(new int[]{entry.getKey(),entry.getValue()});
}
int[] res = new int[k];
for (int i = 0; i < k; i++) {
res[i] = pq.poll()[0];
}
return res;
}
}
215. 数组中的第K个最大元素 - 力扣(LeetCode)
给定整数数组 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
小顶堆
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>();
for(int i = 0; i < nums.length;i++){
pq.offer(nums[i]);
if(pq.size() > k){
pq.poll();
}
}
return pq.peek();
}
}
大顶堆
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>((a,b)->b-a);
for(int i = 0; i < nums.length;i++){
pq.offer(nums[i]);
}
for(int i = 0; i < k-1;i++){
pq.poll();
}
return pq.peek();
}
}
11.8
中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
例如 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
二分插入
class MedianFinder {
List<Integer> list;
public MedianFinder() {
list = new ArrayList<>();
}
public void addNum(int num) {
int index = Collections.binarySearch(list, num);
if (index < 0) index = -index - 1; // 二分插入点
list.add(index, num); // 保持升序
}
public double findMedian() {
int n = list.size();
if ((n & 1) != 0) {
return list.get(n / 2);
} else {
return (list.get(n / 2 - 1) + list.get(n / 2)) / 2.0;
}
}
}
双堆,保持两个堆的大小相差不超过1
class MedianFinder {
PriorityQueue<Integer> small; // 大顶堆,存较小的一半
PriorityQueue<Integer> large; // 小顶堆,存较大的一半
public MedianFinder() {
small = new PriorityQueue<>((a, b) -> b - a);
large = new PriorityQueue<>();
}
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();
}
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. 买卖股票的最佳时机 - 力扣(LeetCode)
class Solution {
public int maxProfit(int[] prices) {
int min = Integer.MAX_VALUE;
int res = 0;
for(int i = 0; i < prices.length;i++){
min = Math.min(prices[i],min);
res = Math.max(prices[i] - min,res);
}
return res;
}
}
55. 跳跃游戏 - 力扣(LeetCode)
给你一个非负整数数组 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) {
int farthest = 0;
for(int i = 0; i < nums.length - 1;i++){
farthest = Math.max(farthest,i + nums[i]);
//nums[i]是0,被卡住脚
if(farthest <= i){
return false;
}
}
return true;
}
}
从后向前
class Solution {
public boolean canJump(int[] nums) {
int last = nums.length - 1;
for(int i = last;i >= 0;i--){
if(i + nums[i] >= last){
last = i;
}
}
return last == 0;
}
}
堆总结
双堆
11.9
给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。例如,字符串 "ababcc" 能够被分为 ["abab", "cc"],但类似 ["aba", "bcc"] 或 ["ab", "ab", "cc"] 的划分是非法的。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。
返回一个表示每个字符串片段的长度的列表。
示例 1:
输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。示例 2:
输入:s = "eccbbbbdec"
输出:[10]
class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> partitionLabels(String s) {
int[] last = new int[26];
for(int i = 0; i < s.length();i++){
char c = s.charAt(i);
last[c - 'a'] = i;
}
int start = 0, end = 0;
for(int i = 0; i < s.length();i++){
end = Math.max(end,last[s.charAt(i) - 'a']);
if(end == i){
res.add(end - start + 1);
start = i + 1;
}
}
return res;
}
}
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 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 。偷第i个和不偷第i个
class Solution {
public int rob(int[] nums) {
int n = nums.length;
if(n == 1){
return nums[0];
}
int[] dp = new int[n];
dp[0] = nums[0];
dp[1] = Math.max(nums[0],nums[1]);
for(int i = 2; i < n;i++){
dp[i] = Math.max(dp[i- 1],nums[i] + dp[i-2]);
}
return dp[n - 1];
}
}
70. 爬楼梯 - 力扣(LeetCode)
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶
- 1 阶 + 2 阶
- 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. 杨辉三角 - 力扣(LeetCode)
给定一个非负整数 _numRows,_生成「杨辉三角」的前 numRows 行。
在**「杨辉三角」**中,每个数是它左上方和右上方的数的和。
示例 1:
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2:
输入: numRows = 1
输出: [[1]]提示:
1 <= numRows <= 30
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < numRows; i++) {
List<Integer> row = new ArrayList<>();
for (int j = 0; j <= i; j++) {
if (j == 0 || j == i) {
row.add(1);
} else {
row.add(res.get(i - 1).get(j - 1) + res.get(i - 1).get(j));
}
}
res.add(row);
}
return res;
}
}
贪心总结
11.10
动态规划=递归+记忆化
给你一个整数 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 {
int[] memo;
public int numSquares(int n) {
memo = new int[n + 1];
Arrays.fill(memo,-666);
return dp(n);
}
int dp(int n) {
if (n == 0) {
return 0;
}
if( n < 0){
return -1;
}
if(memo[n] != -666){
return memo[n];
}
int res = Integer.MAX_VALUE;
for (int i = 1; i * i<= n; i++) {
int t = dp(n - i * i);
if(t == -1){
continue;
}
res = Math.min(res,t + 1);
if(res == 1) break;
}
memo[n] = (res == Integer.MAX_VALUE) ? 0 : res;
return memo[n];
}
}
给你一个整数数组 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 {
int[] memo;
public int coinChange(int[] coins, int amount) {
memo = new int[amount + 1];
Arrays.fill(memo,-666);
return dp(coins,amount);
}
int dp(int[] coins,int amount){
if(amount == 0){
return 0;
}
if(amount < 0){
return - 1;
}
if(memo[amount] != -666){
return memo[amount];
}
int res = Integer.MAX_VALUE;
for(int coin : coins){
int t = dp(coins,amount - coin);
if(t == -1){
continue;
}
res = Math.min(res,t + 1);
}
memo[amount] = (res == Integer.MAX_VALUE) ? -1 : res;
return memo[amount];
}
}
⭐139. 单词拆分 - 力扣(LeetCode)
给你一个字符串 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 {
int[] memo;
public boolean wordBreak(String s, List<String> wordDict) {
memo = new int[s.length()];
Arrays.fill(memo, -666);
return dfs(s, wordDict, 0);
}
boolean dfs(String s, List<String> wordDict, int idx) {
if (idx == s.length()) {
return true;
}
if (idx > s.length()) {
return false;
}
if (memo[idx] != -666) {
return memo[idx] == 1;
}
for (String word : wordDict) {
int size = word.length();
if (idx + size > s.length()) {
continue;
}
String str = s.substring(idx, idx + size);
if (word.equals(str)) {
boolean flag = dfs(s, wordDict, idx + size);
if (flag) {
memo[idx] = 1;
return true;
}
}
}
memo[idx] = 0;
return false;
}
}
11.11
给你一个整数数组 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
完全背包:选或者不选这个物品,并且物品可以无限使用
完全背包有两种写法,一种是外层遍历物品内层遍历体积,另一种是外层遍历体积,内层遍历物品。更推荐写第一种,第二种会多次遍历同一个物品
⭐完全背包的j是正序遍历的,0-1背包j是逆序遍历的
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp,amount + 1);
dp[0] = 0;
for(int i = 0; i < coins.length;i++){
for(int j = coins[i]; j <= amount;j++){
dp[j] = Math.min(dp[j],dp[j - coins[i]] + 1);
}
}
return dp[amount] == amount + 1? -1 : dp[amount];
}
}
0-1背包写法伪代码
for (物品 i)
for (容量 j 从最大到 w[i] 递减)
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
j是逆序的,从sum开始到nums[i]
dp[j]含义:是否可以从前若干个数中选出一些,使它们的和恰好等于 j。
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for(int num : nums){
sum += num;
}
if((sum & 1) == 1){
return false;
}
sum >>= 1;
boolean[] dp = new boolean[sum + 1];
dp[0] = true;
for(int i = 0; i < nums.length;i++){
for(int j = sum; j >= nums[i];j--){
dp[j] = dp[j] || dp[j - nums[i]];
}
}
return dp[sum];
}
}
给你一个字符串 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
提示:
1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s 和 wordDict[i] 仅由小写英文字母组成
wordDict 中的所有字符串 互不相同
⚠先物品再背包这道题会出错
你把 wordDict 放在外层、j(位置)放在内层:
for (String word : wordDict) {
for (int j = len; j <= n; j++) {
if (dp[j - len] && s.substring(j - len, j).equals(word)) dp[j] = true;
}
}
问题是循环顺序会丢失“先用某个单词把 dp[x] 设为 true,再用另一个单词基于这个 dp[x] 推导更远位置”的机会。举个最小反例:
s = "abccc",wordDict = ["ccc", "ab"]。
如果先处理 "ccc",此时 dp[2] 仍是 false,不会把 dp[5] 置 true;随后处理 "ab" 把 dp[2] 设为 true,但已经没有机会再去处理 "ccc",最终 dp[5] 仍为 false(错误)。
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
int n = s.length();
boolean[] dp = new boolean[n + 1];
dp[0] = true;
for (int i = 1; i <= n; i++) {
for (String word : wordDict) {
int len = word.length();
if (i >= len && dp[i - len] && s.substring(i - len, i).equals(word)) {
dp[i] = true;
}
}
}
return dp[n];
}
}
给你一个整数 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, n + 1);
dp[0] = 0;
for (int i = 1; i * i <= n; i++) {
int square = i * i;
for (int j = square; j <= n; j++) {
dp[j] = Math.min(dp[j], dp[j - square] + 1);
}
}
return dp[n];
}
}
完全背包总结
完全背包:选或者不选这个物品,并且物品可以无限使用
完全背包有两种写法,一种是外层遍历物品内层遍历体积,另一种是外层遍历体积,内层遍历物品。更推荐写第一种,第二种会多次遍历同一个物品
⭐完全背包的j是正序遍历的,0-1背包j是逆序遍历的
⭐字符串DP问题一般长度都设置为n+1,为了方便表示空字符串
⭐完全背包是同一个物品连续选择,然后就再也不用选这个物品了,而139. 单词拆分 - 力扣(LeetCode)这道题是交替选择,比如S是ABA类型,完全背包只能枚举AAB、ABB这样的连续子串,不能枚举ABA
11.12
416. 分割等和子集 - 力扣(LeetCode)
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。j是逆序的,从sum开始到nums[i]
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for(int num : nums){
sum += num;
}
if((sum & 1) == 1){
return false;
}
sum >>= 1;
boolean[] dp = new boolean[sum + 1];
dp[0] = true;
for(int i = 0; i < nums.length;i++){
for(int j = sum; j >= nums[i];j--){
dp[j] = dp[j] || dp[j - nums[i]];
}
}
return dp[sum];
}
}
300. 最长递增子序列 - 力扣(LeetCode)
给你一个整数数组 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) {
int n = nums.length;
int[] dp = new int[n];
Arrays.fill(dp,1);
int res = 1;
for(int i = 1; i < n;i++){
for(int j = 0; j < i;j++){
if(nums[j] < nums[i]){
dp[i] = Math.max(dp[j] + 1,dp[i]);
}
res = Math.max(dp[i],res);
}
}
return res;
}
}
给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续 子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
请注意,一个只包含一个元素的数组的乘积是这个元素的值。
示例 1:
输入: nums = [2,3,-2,4] 输出: 6解释: 子数组 [2,3] 有最大乘积 6。示例 2:
输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
提示:
1 <= nums.length <= 2 * 10(4)
-10 <= nums[i] <= 10
nums 的任何子数组的乘积都 保证 是一个 32-位 整数
最大值可能是正数×正数,也可能是负数×负数,所以记录最大和最小值
class Solution {
public int maxProduct(int[] nums) {
int n = nums.length;
int[] min = new int[n];
int[] max = new int[n];
min[0] = max[0] = nums[0];
int res = nums[0];
for (int i = 1; i < n; i++) {
max[i] = Math.max(Math.max(max[i - 1] * nums[i], min[i - 1] * nums[i]),nums[i]);
min[i] = Math.min(Math.min(min[i - 1] * nums[i], max[i - 1] * nums[i]),nums[i]);
res = Math.max(res,max[i]);
}
return res;
}
}
11.13
给你一个字符串 s,找到 s 中最长的 回文 子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
s.charAt(l) == s.charAt(r) && (r - l <= 2 || dp[l + 1][r - 1]),长度小于3的左右是相同就不用管中间的或者中间是回文,左右相等,也是回文子串
class Solution {
public String longestPalindrome(String s) {
int len = s.length();
int maxStart = 0;
int maxEnd = 0;
int maxLen = 1;
boolean[][] dp = new boolean[len][len];
for(int r = 1;r < len;r++){
for(int l = 0; l < r;l++){
if(s.charAt(l) == s.charAt(r) && (r - l <=2 || dp[l + 1][r - 1])){
dp[l][r] = true;
if(r - l + 1 > maxLen){
maxEnd = r;
maxStart = l;
maxLen = r - l + 1;
}
}
}
}
return s.substring(maxStart,maxEnd + 1);
}
}
中心扩散法
class Solution {
public String longestPalindrome(String s) {
String res = "";
for(int i = 0; i < s.length();i++){
String s1 = f(s,i,i);
String s2 = f(s,i,i+1);
res = res.length() > s1.length() ? res : s1;
res = res.length() > s2.length() ? res : s2;
}
return res;
}
String f(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);
}
}
72. 编辑距离 - 力扣(LeetCode)
给你两个单词 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')
1143. 最长公共子序列 - 力扣(LeetCode)
给你一个字符串 s,找到 s 中最长的 回文 子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。示例 2:
输入:s = "cbbd"
输出:"bb"提示:
1 <= s.length <= 1000
s 仅由数字和英文字母组成
11.14
技巧题
136. 只出现一次的数字 - 力扣(LeetCode)
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
示例 1 :
输入:nums = [2,2,1]
输出:1
示例 2 :
输入:nums = [4,1,2,1,2]
输出:4
示例 3 :
输入:nums = [1]
输出:1
a ^ a = 0;
0 ^ a = a;
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for(int num : nums){
res = res ^ num;
}
return res;
}
}
31. 下一个排列 - 力扣(LeetCode)
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
例如,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]提示:
1 <= nums.length <= 100
0 <= nums[i] <= 100
287. 寻找重复数 - 力扣(LeetCode)
给定一个包含 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示例 3 :
输入:nums = [3,3,3,3,3]
输出:3提示:
1 <= n <= 10(5)
nums.length == n + 1
1 <= nums[i] <= n
nums 中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次
进阶:
如何证明 nums 中至少存在一个重复的数字?
你可以设计一个线性级时间复杂度 O(n) 的解决方案吗?
75. 颜色分类 - 力扣(LeetCode)
给定一个包含红色、白色和蓝色、共 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]提示:
n == nums.length
1 <= n <= 300
nums[i] 为 0、1 或 2
进阶:
你能想出一个仅使用常数空间的一趟扫描算法吗?
169. 多数元素 - 力扣(LeetCode)
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入:nums = [3,2,3]
输出:3示例 2:
输入:nums = [2,2,1,1,1,2,2]
输出:2提示:
n == nums.length
1 <= n <= 5 * 10(4)
-10(9) <= nums[i] <= 10(9)
输入保证数组中一定有一个多数元素。
**进阶:**尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
146. LRU 缓存 - 力扣(LeetCode)
请你设计并实现一个满足 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提示:
1 <= capacity <= 3000
0 <= key <= 10000
0 <= value <= 10(5)
最多调用 2 * 10(5) 次 get 和 put
LinkedHashMap链接起来,put和get是O(1),并且还保持顺序
实现思路:
put:先判断key是否存在,若存在直接替换就行若不存在cache.size()>=cap,true就先删除链表头部的节点,再将key节点放进cache
get:先判断key是否存在,不存在返回-1,存在将key设置为最新的,并返回value
class LRUCache {
int cap;
LinkedHashMap<Integer, Integer> cache;
public LRUCache(int capacity) {
cache = new LinkedHashMap<>();
this.cap = capacity;
}
public int get(int key) {
if(cache.get(key) == null){
return -1;
}
makeRecently(key);
return cache.get(key);
}
public void put(int key, int value) {
if (cache.containsKey(key)) {
cache.put(key, value);
makeRecently(key);
return;
}
if (cache.size() >= cap) {
int oldKey = cache.keySet().iterator().next();
cache.remove(oldKey);
}
cache.put(key, value);
}
private void makeRecently(int key) {
int val = cache.get(key);
cache.remove(key);
cache.put(key, val);
}
}
208. 实现 Trie (前缀树) - 力扣(LeetCode)
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每一层都是一个26叉树cur.end == true就说明有这个路径的单词
class Trie {
class Node{
boolean end = false;
Node[] son = new Node[26];
}
Node root;
public Trie() {
root = new Node();
}
public void insert(String word) {
Node cur = root;
for(char c : word.toCharArray()){
int index = c - 'a';
if(cur.son[index] == null){
cur.son[index] = new Node();
}
cur = cur.son[index];
}
cur.end = true;
}
public boolean search(String word) {
return find(word) == 2;
}
public boolean startsWith(String prefix) {
return find(prefix) != 0;
}
int find(String word){
Node cur = root;
for(char c : word.toCharArray()){
int index = c - 'a';
if(cur.son[index] == null){
return 0;
}
cur = cur.son[index];
}
return cur.end ? 2 : 1;
}
}
11.14号完结,Hot100历时30天完成第一遍
数学相关算法
数学相关算法
快速幂乘法(高效计算大数次幂)
long pow(long a, long b) {
long res = 1; // 使用long而不是int
while (b > 0) {
if ((b & 1) == 1) { // 判断最低位是否为1
res = res * a; // 正确的乘法方式
}
a = a * a; // 平方底数
b >>= 1; // 右移一位
}
return res; // 添加返回语句
}
求整数数位的值
int a(num){
num %= 10;
num/=10;
}
逆转整数
public int reverse(int num){
int res = 0;
while(num > 0){
res = res * 10 + num % 10;
num /= 10;
}
return res;
}
取模有负数
//给定一个整数数组 nums 和一个整数 k ,返回其中元素之和可被 k 整除的非空 子数组 的数目。
//子数组 是数组中 连续 的部分。
public static int subarraysDivByK(int[] nums, int k) {
int ans = 0;
Map<Integer,Integer> cnt = new HashMap<>();
int[] s = new int[nums.length + 1];
for (int i = 0; i < nums.length; i++) {
s[i+1] = s[i] + nums[i];
}
for(int i = 0; i < s.length;i++){
ans += cnt.getOrDefault((s[i] % k + k) % k,0);
cnt.merge((s[i] % k + k) % k,1,Integer::sum);
}
return ans;
}
/*
s[i] % k:计算当前前缀和对 k 的模。
如果 s[i] 是正数,结果可能是 [0, k-1]。
如果 s[i] 是负数,结果可能是 [-(k-1), -1]
*/
素数(又称素数,指在大于1的自然数中,除了和该数自身外,无法被其他自然数整除的数)
boolean prime(int num){
if(num < 2){
return false;
}
for(int i =2; i * i<= num;i++){
if(num % i == 0){
return false;
}
}
return true;
}
欧几里得算法(最大公约数)
int gcd(int a,int b){
return b== 0 ? a : gcd(b,a % b);
}
最小公倍数
int lcm(int a, int b){
return a / gcd(a, b) * b; // 先除后乘,避免溢出
}
质数筛(埃氏筛)
将质数的倍数都筛选掉
class Solution {
public int countPrimes(int n) {
boolean[] isPrime = new boolean[n];
// 将数组都初始化为 true
Arrays.fill(isPrime, true);
for (int i = 2; i < n; i++) {
if (isPrime[i]) {
// i 的倍数不可能是素数了
for (int j = 2 * i; j < n; j += i) {
isPrime[j] = false;
}
}
}
int count = 0;
for (int i = 2; i < n; i++) {
if (isPrime[i]) count++;
}
return count;
}
}
最高效
class Solution {
public int countPrimes(int n) {
boolean[] isPrime = new boolean[n];
Arrays.fill(isPrime, true);
for (int i = 2; i * i < n; i++)
if (isPrime[i])
for (int j = i * i; j < n; j += i)
isPrime[j] = false;
int count = 0;
for (int i = 2; i < n; i++)
if (isPrime[i]) count++;
return count;
}
}
判断正数是否是2的幂
n & (n - 1)消除最后一个1,2的幂的二进制数有且只有一个1
class Solution {
public boolean isPowerOfTwo(int n) {
if (n <= 0) return false;
return (n & (n - 1)) == 0;
}
}
