LeetCode Hot 100
LeetCode Hot 100 · Java 题解
mindmap
root((LeetCode Hot100 分类图1))
哈希
查找与分组
1 两数之和
核心 哈希表找补数
49 字母异位词分组
核心 排序后做key或计数做key
128 最长连续序列
核心 哈希集合判定序列起点
双指针
原地移动
283 移动零
核心 快慢指针原地覆盖
对撞指针
11 盛最多水的容器
核心 每次移动短板
15 三数之和
核心 排序加左右夹逼去重
42 接雨水
核心 双指针维护左右最大值
滑动窗口
不定长窗口
3 无重复字符的最长子串
核心 map记录字符位置收缩左边界
76 最小覆盖子串
核心 满足覆盖后尽量缩小窗口
定长窗口
438 找到字符串中所有字母异位词
核心 定长滑窗维护词频差
239 滑动窗口最大值
核心 单调队列维护窗口最大值
普通数组
子数组与区间
53 最大子数组和
核心 Kadane维护当前位置最优
56 合并区间
核心 排序后线性合并
原地变换
189 轮转数组
核心 三次翻转
238 除自身以外数组的乘积
核心 前缀积后缀积
41 缺失的第一个正数
核心 原地哈希值归位
前缀和
560 和为K的子数组
核心 前缀和加哈希计数
矩阵
标记与模拟
73 矩阵置零
核心 首行首列做标记
54 螺旋矩阵
核心 四边界模拟
48 旋转图像
核心 转置后每行翻转
查找
240 搜索二维矩阵II
核心 右上角逐步排除
链表
基础操作
160 相交链表
核心 双指针对齐长度
206 反转链表
核心 三指针原地反转
234 回文链表
核心 找中点反转后半比较
环问题
141 环形链表
核心 快慢指针判环
142 环形链表II
核心 相遇后同步找入口
合并与删除
21 合并两个有序链表
核心 归并思想
2 两数相加
核心 链表模拟竖式加法
19 删除链表的倒数第N个结点
核心 快指针先走N步
24 两两交换链表中的节点
核心 成对交换指针
25 K个一组翻转链表
核心 分组反转
复制与排序
138 随机链表的复制
核心 哈希映射或穿插复制
148 排序链表
核心 链表归并排序
23 合并K个升序链表
核心 分治或小根堆
设计题
146 LRU缓存
核心 哈希表加双向链表
栈
匹配与设计
20 有效的括号
核心 栈匹配括号
155 最小栈
核心 辅助栈维护最小值
单调栈与表达式
394 字符串解码
核心 栈保存倍数和前缀串
739 每日温度
核心 单调递减栈找下一个更大
84 柱状图中最大的矩形
核心 单调栈求左右边界
堆
TopK
215 数组中的第K个最大元素
核心 小根堆或快速选择
347 前K个高频元素
核心 词频哈希加堆
数据流
295 数据流的中位数
核心 大根堆小根堆平衡
二分查找
基础边界
35 搜索插入位置
核心 lower_bound模板
34 在排序数组中查找元素的第一个和最后一个位置
核心 两次二分找边界
变形数组
74 搜索二维矩阵
核心 映射成一维二分
33 搜索旋转排序数组
核心 判断哪半边有序
153 寻找旋转排序数组中的最小值
核心 最小值落在无序侧
经典难题
4 寻找两个正序数组的中位数
核心 二分切分数组
贪心
局部最优
121 买卖股票的最佳时机
核心 维护最低价
55 跳跃游戏
核心 维护最远可达位置
45 跳跃游戏II
核心 分层贪心跳步数
763 划分字母区间
核心 维护当前段最远边界
技巧
位运算与投票
136 只出现一次的数字
核心 异或抵消
169 多数元素
核心 摩尔投票
原地操作
75 颜色分类
核心 荷兰国旗三指针
31 下一个排列
核心 找下降点交换反转后缀
映射成环
287 寻找重复数
核心 数组映射链表找环入口
mindmap
root((LeetCode Hot100 分类图2))
二叉树
遍历基础
94 二叉树的中序遍历
核心 递归或栈模拟中序
102 二叉树的层序遍历
核心 BFS按层遍历
结构性质
104 二叉树的最大深度
核心 后序求左右深度
226 翻转二叉树
核心 递归交换左右子树
101 对称二叉树
核心 镜像递归比较
543 二叉树的直径
核心 左右深度和更新答案
BST专题
108 将有序数组转换为二叉搜索树
核心 中点建根递归构造
98 验证二叉搜索树
核心 中序递增或上下界判断
230 二叉搜索树中第K小的元素
核心 中序第K个
视图与变形
199 二叉树的右视图
核心 每层最后一个节点
114 二叉树展开为链表
核心 前序重连右指针
构造与路径
105 从前序与中序遍历序列构造二叉树
核心 前序定根中序分治
437 路径总和III
核心 前缀和统计路径
236 二叉树的最近公共祖先
核心 左右子树递归回报
124 二叉树中的最大路径和
核心 单边贡献加全局更新
图论
网格搜索
200 岛屿数量
核心 DFS或BFS淹没岛屿
994 腐烂的橘子
核心 多源BFS分层扩散
拓扑与前缀树
207 课程表
核心 拓扑排序判环
208 实现Trie前缀树
核心 children数组加结束标记
回溯
全排列与子集
46 全排列
核心 used数组或交换法
78 子集
核心 选或不选
组合搜索
17 电话号码的字母组合
核心 逐位DFS拼接
39 组合总和
核心 可重复选的回溯搜索
22 括号生成
核心 左右括号数量剪枝
网格与分割
79 单词搜索
核心 DFS加visited回溯
131 分割回文串
核心 回文预判加切割
棋盘搜索
51 N皇后
核心 列与对角线剪枝
动态规划
基础递推
70 爬楼梯
核心 Fibonacci型递推
118 杨辉三角
核心 由上一行推出下一行
线性DP
198 打家劫舍
核心 取或不取转移
279 完全平方数
核心 完全背包最少数量
322 零钱兑换
核心 完全背包最少硬币
139 单词拆分
核心 前缀可达性DP
300 最长递增子序列
核心 O n平方DP或牌堆二分
152 乘积最大子数组
核心 同时维护最大积最小积
416 分割等和子集
核心 01背包凑一半
括号DP
32 最长有效括号
核心 以i结尾的最长合法长度
多维动态规划
网格DP
62 不同路径
核心 上方左方转移
64 最小路径和
核心 取最小代价路径
字符串DP
5 最长回文子串
核心 中心扩展或区间DP
1143 最长公共子序列
核心 二维DP对齐字符
72 编辑距离
核心 插入删除替换三种转移
哈希
1. 两数之和
代码块收起展开
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int need = target - nums[i];
if (map.containsKey(need)) return new int[]{map.get(need), i};
map.put(nums[i], i);
}
return new int[0];
}
}2. 字母异位词分组
代码块收起展开
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String s : strs) {
char[] arr = s.toCharArray();
Arrays.sort(arr);
String key = new String(arr);
map.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
}
return new ArrayList<>(map.values());
}
}Map ( sorted —> all ) map.computeIfAbsent(k, xxx) --->返回val map.values();
1
你好
3. 最长连续序列
代码块收起展开
class Solution {
int ans=0;
public int longestConsecutive(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int x : nums) set.add(x);
for (int x : set) {
if (set.contains(x - 1)) continue;
int len=1;
while(set.contains(++x)) len++;
ans = Math.max(ans, len);
}
return ans;
}
}Set
双指针
4. 移动零
代码块收起展开
class Solution {
public void moveZeroes(int[] nums) {
int pos=0;
for(int x:nums){
if(x!=0) nums[pos++]=x;
}
//注意这个api是左闭右开
Arrays.fill(nums,pos,nums.length,0);
}
}5. 盛最多水的容器
代码块收起展开
class Solution {
public int maxArea(int[] height) {
int l = 0, r = height.length - 1, ans = 0;
while (l < r) {
ans = Math.max(ans, (r - l) * Math.min(height[l], height[r]));
if (height[l] < height[r]) l++;
else r--;
}
return ans;
}
}从两头开始, 一直遍历, max, 哪头低就移动, 不会放过答案
6. 三数之和
代码块收起展开
class Solution {
List<List<Integer>> ans=new ArrayList<>();
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
for(int i=0;i<nums.length-2;i++){
//去重
if(i>=1&&nums[i]==nums[i-1]) continue;
int l=i+1, r=nums.length-1;
while(l<r){
int sum=nums[i]+nums[l]+nums[r];
if(sum>0) r--;
else if(sum<0) l++;
else {
ans.add(Arrays.asList(nums[i],nums[l],nums[r]));
while(l<r&&nums[l]==nums[l+1]) l++;
while(l<r&&nums[r-1]==nums[r]) r--;
l++;
// 1 2 2 2 3 4 5 跳到3
r--;
//到相等的最后一个, 然后跳到不同的下一个
}
}
}
return ans;
}
}遍历左断点 ,
然后双指针剩下的数组, 大了就移动右边, 下来了就移动左边, 一旦等于, 就添加, 同时为了避免重复, 还要skip l,r相等的位置, l, r都要
7. 接雨水
代码块收起展开
class Solution {
public int trap(int[] height) {
int l = 0, r = height.length - 1;
int lMax = 0, rMax = 0, ans = 0;
while (l < r) {
lMax = Math.max(lMax, height[l]);
rMax = Math.max(rMax, height[r]);
if (lMax < rMax) ans += lMax - height[l++];
else ans += rMax - height[r--];
}
return ans;
}
}
3 2 5
lmax=3,rmax=5, ans+=lmax-nums[l++]=3-3;
lmax=3,rmax=5, ans+=lmax-nums[l++]=3-2;
左右双指针, 先更新max, 然后哪边有倾斜就 ans+=哪边, 该格子能取多少水, 取决于最矮的一端, 木桶效应!
滑动窗口 / 子串
8. 无重复字符的最长子串
代码块收起展开
class Solution {
public int lengthOfLongestSubstring(String s) {
int[] cnt = new int[128];
int l = 0, ans = 0;
for (int r = 0; r < s.length(); r++) {
cnt[s.charAt(r)]++;
while (cnt[s.charAt(r)] > 1) cnt[s.charAt(l++)]--;
ans = Math.max(ans, r - l + 1);
/*注意别写成
while(++cnt[s.charAt(r)]>1){
cnt[s.charAt(l++)]--;
}
因为while每次都会判断, 每次都给++cnt(r)
*/
}
return ans;
}
}遍历右端点, 发现右端点进来后重复, 就一直T出左端点, 然后更新ans
9. 找到字符串中所有字母异位词
代码块收起展开
class Solution {
List<Integer> ans = new ArrayList<>();
public List<Integer> findAnagrams(String s, String p) {
if (s.length() < p.length()) return ans;
int[] need = new int[26], win = new int[26];
//先搞出标准数组
for (char c : p.toCharArray()) need[c - 'a']++;
int plen = p.length();
for (int i = 0; i < s.length(); i++) {
win[s.charAt(i) - 'a']++;
//超出了, 先踢出左侧, 在i>=plen时伊始
if (i >= plen) win[s.charAt(i - m) - 'a']--;
if (i >= plen - 1 && Arrays.equals(need, win)) ans.add(i - m + 1);
}
return ans;
}
}在长串s 中找出p串的异位词, 直接比较肯定不好, 我们用p做出一个标准数组, 通过比较标准数组来搞 Arrays.equals(a,b) 依旧右端点一直进来, 发现长度大于plen了, 就一直t出左边, 如果发现符合要求, 就ans.add(), 这里是起点
10. 和为 K 的子数组
代码块收起展开
class Solution {
int ans=0;
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
//空前缀出现一次
//别用前缀和数组, 下标麻烦
/* presum[i] 表示 nums 前 i 个元素之和
int[] presum = new int[n + 1];
for (int i = 1; i <= n; i++) {
presum[i] = presum[i - 1] + nums[i - 1];
}*/
int sum = 0;
for (int x : nums) {
sum += x;
ans += map.getOrDefault(sum - k, 0);
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
return ans;
}
}sum一直加, 然后ans+=map, 最后再补充map的 presum->cnt
11. 滑动窗口最大值
代码块收起展开
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int pos=0;
int[] ans=new int[nums.length-k+1];
Deque<Integer> deq=new ArrayDeque<>();
for(int i=0;i<nums.length;i++){
//弹出窗口外的
while(!deq.isEmpty()&&deq.peekFirst()<=i-k) deq.pollFirst();
//将队列按从大到小排
while(!deq.isEmpty()&&nums[i]>=nums[deq.peekLast()]) deq.pollLast();
//加入当前
deq.addLast(i);
if(i>=k-1){
ans[pos++]=nums[deq.peekFirst()];
//注意ans装的是数, 非位
}
}
return ans;
}
}用双端队列, 保存以固定长度 && end为i的数组的从大到小的排序, (由于要排除out of window的下标, 所以要记录下标) 然后 i 走到固定长度时就开始统计
12. 最小覆盖子串
代码块收起展开
class Solution {
public String minWindow(String s, String t) {
if(s.length()<t.length()) return "";
//统计目标串的词频, 用负数表示
int[] cnt=new int[256];
for(char c:t.toCharArray()) cnt[c]--;
int l=0,start=0,debt=t.length(),minlen=Integer.MAX_VALUE;
for(int r=0;r<s.length();r++){
//如果++后<=0, 说明是目标, 则--debt
if(++cnt[s.charAt(r)]<=0) debt--;
while(debt==0){
if(r-l+1<=minlen){
start=l;
minlen=r-l+1;
}
//如果--后<0, 说明是目标, 因为不是目标串应该--后>=0
if(--cnt[s.charAt(l++)]<0) debt++;
}
}
return minlen==Integer.MAX_VALUE?"":s.substring(start,start+minlen);
}
}搞一个debt和cnt[], 目标串的搞成负的, 然后遍历右边不停进窗口++, 如果debt==0, 那么我们就t出去
普通数组
13. 最大子数组和
代码块收起展开
class Solution {
public int maxSubArray(int[] nums) {
int cur = nums[0], ans = nums[0];
//防止出现[ -1 ]的情况, 所以不直接设0
for (int i = 1; i < nums.length; i++) {
cur = Math.max(nums[i], cur + nums[i]);
ans = Math.max(ans, cur);
}
return ans;
}
}一路遍历, cur++, 如果发现+x不如直接x开头, 就换车头
14. 合并区间
代码块收起展开
class Solution {
List<int[]> ans=new ArrayList<>();
public int[][] merge(int[][] intervals) {
//按第一个数从小到大排
Arrays.sort(intervals,(a,b)->a[0]-b[0]);
for(int[] cur:intervals){
//当前答案是空or两个数组尾首不相接
if(ans.isEmpty()||ans.get(ans.size()-1)[1]<cur[0]){
ans.add(cur);
}else ans.get(ans.size()-1)[1]=Math.max(ans.get(ans.size()-1)[1],cur[1]);
}
return ans.toArray(new int[0][]);
//注意这个写法!
}
}先按起点排序, 遍历所有数组 如果ans空, 则直接放进去 否则, 拿出second和当前first比较, 比他小, 那么直接加入 比他大就把second改为 Math.max(second,cur[1]) return ans.toArray(new int[0][])
15. 轮转数组
代码块收起展开
class Solution {
//非常厉害的思维
public void rotate(int[] nums, int k) {
//k可能大于长度nums.length;
k%=nums.length;
reverse(nums,0,nums.length-1);
reverse(nums,0,k-1);
reverse(nums,k,nums.length-1);
}
public void reverse(int[] nums,int l,int r){
while(l<r){
int tmp=nums[l];
nums[l++]=nums[r];
nums[r--]=tmp;
}
}
}右移动k位置 等价于 反转数组 反转前k位 反转后k位
16. 除自身以外数组的乘积
代码块收起展开
class Solution {
public int[] productExceptSelf(int[] nums) {
int len=nums.length;
int[] pre=new int[len]; pre[0]=1;
for(int i=1;i<len;i++){
pre[i]=pre[i-1]*nums[i-1];
}
//自己写一下前缀和数组和后缀和数组, 即知道累积规则
int suffix=1;
for(int i=len-1;i>=0;i--){
pre[i]*=suffix;
suffix*=nums[i];
}
return pre;
}
}除去自己的数组乘积, 也就是自己的 前缀积*后缀积
17. 缺失的第一个正数
nums[nums[i]-1]!=nums[i] 原地矫正整数
代码块收起展开
class Solution {
public int firstMissingPositive(int[] nums) {
int len=nums.length;
for(int i=0;i<len;i++){
//发现属于[1~n]的数并且(pos,val)!=(i-1,nums[i])
while(1<=nums[i]&&nums[i]<=len&&nums[nums[i]-1]!=nums[i]){
int tmp=nums[nums[i]-1];
nums[nums[i]-1]=nums[i];
nums[i]=tmp;
}
}
//再遍历一遍(pos,val)!=(i-1,nums[i])
for(int i=0;i<len;i++){
if(nums[i]!=i+1){
return i+1;
}
}
return len+1;
//所有学生都在对的位置, [1,2]->3
}
}遍历每一个数, 发现是负数或者 nums[i]=i-1, 就是符合的 否则一直和那个位置的swap, 有点像幂等性校验了
矩阵
18. 矩阵置零
用行列数组标记再置零
代码块收起展开
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
boolean row0 = false, col0 = false;
for (int j = 0; j < n; j++) if (matrix[0][j] == 0) row0 = true;
for (int i = 0; i < m; i++) if (matrix[i][0] == 0) col0 = true;
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][j] == 0) {
matrix[i][0] = 0;
matrix[0][j] = 0;
}
}
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][0] == 0 || matrix[0][j] == 0) matrix[i][j] = 0;
}
}
if (row0) Arrays.fill(matrix[0], 0);
if (col0) for (int i = 0; i < m; i++) matrix[i][0] = 0;
}
}两个zero的boolean[], 遍历一遍matrix后, 通过zero[]来判断行列是否要复制
19. 螺旋矩阵
四个flag, for循环
代码块收起展开
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> ans = new ArrayList<>();
int l=0,r=matrix[0].length-1;
int top=0,bot=matrix.length-1;
while(l<=r&&top<=bot){
for(int i=l;i<=r;i++){
ans.add(matrix[top][i]);
}
top++;
for(int i=top;i<=bot;i++){
ans.add(matrix[i][r]);
}
r--;
//检查边界, 如果这个不符合, 下一个也肯定符合
//否则下一轮while循环结束, 也即到达最中心一点
if(top<=bot){
for(int i=r;i>=l;i--){
ans.add(matrix[bot][i]);
}
bot--;
}
if(l<=r){
for(int i=bot;i>=top;i--){
ans.add(matrix[i][l]);
}
l++;
}
}
return ans;
}
}20. 旋转图像
顺时针90° 等价于 对角线交换+左右翻转
代码块收起展开
class Solution {
public void rotate(int[][] matrix) {
//本题本质就是对角线交换+左右反转
int rows=matrix.length, cols=matrix[0].length;
for(int i=0;i<rows;i++){
//注意这里只需要到对角线就行了
for(int j=0;j<i;j++){
if(i==j) continue;
int tmp = matrix[i][j];
matrix[i][j]=matrix[j][i];
matrix[j][i]=tmp;
}
}
for(int[] row:matrix){
int l=0,r=cols-1;
while(l<r){
int tmp=row[l];
row[l++]=row[r];
row[r--]=tmp;
}
}
}
}21. 搜索二维矩阵 II
代码块收起展开
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int i = 0, j = matrix[0].length - 1;
while (i < matrix.length && j >= 0) {
if (matrix[i][j] == target) return true;
if (matrix[i][j] > target) j--;
else i++;
}
return false;
}
}链表
22. 相交链表
哈希表 O(n)
代码块收起展开
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode a = headA, b = headB;
while (a != b) {
//注意这里是等于null才变向
a = (a == null) ? headB : a.next;
b = (b == null) ? headA : b.next;
}
return a;
//or b
}
}
a:x
b:y
公共:z
a: x z y z
b: y z x z23. 反转链表
代码块收起展开
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre=null,cur=head;
while(cur!=null){
//先保住下一个, 再转向
ListNode nxt=cur.next;
cur.next=pre;
pre=cur;
cur=nxt;
}
return pre;
}
}24. 回文链表
代码块收起展开
class Solution {
public boolean isPalindrome(ListNode head) {
ListNode slow=head,fast=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
//反转前半段到slow正好
ListNode pre=null;
while(head!=slow){
ListNode nxt=head.next;
head.next=pre;
pre=head;
head=nxt;
}
slow= fast==null?slow:slow.next;
while(slow!=null){
if(slow.val!=pre.val) return false;
//注意别忘记走
slow=slow.next;
pre=pre.next;
}
return true;
}
}快慢指针到中点, 然后反转前半段, 通过fast判断是奇长度还是偶长度, 然后==判断
25. 环形链表
代码块收起展开
public class Solution {
public boolean hasCycle(ListNode head) {
//如果有环必定相遇的!!!!!!!!!!!!!!!!!!!1
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
}如果有环, 那么走两步走一步的点必定相遇, 否则必定走到null
27. 环形链表II
代码块收起展开
public class Solution {
public ListNode detectCycle(ListNode head) {
//slow=b=(kc-a), fast=2b
//fast-slow=kc--> b=kc-a-->差a到kc完整的圆!
//head到入环口也是a, 所以while(slow!=head)
ListNode fast=head, slow=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
//有环必相遇
if(fast==slow){
//相遇则此时head和slow齐步走, 必定到环口
while(slow!=head){
slow=slow.next;
head=head.next;
}
return slow;
}
}
return null;
}
}两倍速有环必相遇, 相遇head和slow齐走
26. 合并两个有序链表
代码块收起展开
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummy=new ListNode(0),cur=dummy;
ListNode p1=list1,p2=list2;
while(p1!=null && p2!=null){
if(p1.val<=p2.val){
cur.next=p1;
p1=p1.next;
}else {
cur.next=p2;
p2=p2.next;
}
cur=cur.next;
}
cur.next= p1==null?p2:p1;
return dummy.next;
}
}27. 两数相加
代码块收起展开
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummy=new ListNode(0), cur=dummy;
int carry=0,sum=0;
while(l1!=null||l2!=null||carry!=0){
//这里主要要加上上一位的进位!!!!!!!!!
sum=getVal(l1)+getVal(l2)+carry;
carry=sum/10;
cur.next=new ListNode(sum%10);
cur=cur.next;
l1=l1==null?null:l1.next;
l2=l2==null?null:l2.next;
}
return dummy.next;
}
public int getVal(ListNode p){
return p==null?0:p.val;
}
}sum=v1+v2+carry
28. 删除链表的倒数第 N 个结点
代码块收起展开
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode fast = dummy, slow = dummy;
for (int i = 0; i < n; i++) fast = fast.next;
//注意这里是刚好走到最后一个节点, fast!=null则是走到最后一个节点的下一个
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}倒数第k个不好控制, 但是找第k个却可以, 到第k个然后一个从头一个从k走
29. 两两交换链表中的节点
代码块收起展开
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy=new ListNode(0,head);
ListNode node0=dummy, node1=head;
while(node1!=null&&node1.next!=null){
ListNode node2=node1.next;
ListNode node3=node2.next;
//改签
node0.next=node2;
node2.next=node1;
node1.next=node3;
//更新好下一轮
node0=node1;
node1=node3;
}
return dummy.next;
}
}30. K 个一组翻转链表
代码块收起展开
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
//计算总长度, 好控制循环次数
int len=0;
ListNode cur=head;
while(cur!=null){ cur=cur.next; len++;}
ListNode dummy=new ListNode(0,head);
ListNode p0=dummy, pre=null;
cur=head;
while(len>=k){
for(int i=0;i<k;i++){
ListNode nxt=cur.next;
cur.next=pre;
pre=cur;
cur=nxt;
}
//标记链表的尾巴
ListNode nxt=p0.next;
//连接链表的首
p0.next=pre;
//尾连下一条首
nxt.next=cur;
//继续下一轮
p0=nxt;
len-=k;
}
return dummy.next;
}
}画个交接图理解
31. 随机链表的复制
代码块收起展开
class Solution {
public Node copyRandomList(Node head) {
Map<Node,Node> map = new HashMap<>();
Node cur=head;
while(cur!=null){
map.put(cur,new Node(cur.val));
cur=cur.next;
}
cur=head;
while(cur!=null){
Node bro=map.get(cur);
//注意这里指向的依旧是bro链表的
bro.next=map.get(cur.next);
bro.random=map.get(cur.random);
cur=cur.next;
}
return map.get(head);
}
}直接赋值大法, 完全的copy, 你做啥我做啥
32. 排序链表
代码块收起展开
class Solution {
public ListNode sortList(ListNode head) {
if(head==null||head.next==null) return head;
ListNode slow=head,fast=head.next;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
ListNode mid = slow.next;
slow.next=null;
return merge(sortList(head),sortList(mid));
//这句是核心! 一直递归,
}
//合并链表
public ListNode merge(ListNode a,ListNode b){
ListNode dummy=new ListNode(0), cur=dummy;
while(a!=null&&b!=null){
if(a.val<=b.val){
cur.next=a;
a=a.next;
}else {
cur.next=b;
b=b.next;
}
cur=cur.next;
}
cur.next= a==null?b:a;
return dummy.next;
}
}排序两个链表 + 递归
33. LRU缓存
代码块收起展开
class LRUCache {
class Node{
int key,val;
Node pre,next;
public Node(int key,int val){
this.key=key;
this.val=val;
}
}
public void insert(Node cur){
cur.pre=head; cur.next=head.next;
head.next=cur; cur.next.pre=cur;
}
public void remove(Node cur){
cur.pre.next=cur.next;
cur.next.pre=cur.pre;
}
int capacity;
Node head,tail;
Map<Integer,Node>map=new HashMap<>();
public LRUCache(int capacity) {
head=new Node(0,0);
tail=new Node(0,0);
head.next=tail;
tail.pre=head;
this.capacity=capacity;
}
public int get(int key) {
Node cur=map.get(key);
if(cur==null) return -1;
remove(cur);
insert(cur);
return cur.val;
}
public void put(int key, int value) {
Node cur=map.get(key);
if(cur!=null){
cur.val=value;
remove(cur);
insert(cur);
return;
}
cur=new Node(key,value);
insert(cur);
map.put(key,cur);
if(map.size()>capacity){
Node lru=tail.pre;
remove(lru);
map.remove(lru.key);
}
}
}34. 合并K个有序链表
代码块收起展开
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
ListNode dummy=new ListNode(0), cur=dummy;
PriorityQueue<ListNode> minq=new PriorityQueue<>((a,b)->a.val-b.val);
for(ListNode head:lists){
//注意offer进去的不能为null
if(head!=null) minq.offer(head);
}
while(!minq.isEmpty()){
ListNode out=minq.poll();
if(out.next!=null) minq.offer(out.next);
cur.next=out;
cur=cur.next;
cur.next=null;
}
return dummy.next;
}
}小顶堆
二叉树
35. 二叉树的中序遍历
代码块收起展开
class Solution {
List<Integer> ans = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
Stack<TreeNode> stk = new Stack<>();
TreeNode cur=root;
//中序一直往左走, 直到尽头, 放出val再往右
while(!stk.isEmpty()||cur!=null){
while(cur!=null){
stk.push(cur);
cur=cur.left;
}
cur=stk.pop();
ans.add(cur.val);
cur=cur.right;
}
return ans;
}
}中序遍历就是左中右, 所以永远走到最左边, 然后加中间, 再走右边
36. 二叉树的最大深度
代码块收起展开
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
}最大深度= max(l,r)+1
37. 翻转二叉树
代码块收起展开
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
//先反转
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
//交叉获得各自反转的结果
root.left = right;
root.right = left;
return root;
}
}先反转左右子树, 再交换左右节点
38. 对称二叉树
代码块收起展开
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null) return true; //防止后面空指针
return isSame(root.left,root.right);
}
public boolean isSame(TreeNode l, TreeNode r){
if(l==null&&r==null) return true; //都空
if(l==null||r==null) return false;//亦或空
if(l.val!=r.val) return false;//不等
//注意这里是交叉的, 真正的镜像
return isSame(l.left,r.right)&&isSame(r.left,l.right);
}
}卫语句
39. 二叉树的直径
代码块收起展开
class Solution {
int ans=0;
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
return ans;
}
public int dfs(TreeNode root){
if(root==null) return 0; //null返回一, 否则走下一个return +1
int l=dfs(root.left);
int r=dfs(root.right);
ans=Math.max(ans,r+l);//两条链拼接
return Math.max(l,r)+1;//选择最长的链+1, 但他实际上始终是单链
}
}40. 二叉树的层序遍历
代码块收起展开
class Solution {
List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> levelOrder(TreeNode root) {
if(root==null) return ans;
Deque<TreeNode> dq=new ArrayDeque<>();
dq.offer(root);
while(!dq.isEmpty()){
int size=dq.size();
List<Integer> tmp=new ArrayList<>();
for(int i=0;i<size;i++){
TreeNode cur=dq.poll();
tmp.add(cur.val);
if(cur.left!=null) dq.addLast(cur.left);
if(cur.right!=null) dq.addLast(cur.right);
}
ans.add(tmp);
}
return ans;
}
}41. 将有序数组转换为二叉搜索树
代码块收起展开
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return dfs(nums,0,nums.length-1); //[]闭区间
}
public TreeNode dfs(int[] nums,int l,int r){
if(l>r) return null;
int mid=l+(r-l)/2;
//mid是root, 所以左右不接触
TreeNode ls=dfs(nums,l,mid-1);
TreeNode rs=dfs(nums,mid+1,r);
return new TreeNode(nums[mid],ls,rs);
}
}42. 验证二叉搜索树
代码块收起展开
class Solution {
long pre = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if(root==null) return true;
if(!isValidBST(root.left)) return false;
if(root.val<=pre) return false;
pre=root.val;
return isValidBST(root.right);
}
}43. 二叉搜索树中第 K 小的元素
代码块收起展开
class Solution {
public int kthSmallest(TreeNode root, int k) {
Deque<TreeNode> dq=new ArrayDeque<>();
while(true){
while(root!=null){
//注意这里是栈的api, 只有这样后进才能先出, 找到第k小!
dq.push(root);
root=root.left;
}
root=dq.pop();
if(--k==0) return root.val;
root=root.right;
}
}
}44. 二叉树的右视图
代码块收起展开
class Solution {
List<Integer> ans=new ArrayList<>();
public List<Integer> rightSideView(TreeNode root) {
Deque<TreeNode> dq=new ArrayDeque<>();
if(root==null) return ans;
dq.offer(root);
while(!dq.isEmpty()){
int size=dq.size();
for(int i=0;i<size;i++){
TreeNode cur=dq.poll();
if(cur.left!=null) dq.offer(cur.left);
if(cur.right!=null) dq.offer(cur.right);
//每层最后一个就是右视图点!!!!!!!!!!!!!
if(i==size-1) ans.add(cur.val);
}
}
return ans;
}
}层序遍历的最后一个就是右视图
45. 二叉树展开为链表
代码块收起展开
class Solution {
public void flatten(TreeNode root) {
TreeNode cur=root;
while(cur!=null){
//思想就是把根的右边街道左节点的右尽头, 然后继续往右走
if(cur.left!=null){
TreeNode pre=cur.left;
while(pre.right!=null) pre=pre.right;
pre.right=cur.right;
cur.right=cur.left;
cur.left=null;
}
cur=cur.right;
}
}
}走到root.left然后一路向右, 接着把root.right放到尽头, root.right=root.left, root.left=null
46. 从前序与中序遍历序列构造二叉树
代码块收起展开
class Solution {
//val->index, 便于锁定在中序遍历中根节点的位置
HashMap<Integer,Integer> inpos=new HashMap<>();
int[] pre;
public TreeNode buildTree(int[] preorder, int[] inorder) {
pre=preorder;
for(int i=0;i<inorder.length;i++) inpos.put(inorder[i],i);
//注意[]
return build(0,preorder.length-1,0,inorder.length-1);
}
public TreeNode build(int prel,int prer,int inl,int inr){
if(prel>prer) return null;
int rootVal=pre[prel], rootInPos=inpos.get(rootVal);
TreeNode root = new TreeNode(rootVal);
int leftsize=rootInPos-inl;
//注意看是前序的左子树在何处, 右子树在何处, 在对应里面找边界!
root.left=build(prel+1,prel+leftsize,inl,rootInPos-1);
root.right=build(prel+leftsize+1,prer,rootInPos+1,inr);
return root;
}
}47. 路径总和 III
代码块收起展开
class Solution {
int ans = 0;
Map<Long, Integer> map = new HashMap<>();
public int pathSum(TreeNode root, int targetSum) {
map.put(0L, 1);
dfs(root, 0L, targetSum);
return ans;
}
//1
// / \
// 2 3
// / \
// 4 5 ,比如到了4, 要回退到2并且去访问右5, 那肯定要删掉当前4
void dfs(TreeNode root, long sum, int target) {
if (root == null) return;
sum += root.val;
ans += map.getOrDefault(sum - target, 0);
map.merge(sum,1,Integer::sum); //map.put(sum, map.getOrDefault(sum, 0) + 1);
dfs(root.left, sum, target);
dfs(root.right, sum, target);
map.merge(sum,-1,Integer::sum);
}
}树上前缀和+Map+回溯
48. 二叉树的最近公共祖先
代码块收起展开
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//先确定递归边界去找p/q, 没找到返回null
if(root==null||root==p||root==q) return root;
//递归找p,q, 看看左右子树有没有
TreeNode l=lowestCommonAncestor(root.left,p,q);
TreeNode r=lowestCommonAncestor(root.right,p,q);
if(l==null) return r; //左边是null, 答案在右
if(r==null) return l; //右边是null, 答案在左
//左右都不是null, 说明横跨左右子树, 那只能是root了
return root;
}
}二叉树中的最大路径和
代码块收起展开
class Solution {
int ans=Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return ans;
}
//递归找到最长的边
public int dfs(TreeNode root){
if(root==null) return 0;
int l=dfs(root.left), r=dfs(root.right);
ans=Math.max(ans,l+r+root.val);
int cur=Math.max(l,r)+root.val;
return Math.max(0,cur); //注意节点可能是负的, 那么干脆不要!
}
}图论
49. 岛屿数量
代码块收起展开
class Solution {
//1是陆地
int rows,cols;
int ans=0;
public int numIslands(char[][] grid) {
rows=grid.length;
cols=grid[0].length;
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++){
if(grid[i][j]=='1'){
dfs(grid,i,j);
ans++;
}
}
}
return ans;
}
public void dfs(char[][] grid,int i,int j){
if(i<0||i>=rows||j<0||j>=cols||grid[i][j]!='1') return;
grid[i][j]='0';
dfs(grid,i,j+1);
dfs(grid,i,j-1);
dfs(grid,i+1,j);
dfs(grid,i-1,j);
}
}50. 腐烂的橘子
代码块收起展开
class Solution {
int ans=0;
public static int[][] dirs={{-1,0},{0,1},{1,0},{0,-1}};
public int orangesRotting(int[][] grid) {
int rows=grid.length, cols=grid[0].length, fresh=0;
List<int[]> q=new ArrayList<>();
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++){
if(grid[i][j]==1) fresh++;
else if(grid[i][j]==2) q.add(new int[]{i,j});
}
}
while(fresh>0&&!q.isEmpty()){
ans++;
List<int[]> tmp=q;
q=new ArrayList<>();
for(int[] cur:tmp){
for(int[] d:dirs){
int x=cur[0]+d[0];
int y=cur[1]+d[1];
if(x<0||y<0||x>=rows||y>=cols||grid[x][y]!=1) continue;
grid[x][y]=2; fresh--;
q.add(new int[]{x,y});
}
}
}
return fresh>0?-1:ans;
}
}51. 课程表
代码块收起展开
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<Integer>[] graph = new ArrayList[numCourses];
for(int i=0;i<numCourses;i++) graph[i]=new ArrayList<>();
//建立邻接表, 便于t出0度节点的后继
int[] indegree=new int[numCourses];
for(int[] side:prerequisites){
int ex=side[1], in=side[0];
graph[ex].add(in);
indegree[in]++;
}
Queue<Integer> que = new LinkedList<>();
for(int i=0;i<numCourses;i++){
if(indegree[i]==0) que.offer(i);
}
int cnt=0;
while(!que.isEmpty()){
int u=que.poll();
cnt++; //T出一节课, cnt++, 最后检验t出的是否等于nc
for(int v:graph[u]){
if(--indegree[v]==0) que.offer(v);
}
}
return cnt==numCourses;
}
}建邻接表, 建入度数组, 拓扑排序, 然后入度为0者先进队列, 遍历邻接表
52. 实现 Trie
代码块收起展开
class Trie {
class node{
node[] next=new node[26];
boolean isEnd;
}
node root=new node();
public void insert(String word) {
node cur=root;
for(char ch : word.toCharArray()){
int idx=ch-'a';
if(cur.next[idx]==null) cur.next[idx]=new node();
cur=cur.next[idx];
}
cur.isEnd=true;
}
public boolean search(String word) {
node cur=root;
for(char ch:word.toCharArray()){
int idx=ch-'a';
if(cur.next[idx]==null) return false;
cur=cur.next[idx];
}
return cur.isEnd;
}
public boolean startsWith(String prefix) {
node cur=root;
for(char ch : prefix.toCharArray()){
int idx=ch-'a';
if(cur.next[idx]==null) return false;
cur=cur.next[idx];
}
return true;
}
}回溯
57. 全排列
代码块收起展开
class Solution {
int len;
List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
len=nums.length;
List<Integer> path = new ArrayList<>();
for(int num:nums) path.add(num);
dfs(path,0);
return ans;
}
public void dfs(List<Integer> path, int start){
if(start==len){
ans.add(new ArrayList<>(path)); //这里直接放path会用引用导致元素全都一样??
return;
}
for(int i=start;i<len;i++){
Collections.swap(path,start,i);
dfs(path,start+1);
Collections.swap(path,start,i);
}
}
}代码块收起展开
Collections.swap(path,start,i);
代码块收起展开
dfs(path,start+1);
Collections.swap(path,start,i);58. 子集
代码块收起展开
class Solution {
List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
int len=nums.length, limit= 1<<len;
//遍历每种情况
for(int i=0;i<limit;i++){
List<Integer> tmp = new ArrayList<>();
for(int j=0;j<len;j++){
if(((1<<j)&i)!=0) tmp.add(nums[j]);
}
ans.add(tmp);
}
return ans;
}
}59. 电话号码的字母组合
代码块收起展开
class Solution {
private int len = 0;
private List<String> ans = new ArrayList<>();
private static final String[] MAPPING =
new String[]{"", "", "abc", "def", "ghi",
"jkl", "mno", "pqrs", "tuv", "wxyz"};
public List<String> letterCombinations(String digits) {
len=digits.length();
char[] nums = digits.toCharArray();
dfs(0,new char[len],nums);
return ans;
}
public void dfs(int i,char[] path,char[] nums){
if(i==len){
ans.add(new String(path));
return;
}
char[] letters=MAPPING[nums[i]-'0'].toCharArray();
for(char ch:letters){
path[i]=ch;
dfs(i+1,path,nums); //这里为啥直接选 因为是枚举
}
}
}建立数字 -> String[]的映射 然后dfs(idx, path, nums) 此处的nums是我们的拨号 然后for循环遍历nums[i]对应的String
60. 组合总和
代码块收起展开
class Solution {
int tar, len;
List<List<Integer>> ans = new ArrayList<>();
int[] cands;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
tar=target; cands=candidates; len=candidates.length;
dfs(0,0,new ArrayList<>());
return ans;
}
public void dfs(int i,int sum,List<Integer> path){
if(sum==tar){
//注意这里不能用path, 不然会一直引用同一份path, 由于最后一句一直是removeLast, 所以会导致没有[] [] []
ans.add(new ArrayList<>(path));
return;
}
//剪枝
if (sum > tar || i == len) {
return;
}
//这里可选可不选
for(int j=i;j<len;j++){
path.add(cands[j]);
dfs(j,sum+cands[j],path); //注意题目可以重复选当前
path.removeLast(); //不选当前
}
}
}ans.add(new ArrayList<>(path)); 注意for循环本身就在横向选举了!!!!!!!!!!!!!!
61. 括号生成
代码块收起展开
class Solution {
List<String> ans = new ArrayList<>();
public List<String> generateParenthesis(int n) {
dfs(0,0,n,new char[n*2]);
return ans;
}
public void dfs(int l,int r,int limit,char[] path){
if(r==limit){
ans.add(new String(path));
return;
}
//左边还没到顶, 则试试
if(l<limit){
path[l+r]='(';
dfs(l+1,r,limit,path);
}
//左边比右边多了, 那必须出手
if(l>r){
path[l+r]=')';
dfs(l,r+1,limit,path);
}
}
}成对出现, 约束1: L < limit && R < limit 约束2: L < R
62. 单词搜索
代码块收起展开
class Solution {
public static final int[] dirs ={0,1,0,-1,0};
int rows,cols, len;
char[][] mat; char[] aim;
public boolean exist(char[][] board, String word) {
rows=board.length; cols=board[0].length; len=word.length();
mat=board; aim=word.toCharArray();
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++){
if(dfs(i,j,0)) return true;
}
}
return false;
}
public boolean dfs(int x,int y,int i){
if(i==len||mat[x][y]!=aim[i]) return false;
if(i==len-1) return true;
mat[x][y]='0'; //3
for(int j=0;j<4;j++){
int nx=x+dirs[j];
int ny=y+dirs[j+1];
if(nx>=0&&nx<rows&&ny>=0&&ny<cols&&dfs(nx,ny,i+1)) return true;
}
mat[x][y]=aim[i]; //3 放重复
return false;
}
}1.不要用path==aim这种鬼判断方式 最好就 if(mat[x][y]!=aim[i]) return false; if(i==len-1) return true; //如果对上了且是最后一个就天然true 2.for(四个方向) if(不越界&&dfs(nx,ny,cur+1)) return true; 3.要注意一定要在for前后标记当前, 不然会重复走
63. 分割回文串
代码块收起展开
class Solution {
String ss;
List<List<String>> ans=new ArrayList<>();
public List<List<String>> partition(String s) {
ss=s;
dfs(0,new ArrayList<>());
return ans;
}
public void dfs(int l,List<String> path){
if(l==ss.length()){
ans.add(new ArrayList<>(path)); return;
}
for(int r=l;r<ss.length();r++){
if(isPal(l,r)){
//注意这里是左闭右开
path.add(ss.substring(l,r+1)); //以当前结束
dfs(r+1,path);
path.remove(path.size()-1);//不以当前结束, 继续看下一位
}
}
}
public boolean isPal(int l,int r){
while(l<r){
if(ss.charAt(l++)!=ss.charAt(r--)) return false;
}
return true;
}
}dfs L端, for枚举R端, 发现pal就选当前子串 dfs(r+1) / 不选当前
64. N 皇后
代码块收起展开
class Solution {
List<List<String>> ans = new ArrayList<>();
boolean[] colF, l, r;
int cols; int[] qpos;
public List<List<String>> solveNQueens(int n) {
cols=n; colF=new boolean[n]; qpos=new int[n];
l = new boolean[2*n-1]; r=new boolean[2*n-1];
dfs(0);
return ans;
}
public void dfs(int currow){
if(currow==cols){
List<String> one = new ArrayList<>(cols);
for(int col:qpos){
char[] row=new char[cols];
Arrays.fill(row,'.');
row[col]='Q';
one.add(new String(row));
}
ans.add(one);
return;
}
for(int i=0;i<cols;i++){
int lval = currow-i+cols-1;
int rval = currow+i;
if(colF[i]==false&&l[lval]==false&&r[rval]==false){
qpos[currow]=i;
colF[i]=true;
l[lval]=true;
r[rval]=true;
dfs(currow+1);
colF[i]=false;
l[lval]=false;
r[rval]=false;
}
}
}
}colF[] L[x-y+n-1] = 2*n-1 R[x+y] = 2*n-1 qpos[] = col ---> for
二分查找
65. 搜索插入位置
代码块收起展开
class Solution {
int ans;
public int searchInsert(int[] nums, int target) {
int l=0,r=nums.length-1;
while(l<=r){
int mid=l+(r-l)/2;
if(nums[mid]==target){
return mid;
}else if(nums[mid]<target){
l=mid+1;
}else r=mid-1;
}
return l;
}
}66. 搜索二维矩阵
代码块收起展开
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int rows=matrix.length, cols=matrix[0].length;
int l=0, r=rows*cols-1;
while(l<=r){
int mid=l+(r-l)/2;
int val=matrix[mid/cols][mid%cols];
if(val==target) return true;
else if(val<target) l=mid+1;
else r=mid-1;
}
return false;
}
}67. 在排序数组中查找元素的第一个和最后一个位置
代码块收起展开
class Solution {
public int[] searchRange(int[] nums, int target) {
int start = lowerBound(nums, target);
//target太大-> l=len, target太小返回第一个>=target的
if (start == nums.length || nums[start] != target) {
return new int[]{-1, -1}; // nums 中没有 target
}
// 如果 start 存在,那么 end 必定存在
int end = lowerBound(nums, target + 1) - 1;
return new int[]{start, end};
}
// lowerBound 返回最小的满足 nums[i] >= target 的下标 i 即L
// 如果数组为空,或者所有数都 < target,则返回 nums.length
// 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]
private int lowerBound(int[] nums, int target) {
int left = 0;
int right = nums.length - 1; // 闭区间 [left, right]
while (left <= right) { // 区间不为空
// 循环不变量:
// nums[left-1] < target
// nums[right+1] >= target
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1; // 范围缩小到 [mid+1, right]
} else {
right = mid - 1; // 范围缩小到 [left, mid-1]
}
}
// 循环结束后 left = right+1
// 此时 nums[left-1] < target 而 nums[left] = nums[right+1] >= target
// 所以 left 就是第一个 >= target 的元素下标
return left;
}
}求 <=target的最后一个数, 即求第一个 >target的数, 也即求 >=target+1的数 如果不存在 -> L=len 不存在target, 太大->l=len, 太小 -> 返回第一个>=target的位置
68. 搜索旋转排序数组
代码块收起展开
class Solution {
public int search(int[] nums, int target) {
int l = 0, r = nums.length - 1;
while (l <= r) {
int m = l + (r - l) / 2;
if (nums[m] == target) return m;
if (nums[l] <= nums[m]) {
//左边是有序的
if (nums[l] <= target && target < nums[m]) r = m - 1;
//如果在左边, 则收紧有边界
else l = m + 1;
//否则跳过此处
} else {
//右边是有序的
if (nums[m] < target && target <= nums[r]) l = m + 1;
//如果在右边, 则收紧右边界
else r = m - 1;
}
}
return -1;
}
}69. 寻找旋转排序数组中的最小值
代码块收起展开
class Solution {
public int findMin(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int m = l + (r - l) / 2;
// 3 4 5 1 2 mid->5, min=l+1
if (nums[m] > nums[r]) l = m + 1;
else r = m;
}
return nums[l];
}
}70. 寻找两个正序数组的中位数
代码块收起展开
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len1=nums1.length, len2=nums2.length;
if(len1>len2) return findMedianSortedArrays(nums2,nums1);
int mid=(len1+len2+1)/2; //注意这里
for(int i=0;i<=len1;i++){ //i表示nums1左边取几个数
int j=mid-i; //nums2属于左半部分的 nums2左边取几个数
//[1,3,8] i=2 -> 取[1,3] ---- i==0, 不取
int al = i==0 ? Integer.MIN_VALUE : nums1[i-1];
int ar = i==len1 ? Integer.MAX_VALUE : nums1[i];
int bl = j==0 ? Integer.MIN_VALUE : nums2[j-1];
int br = j==len2 ? Integer.MAX_VALUE : nums2[j];
//nums1: [ ... al | ar ... ]
//nums2: [ ... bl | br ... ]
//合法切分 -> 左边严格小于右边: max(al, bl) <= min(ar, br)
if(al<=br && bl<=ar){
int lmax=Math.max(al,bl);
int rmin=Math.min(ar,br);
//mid = (len1 + len2 + 1)/2;
//奇数时,左边比右边多 1 个
//偶数时,左右一样多
if(((len1+len2)&1) ==1) return lmax;
return (lmax+rmin)/2.0;
}
}
return 0.0;
}
}求两个升序数组的中位数:
- 将两个数组都拆分成左右两部分
- 必须满足两个数组 左半部分值 < 右半部分值: max(al , bl) < min(ar , br)
- 我们遍历nums1的左半部分 i, 表示nums1取多少个作为左半部分 同时设置 mid= (len1+len2+1)/2 , 于是nums2左半部分为 mid-i , mid的设置可以确保左边比右边多1
- 如果是len和为奇数, 那么答案一定是 al 或者 bl, max即可
- 如果是偶数, 那么就是 (lmax+rmin)/2
有效的括号
代码块收起展开
class Solution {
public boolean isValid(String s) {
Map<Character, Character> map = new HashMap<>();
map.put(')', '(');
map.put(']', '[');
map.put('}', '{');
Deque<Character> stk = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (!map.containsKey(c)) {
stk.push(c);
} else {
if (stk.isEmpty() || stk.pop() != map.get(c)) return false;
}
}
return stk.isEmpty();
}
}最小栈
代码块收起展开
class MinStack {
private final Deque<int[]> stk = new ArrayDeque<>();
public MinStack() {
stk.push(new int[]{0,Integer.MAX_VALUE}); //放在队首, 就是栈的api
}
public void push(int val) {
int min = Math.min(val,stk.peek()[1]);
stk.push(new int[]{val,min});
}
public void pop() {
stk.pop(); //从头部出来, 就是栈的api
}
public int top() {
return stk.peek()[0];
}
public int getMin() {
return stk.peek()[1];
}
}队列API : offer() + poll() + peek() 用双端队列中栈的API : push, pop, peek 这题要求我们同时满足可以拿出 val, 以及当前栈的最小值, 所以我们只需要每次放进去的时候比较最小值再放入 min = min(val, stk.peek()[1]) 用pair, 但是java没pair, 所以用int[2] -> val, min 注意放个哨兵上去! stk.push(new int[]{0,Integer.MAX_VALUE});
71. 寻找峰值
代码块收起展开
class Solution {
public int findPeakElement(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int m = l + (r - l) / 2;
if (nums[m] > nums[m + 1]) r = m;
else l = m + 1;
}
return l;
}
}栈
72. 有效的括号
代码块收起展开
class Solution {
public boolean isValid(String s) {
Deque<Character> st = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (c == '(') st.push(')');
else if (c == '[') st.push(']');
else if (c == '{') st.push('}');
else if (st.isEmpty() || st.pop() != c) return false;
}
return st.isEmpty();
}
}73. 最小栈
代码块收起展开
class MinStack {
Deque<Integer> st = new ArrayDeque<>();
Deque<Integer> minSt = new ArrayDeque<>();
public void push(int val) {
st.push(val);
if (minSt.isEmpty() || val <= minSt.peek()) minSt.push(val);
}
public void pop() {
if (st.pop().equals(minSt.peek())) minSt.pop();
}
public int top() { return st.peek(); }
public int getMin() { return minSt.peek(); }
}74. 字符串解码
代码块收起展开
class Solution {
public String decodeString(String s) {
Deque<Integer> numSt = new ArrayDeque<>();
Deque<StringBuilder> strSt = new ArrayDeque<>();
StringBuilder cur = new StringBuilder();
int num = 0;
for (char c : s.toCharArray()) {
if (Character.isDigit(c)) num = num * 10 + c - '0';
else if (c == '[') {
numSt.push(num);
strSt.push(cur);
num = 0;
cur = new StringBuilder();
} else if (c == ']') {
int k = numSt.pop();
StringBuilder prev = strSt.pop();
while (k-- > 0) prev.append(cur);
cur = prev;
} else cur.append(c);
}
return cur.toString();
}
}75. 每日温度
代码块收起展开
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int len=temperatures.length;
int[] ans=new int[len];
Deque<Integer> deq=new ArrayDeque<>();
for(int i=len-1;i>=0;i--){
while(!deq.isEmpty()&&temperatures[deq.peek()]<temperatures[i]){
deq.pop();
}
if(!deq.isEmpty()) ans[i]=deq.peek()-i;
deq.push(i);
}
return ans;
}
}倒序遍历, 比他小的踢出去
76. 柱状图中最大的矩形
代码块收起展开
class Solution {
int ans = 0;
public int largestRectangleArea(int[] heights) {
int len = heights.length;
int[] h = new int[len + 2];
for (int i = 0; i < len; i++) h[i + 1] = heights[i];
// 首尾哨兵=0:左哨兵保证栈不空(peek不报错),右哨兵强制弹出所
有剩余柱子结算
Deque<Integer> stk = new ArrayDeque<>();
for (int i = 0; i < len + 2; i++) {
// 维护递增栈:栈底->栈顶 = 矮->高
//
遇到h[i]比栈顶矮,说明栈顶柱子的右边界就是i,可以结算了
while (!stk.isEmpty() && h[stk.peek()] > h[i]) {
int top = stk.pop(); // 当前结算的柱子
int l = stk.peek();
//左边第一个比它矮的(天然在栈里)
int width = i - l - 1;
//左右边界之间的宽度(不含两端)
ans = Math.max(ans, h[top] * width);
}
stk.push(i);
}
return ans;
}
}首先把这道题转化成走到当前位置 i , 然后找左边第一个比他小的和右边第一个比他小的, 我们只需要用一个 栈底到栈顶是 从小到大 , 然后走到当前位置, 发现栈顶比当前位置大! 并且栈又是从小到达的排序, 所以肯定有 左小 高 右小 , 此时弹出栈顶得到高, peek得到左低, 然后width=i-l-1算出宽 ---> 注意这里其实左 l 右 r 是左开右开!!!! 不计入的状态
堆 / 优先队列
77. 数组中的第 K 个最大元素
代码块收起展开
//堆
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int x : nums) {
pq.offer(x);
if (pq.size() > k) pq.poll();
}
return pq.peek();
}
}
class Solution {
public int findKthLargest(int[] nums, int k) {
return quickSort(nums,0,nums.length-1,k-1);
}
public int quickSort(int[] nums, int l, int r, int k){
int pivot=nums[l];
int hi=l, lo=r,i=l;
while(i<=lo){
if(nums[i]>pivot) swap(nums,i++,hi++);
else if(nums[i]<pivot) swap(nums,i,lo--);
else i++;
}
// [left, hi-1] > pivot, [hi, lo] == pivot, [lo+1, right] < pivot
if(k<hi) return quickSort(nums,l,hi-1,k);
if(k>lo) return quickSort(nums,lo+1,r,k);
return pivot;
}
public void swap(int[] nums,int l,int r){
int tmp=nums[l];
nums[l]=nums[r];
nums[r]=tmp;
}
}[ 0, … , hi, hi+1 , … , lo, lo+1, … , len-1 ] [0-hi-1] > pivot , [hi-lo] == pivot , [lo+1~len-1] < pivot 所以当 k<hi 时, 超过了 往左边缩小 [ l , ho-1] 当 k>lo 时, 还没到, 往右边推进
78. 前 K 个高频元素
代码块收起展开
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for (int x : nums) map.put(x, map.getOrDefault(x, 0) + 1);
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);
for (var e : map.entrySet()) {
pq.offer(new int[]{e.getKey(), e.getValue()});
if (pq.size() > k) pq.poll();
}
int[] ans = new int[k];
for (int i = k - 1; i >= 0; i--) ans[i] = pq.poll()[0];
return ans;
}
}先用hashmap统计频率, 然后把k-v放进堆中
79. 数据流的中位数
代码块收起展开
class MedianFinder {
// 1 2 3, 4 5 6 大小
PriorityQueue<Integer> maxq = new PriorityQueue<>((a,b)->b-a);
PriorityQueue<Integer> minq = new PriorityQueue<>();
public MedianFinder() {
}
public void addNum(int num) {
if(minq.size()==maxq.size()){
//都一样大的时候, 让左边多一个
minq.offer(num);
maxq.offer(minq.poll());
}else{
//此时必定是左边大, 所以让右边多一个
//但是不能直接给右边, 因为我们期待这两个堆是有序的
//所以即使是多一个, 我们也要放在最大堆取他们那最大的出来
maxq.offer(num);
minq.offer(maxq.poll());
}
}
public double findMedian() {
if(minq.size()<maxq.size()){
return maxq.peek();
}
//注意peek()仅仅只是得到堆顶的值, 注意要/2.0
return (minq.peek()+maxq.peek())/2.0;\
}
}两个堆, 奇数的时候维护左边堆多一个即可
贪心
80. 买卖股票的最佳时机
代码块收起展开
class Solution {
public int maxProfit(int[] prices) {
int min=prices[0];
int ans=0;
for(int x:prices){
ans=Math.max(ans,x-min);
min=Math.min(min,x);
}
return ans;
}
}81. 跳跃游戏
代码块收起展开
class Solution {
public boolean canJump(int[] nums) {
int max=0;
for(int i=0;i<nums.length;i++){
//这道题只看是否能到达, 不管是越过还是如何
if(i>max) return false;
max=Math.max(max,nums[i]+i);
}
return true;
}
}记录能到的最右边, 如果发现当前 i > max, 就说明前面的都无法抵达 i这个位置
82. 跳跃游戏 II
代码块收起展开
class Solution {
int ans=0;
public int jump(int[] nums) {
int cr=0,nr=0;
//n-1是终点, 所以无需传达
for(int i=0;i<nums.length-1;i++){
nr=Math.max(nr,i+nums[i]); //这里会选择最长的桥->step小
//由于题目保证了一定能到最后一个的=, 所以
if(i==cr){ //说明无路可走了, 那就回去到nr
cr=nr;
ans++;
}
//cr 表示 当前这一步最多能覆盖到的最右边界
//当 i == cr 时,说明 这一跳能到的所有位置都扫描完了
//这时候必须进入下一跳,所以才ans++
}
return ans;
}
}83. 划分字母区间
代码块收起展开
class Solution {
List<Integer> ans = new ArrayList<>();
public List<Integer> partitionLabels(String s) {
Map<Character,Integer> last=new HashMap<>();
for(int i=0;i<s.length();i++){
last.put(s.charAt(i), i); //当前字母及其最后出现的下标
}
int end=0, preIndex=0;
for(int i=0;i<s.length();i++){
end=Math.max(end,last.get(s.charAt(i)));
if(i==end){
ans.add(i-preIndex+1);
preIndex=i+1;
}
}
return ans;
}
}84. 杨辉三角
代码块收起展开
class Solution {
List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> generate(int numRows) {
for(int i=0;i<numRows;i++){
List<Integer> cur = new ArrayList<>();
for(int j=0;j<=i;j++){
if(j==0||j==i) cur.add(1); //这里就避免了第一组i-1越界
else
cur.add( ans.get(i-1).get(j-1)+ ans.get(i-1).get(j) );
}
ans.add(cur);
}
return ans;
}
}技巧 / 位运算 / 数组结论题
85. 只出现一次的数字
代码块收起展开
class Solution {
public int singleNumber(int[] nums) {
int ans = 0;
for (int x : nums) ans ^= x;
return ans;
}
}86. 多数元素
代码块收起展开
class Solution {
public int majorityElement(int[] nums) {
int candidate = 0, cnt = 0;
for (int x : nums) {
if (cnt == 0) candidate = x;
cnt += (x == candidate) ? 1 : -1;
}
return candidate;
}
}87. 颜色分类
代码块收起展开
class Solution {
public void sortColors(int[] nums) {
int p0=0, p2=nums.length-1;
for(int i=0;i<=p2;){
if(nums[i]==0) swap(nums,i++,p0++);
//注意2的换到后面, 但是换过来的还没校验!
else if(nums[i]==2) swap(nums,p2--,i);
else i++;
}
}
public void swap(int[] nums,int i,int j){
int tmp=nums[i];
nums[i]=nums[j];
nums[j]=tmp;
}
}88. 下一个排列
代码块收起展开
class Solution {
public void nextPermutation(int[] nums) {
int len=nums.length;
int i=len-2;
while(i>=0&&nums[i]>=nums[i+1]) i--;
if(i>=0){
int j=len-1;
while(nums[j]<=nums[i]) j--;
swap(nums,i,j);
}
reverse(nums,i+1,len-1);
}
public void reverse(int[] nums,int l,int r){
while(l<r){
swap(nums,l++,r--);
}
}
public void swap(int[] nums,int i,int j){
int tmp=nums[i];
nums[i]=nums[j];
nums[j]=tmp;
}
}右边已经是降序(最大了,没法更大) → 所以必须动左边第一个能变大的位置(降点) → 换成右边刚好比它大的数(换大点) → 后面要最小(翻转降序→升序)
89. 寻找重复数
代码块收起展开
public void nextPermutation(int[] nums) {
int n = nums.length;
// 1. 找降点:从右往左找第一个 nums[i] < nums[i+1]
int i = n - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
// 2. 换大点:找右边刚好比 nums[i] 大的数,交换
if (i >= 0) {
int j = n - 1;
while (nums[j] <= nums[i]) {
j--;
}
swap(nums, i, j);
}
// 3. 翻后面:i+1 到末尾反转
reverse(nums, i + 1, n - 1);
}
private void swap(int[] nums, int a, int b) {
int tmp = nums[a];
nums[a] = nums[b];
nums[b] = tmp;
}
private void reverse(int[] nums, int l, int r) {
while (l < r) {
swap(nums, l++, r--);
}
}这道题本质就是 返回链表成环的入口!!!!!!!!!!!!!!!!!!! 先快慢指针, 然后slow从头, fast slow齐头并进即可
动态规划
90. 爬楼梯
代码块收起展开
class Solution {
public int climbStairs(int n) {
if (n <= 2) return n;
int a = 1, b = 2;
for (int i = 3; i <= n; i++) {
int c = a + b;
a = b;
b = c;
}
return b;
}
}91. 打家劫舍
代码块收起展开
class Solution {
public int rob(int[] nums) {
int pp=0, p=0;
for(int x:nums){
int cur=Math.max(pp+x,p);
pp=p;
p=cur;
}
return p;
}
}来到当前, max = Math.max(prepre+cur, pre) , 然后更新逻辑是pre
92. 完全平方数
代码块收起展开
class Solution {
public int numSquares(int n) {
//用平方数凑出该数的最小数
int[] dp=new int[n+1];
dp[0]=0;
for(int i=1;i<=n;i++){
dp[i]=i; //全部用1
for(int j=1;j*j<=i;j++){
//接受 j*j+dp[i-j*j]的方法
dp[i]=Math.min(dp[i-j*j]+1,dp[i]);
}
}
return dp[n];
}
}93. 零钱兑换
代码块收起展开
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
Arrays.fill(dp,Integer.MAX_VALUE/2); //防止dp[i-x]+1溢出为负数
dp[0]=0;
for(int x:coins){
for(int i=x;i<=amount;i++){
dp[i]=Math.min(dp[i],dp[i-x]+1);
}
}
return dp[amount]==Integer.MAX_VALUE/2?-1:dp[amount];
}
}94. 最长递增子序列
代码块收起展开
class Solution {
public int lengthOfLIS(int[] nums) {
int[] tail = new int[nums.length];
int size = 0;
for (int x : nums) {
int l = 0, r = size;
while (l < r) {
int m = l + (r - l) / 2;
if (tail[m] < x) l = m + 1;
else r = m;
}
tail[l] = x;
if (l == size) size++;
}
return size;
}
}95. 乘积最大子数组
代码块收起展开
class Solution {
public int maxProduct(int[] nums) {
int ans=nums[0], len=nums.length;
int prefix=1, suffix=1;
for(int i=0;i<len;i++){
prefix*=nums[i];
suffix*=nums[len-1-i];
ans=Math.max(ans,Math.max(prefix,suffix));
if(prefix==0) prefix=1;
if(suffix==0) suffix=1;
}
return ans;
}
}经发现乘积最大子数组除非遇到0, 不然必定是左右端点连续的, 所以一次遍历前后缀, 然后Math.max(ans, Math.max(pre, suf));
96. 分割等和子集
代码块收起展开
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for(int x:nums) sum+=x;
if(sum%2!=0) return false;
//dp[j]表示数组能否凑出j
int target=sum/2;
boolean[] dp=new boolean[target+1];
dp[0]=true; //数组肯定能凑出0的, 不选就是了
//又多了x, 能凑出什么
for(int x:nums){
//j-x>=0防止越界
for(int j=target;j>=x;j--){
dp[j]|=dp[j-x];
}
}
return dp[target];
}
}97. 最长有效括号
代码块收起展开
class Solution {
int max=0;
public int longestValidParentheses(String s) {
Deque<Integer> stk = new ArrayDeque<>();
stk.push(-1);
for(int i=0;i<s.length();i++){
if(s.charAt(i)=='('){
stk.push(i);
}else {
stk.pop();
//发现栈空了, 无法计数, 放入当下作为哨兵
if(stk.isEmpty()){
stk.push(i);
}else {
max=Math.max(max,i-stk.peek());
}
}
}
return max;
}
}放入哨兵-1, 是’(‘就push, 是’)‘就pop然后 加哨兵 or 计数
98. 不同路径
代码块收起展开
class Solution {
public int uniquePaths(int m, int n) {
int[][]dp = new int [m+1][n+1];
//右边第一行和下边第一行只有直走最短一种路径
for(int i=1;i<=m;i++) dp[i][1]=1;
for(int i=1;i<=n;i++) dp[1][i]=1;
for(int i=2;i<=m;i++){
for(int j=2;j<=n;j++){
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m][n];
}
}第一行第一列只有直走一种方法, 所以初始化为1 其他格子 有上左两种来源
99. 最小路径和
代码块收起展开
class Solution {
public int minPathSum(int[][] grid) {
int m=grid.length, n=grid[0].length;
int[][] dp=new int[m][n]; dp[0][0]=grid[0][0];
for(int i=1;i<n;i++) dp[0][i]=dp[0][i-1]+grid[0][i];
for(int j=1;j<m;j++) dp[j][0]=dp[j-1][0]+grid[j][0];
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
return dp[m-1][n-1];
}
}单词拆分
代码块收起展开
class Solution {
public String decodeString(String s) {
Deque<Integer> countStack = new ArrayDeque<>();
Deque<StringBuilder> strStack = new ArrayDeque<>();
StringBuilder cur = new StringBuilder();
int num = 0;
for (char c : s.toCharArray()) {
if (Character.isDigit(c)) {
num = num * 10 + (c - '0'); // 处理多位数
} else if (c == '[') {
countStack.push(num); // 次数入栈
strStack.push(cur); // 前缀入栈
cur = new StringBuilder();
//清空,准备记录括号内
num = 0;
} else if (c == ']') {
int count = countStack.pop();
StringBuilder prev = strStack.pop();
for (int i = 0; i < count; i++) prev.append(cur);
cur = prev;
//拼好的结果变成新的cur
} else {
cur.append(c);
}
}
return cur.toString();
}
}遇到数字, num=num*10 + ch - ‘0’ 遇到 ’ [ ’ , 进入新的领域, cntStk.push(num), num=0; strStk.push(cur), cur=new StringBuilder(); //cur变成前缀了 遇到 ’ ] ’, 该领域结束, pre = strStk.pop(); cnt = cntStk.pop(); for(int i=0;i<cnt;i++) pre.append(cur); cur=pre;
100. 最长回文子串
代码块收起展开
class Solution {
public String longestPalindrome(String s) {
int start = 0, maxLen = 1;
for (int i = 0; i < s.length(); i++) {
int len1 = expand(s, i, i);
int len2 = expand(s, i, i + 1);
int cur = Math.max(len1, len2);
if (cur > maxLen) {
maxLen = cur;
start = i - (cur - 1) / 2;
//中心左边奇数长度 i-(cur-1)/2
//中心 i-cur/2+1--->i-(cur-1)/2 向下截断
}
}
return s.substring(start, start + maxLen);
}
private int expand(String s, int l, int r) {
while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) {
l--;
r++;
}
return r - l - 1; //最后的r和l都是不合法的, 所以中间串等于r-l-1
//0 1 2 3 4--> 123 = r(4)-l(0)-1 = 3
}
}
class Solution {
public String longestPalindrome(String s) {
int n=s.length();
boolean[][] dp=new boolean[n][n];
int maxlen=1, start=0;
for(int r=0;r<n;r++){
for(int l=0;l<=r;l++){
//两端相等, 并且内部也对称or内部根本不够长0 1 2
if(s.charAt(l)==s.charAt(r)&&(r-l<=2||dp[l+1][r-1])){
dp[l][r]=true;
if(r-l+1>maxlen){
maxlen=r-l+1;
start=l;
}
}
}
}
return s.substring(start,start+maxlen);
}
}cur为偶数的时候, -cur/2+1 = (cur-1)/2 注意substring(start, end)
101. 最长公共子序列
代码块收起展开
class Solution {
int ans=0;
public int longestCommonSubsequence(String text1, String text2) {
int len1=text1.length(), len2=text2.length();
int[][] dp = new int[len1+1][len2+1];
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(text1.charAt(i-1)==text2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1]+1; //相等
}else{
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[len1][len2];
}
}== ---> dp[i][j] = dp[i-1][j-1]+1 等于直接+1 dp[i][j]=Math.max(dp[i-1][j], dp[i][j-1]);
102. 编辑距离
代码块收起展开
class Solution {
public int minDistance(String word1, String word2) {
int len1=word1.length(), len2=word2.length();
int[][] dp = new int[len1+1][len2+1];
//如果word1/word2为空, 一直插入
for(int i=0;i<=len1;i++) dp[i][0]=i;
for(int j=0;j<=len2;j++) dp[0][j]=j;
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1];
}else{
dp[i][j]=Math.min(Math.min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]+1);
}
}
}
return dp[len1][len2];
}
}dp[i][j] = 插入, 插入, 替换, 相等 min(dp[i-1][j]+1 , dp[i][j-1]+1, dp[i-1][j-1]+1, dp[i-1][j-1])