LeetCode Hot 100
LeetCode Hot 100 · Java 题解
哈希
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();
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. 三数之和
AA跳过第二个A, 双指针(也要跳过A) sum的大小判断
代码块收起展开
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) {
int[] standard = new int[128], cur=new int[128];
for(char ch:p.toCharArray()) standard[ch]++;
int l=0, len=p.length();
for(int r=0;r<s.length();r++){
cur[s.charAt(r)]++;
//len-1是刚好满足程度的, len就超出1个
if(r>=len) cur[s.charAt(r-len)]--;
if(r>=len-1&&Arrays.equals(standard,cur)) ans.add(r-len+1);
}
return ans;
}
}在长串s 中找出p串的异位词, 直接比较肯定不好, 我们用p做出一个标准数组, 通过比较标准数组来搞 Arrays.equals(a,b)
依旧右端点一直进来, 发现长度大于plen了, 就一直t出左边, 如果发现符合要求, 就ans.add(), 这里是起点
10. 和为 K 的子数组
map.getOrDefault(key, default)
先看sum-target, 再存sum为key
代码块收起展开
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];
//不能设0, 因为如果[-1], 他不会选, 题目又说要至少一个元素
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 {
public int[][] merge(int[][] intervals) {
var ans = new ArrayList<int[]>();
//按第一个数从小到大排
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][])
用 list.toArray(new int[0][]) 就行:
代码块收起展开
ArrayList<int[]> list = new ArrayList<>();
// ... 添加元素
int[][] result = list.toArray(new int[0][]);为什么要传 new int[0][]?因为 toArray() 有两个版本:
toArray()→ 返回Object[],不能直接转成int[][]toArray(T[] a)→ 返回T[],你传什么类型的数组,就返回什么类型
所以必须传一个 int[][] 类型的数组进去,告诉它返回类型。至于为什么是 new int[0][] 而不是 new int[list.size()][]:
new int[0][]:传一个长度为 0 的数组,toArray 发现容量不够,会自动创建一个正确大小的新数组返回new int[list.size()][]:传一个正确大小的数组,toArray 直接填充这个数组
现代 Java 推荐用 new int[0][],因为:
- JVM 对这种模式做了优化,性能不比预分配差
- 代码更简洁,不需要调用
list.size() - 避免手动算错大小
完整例子:
代码块收起展开
ArrayList<int[]> list = new ArrayList<>();
list.add(new int[]{1, 2});
list.add(new int[]{3, 4, 5});
int[][] result = list.toArray(new int[0][]);
// result = [[1, 2], [3, 4, 5]]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]的数并且nums\[x-1]=x
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[]来判断行列是否要复制--->
也可以不用boolean数组, 先俩变量标记第一行列是否有0, 然后在遍历数组检查哪行哪列有0, 令mat[i][0]=mat[0][j]=0,
然后再遍历, 只要第一行列有此情况的, 都归零, 然后处理第一行列的0的情况
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;
}
}
// - matrix[i][j] > target → 当前数太大,只能往左走(往下会更大)
// - matrix[i][j] < target → 当前数太小,只能往下走(往左会更小)链表
22. 相交链表
我走过你走过的路
代码块收起展开
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){
ListNode pre = null; // 移到这里,每轮重置
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;
//fast 初始化为 head.next 是为了让 slow 停在前半段的最后一个节点,而不是中点。
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;
//左子树不行 or 左子树和root.val不严格递增
if(!isValidBST(root.left)||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) {
dfs(root,0);
return ans;
}
public void dfs(TreeNode root, int depth){
if(root==null) return;
if(depth==ans.size()) ans.add(root.val);
dfs(root.right,depth+1);
dfs(root.left,depth+1);
}
}层序遍历的最后一个就是右视图
那我们可以改成右中左的遍历方式,
这样右视图不就是第一个到达该层级的吗? easy, 妙不可言
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) {
//base:
//递归到叶子节点了, 指定没戏, 返回null
//递归到p/q了, 那我就是父
//p root
// \ / \
// q p q
if(root==null||root==p||root==q) return root;
TreeNode l=lowestCommonAncestor(root.left,p,q);
TreeNode r=lowestCommonAncestor(root.right,p,q);
//左不是null, 右不是null 我就是父
if(l!=null&&r!=null) return root;
//左右有一个是null, 非null就是父
return l==null?r:l;
}
}二叉树中的最大路径和
代码块收起展开
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. 分割回文串
string.substring左闭右开[)
代码块收起展开
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++); //遇到0和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. 下一个排列
代码块收起展开
public void nextPermutation(int[] nums) {
int n = nums.length;
int i = n - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
if (i >= 0) { //因为可能是 54321, 最大字典序了
int j = n - 1;//从右往左第一个比nums[i]大的
while (i<j&&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--);
}
}1 3 5 4 2 --->第一个比右边大, 为 i
1 4 5 3 2 --->右边开始第一个比 i 大 j , swap(i,j)
1 4 2 3 5 --->reverse(i+1, len-1)
89. 寻找重复数
代码块收起展开
class Solution {
public int findDuplicate(int[] nums) {
int fast=nums[0], slow=nums[0];
do{
fast=nums[nums[fast]];
slow=nums[slow];
}while(fast!=slow);
fast=nums[0];
while(fast!=slow){
fast=nums[fast];
slow=nums[slow];
}
return slow;
}
}这道题本质就是 返回链表成环的入口!!!!!!!!!!!!!!!!!!!
先快慢指针, 然后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 {
int ans=0;
public int rob(int[] nums) {
int pp=0, p=0; //迭代之前的最优解
for(int x:nums){
ans=Math.max(pp+x, p);
pp=p;
p=ans;
}
return ans;
}
}来到当前, 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.MIN_VALUE/2); //万一遇到
dp[0]=0;
//多了一枚硬币, 那么重新遍历每个dp
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.MIN_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])