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. 两数之和

代码块JAVA · 11 行收起展开
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. 字母异位词分组

代码块JAVA · 12 行收起展开
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. 最长连续序列

代码块JAVA · 14 行收起展开
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. 移动零

代码块JAVA · 10 行收起展开
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. 盛最多水的容器

代码块JAVA · 11 行收起展开
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. 三数之和

代码块JAVA · 26 行收起展开
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. 接雨水

代码块JAVA · 16 行收起展开
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. 无重复字符的最长子串

代码块JAVA · 18 行收起展开
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. 找到字符串中所有字母异位词

代码块JAVA · 17 行收起展开
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 的子数组

代码块JAVA · 21 行收起展开
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. 滑动窗口最大值

代码块JAVA · 20 行收起展开
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. 最小覆盖子串

代码块JAVA · 24 行收起展开
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. 最大子数组和

代码块JAVA · 11 行收起展开
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. 合并区间

代码块JAVA · 15 行收起展开
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. 轮转数组

代码块JAVA · 17 行收起展开
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. 除自身以外数组的乘积

代码块JAVA · 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] 原地矫正整数

代码块JAVA · 21 行收起展开
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. 矩阵置零

用行列数组标记再置零

代码块JAVA · 23 行收起展开
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循环

代码块JAVA · 34 行收起展开
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° 等价于 对角线交换+左右翻转

代码块JAVA · 23 行收起展开
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

代码块JAVA · 11 行收起展开
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)

代码块JAVA · 18 行收起展开
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 z

23. 反转链表

代码块JAVA · 13 行收起展开
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. 回文链表

代码块JAVA · 25 行收起展开
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. 环形链表

代码块JAVA · 12 行收起展开
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

代码块JAVA · 22 行收起展开
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. 合并两个有序链表

代码块JAVA · 18 行收起展开
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. 两数相加

代码块JAVA · 19 行收起展开
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 个结点

代码块JAVA · 14 行收起展开
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. 两两交换链表中的节点

代码块JAVA · 19 行收起展开
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 个一组翻转链表

代码块JAVA · 30 行收起展开
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. 随机链表的复制

代码块JAVA · 19 行收起展开
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. 排序链表

代码块JAVA · 30 行收起展开
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缓存

代码块JAVA · 52 行收起展开
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个有序链表

代码块JAVA · 19 行收起展开
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. 二叉树的中序遍历

代码块JAVA · 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. 二叉树的最大深度

代码块JAVA · 6 行收起展开
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. 翻转二叉树

代码块JAVA · 22 行收起展开
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. 对称二叉树

代码块JAVA · 27 行收起展开
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. 二叉树的直径

代码块JAVA · 29 行收起展开
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. 二叉树的层序遍历

代码块JAVA · 39 行收起展开
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. 将有序数组转换为二叉搜索树

代码块JAVA · 27 行收起展开
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. 验证二叉搜索树

代码块JAVA · 19 行收起展开
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 小的元素

代码块JAVA · 29 行收起展开
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. 二叉树的右视图

代码块JAVA · 39 行收起展开
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. 二叉树展开为链表

代码块JAVA · 31 行收起展开
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. 从前序与中序遍历序列构造二叉树

代码块JAVA · 45 行收起展开
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

代码块JAVA · 28 行收起展开
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. 二叉树的最近公共祖先

代码块JAVA · 27 行收起展开
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;

    }

}

二叉树中的最大路径和

代码块JAVA · 35 行收起展开
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. 岛屿数量

代码块JAVA · 53 行收起展开
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. 腐烂的橘子

代码块JAVA · 62 行收起展开
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. 课程表

代码块JAVA · 57 行收起展开
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

代码块JAVA · 69 行收起展开
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. 全排列

代码块JAVA · 47 行收起展开
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);

        }

    }

}
代码块PLAINTEXT · 1 行收起展开
Collections.swap(path,start,i);
代码块JAVA · 2 行收起展开
            dfs(path,start+1);
            Collections.swap(path,start,i);

58. 子集

代码块JAVA · 31 行收起展开
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. 电话号码的字母组合

代码块JAVA · 53 行收起展开
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. 组合总和

代码块JAVA · 56 行收起展开
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. 括号生成

代码块JAVA · 47 行收起展开
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. 单词搜索

代码块JAVA · 57 行收起展开
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. 分割回文串

代码块JAVA · 59 行收起展开
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 皇后

代码块JAVA · 79 行收起展开
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. 搜索插入位置

代码块JAVA · 29 行收起展开
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. 搜索二维矩阵

代码块JAVA · 27 行收起展开
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. 在排序数组中查找元素的第一个和最后一个位置

代码块JAVA · 66 行收起展开
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. 搜索旋转排序数组

代码块JAVA · 22 行收起展开
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. 寻找旋转排序数组中的最小值

代码块JAVA · 12 行收起展开
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. 寻找两个正序数组的中位数

代码块JAVA · 63 行收起展开
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;

    }

}

求两个升序数组的中位数:

  1. 将两个数组都拆分成左右两部分
  2. 必须满足两个数组 左半部分值 < 右半部分值: max(al , bl) < min(ar , br)
  3. 我们遍历nums1的左半部分 i, 表示nums1取多少个作为左半部分 同时设置 mid= (len1+len2+1)/2 , 于是nums2左半部分为 mid-i , mid的设置可以确保左边比右边多1
  4. 如果是len和为奇数, 那么答案一定是 al 或者 bl, max即可
  5. 如果是偶数, 那么就是 (lmax+rmin)/2

有效的括号

代码块JAVA · 39 行收起展开
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();

    }

}

最小栈

代码块JAVA · 37 行收起展开
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. 寻找峰值

代码块JAVA · 11 行收起展开
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. 有效的括号

代码块JAVA · 12 行收起展开
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. 最小栈

代码块JAVA · 13 行收起展开
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. 字符串解码

代码块JAVA · 23 行收起展开
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. 每日温度

代码块JAVA · 31 行收起展开
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. 柱状图中最大的矩形

代码块JAVA · 27 行收起展开
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 个最大元素

代码块JAVA · 60 行收起展开
//堆
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 个高频元素

代码块JAVA · 14 行收起展开
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. 数据流的中位数

代码块JAVA · 27 行收起展开
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. 买卖股票的最佳时机

代码块JAVA · 11 行收起展开
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. 跳跃游戏

代码块JAVA · 21 行收起展开
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

代码块JAVA · 37 行收起展开
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. 划分字母区间

代码块JAVA · 35 行收起展开
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. 杨辉三角

代码块JAVA · 29 行收起展开
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. 只出现一次的数字

代码块JAVA · 7 行收起展开
class Solution {
    public int singleNumber(int[] nums) {
        int ans = 0;
        for (int x : nums) ans ^= x;
        return ans;
    }
}

86. 多数元素

代码块JAVA · 10 行收起展开
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. 颜色分类

代码块JAVA · 31 行收起展开
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. 下一个排列

代码块JAVA · 53 行收起展开
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. 寻找重复数

代码块JAVA · 33 行收起展开
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. 爬楼梯

代码块JAVA · 12 行收起展开
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. 打家劫舍

代码块JAVA · 21 行收起展开
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. 完全平方数

代码块JAVA · 31 行收起展开
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. 零钱兑换

代码块JAVA · 14 行收起展开
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. 最长递增子序列

代码块JAVA · 17 行收起展开
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. 乘积最大子数组

代码块JAVA · 15 行收起展开
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. 分割等和子集

代码块JAVA · 39 行收起展开
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. 最长有效括号

代码块JAVA · 41 行收起展开
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. 不同路径

代码块JAVA · 31 行收起展开
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. 最小路径和

代码块JAVA · 31 行收起展开
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];

    }

}

单词拆分

代码块JAVA · 29 行收起展开
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. 最长回文子串

代码块JAVA · 90 行收起展开
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. 最长公共子序列

代码块JAVA · 35 行收起展开
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. 编辑距离

代码块JAVA · 41 行收起展开
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])