• Home
  • 归档
文章检索 作品与友邻 关于我
暴君乔博客
Home 归档

leetcode题目详细解答【java版】持续更新

发布于: 2018-10-18
更新于: 2023-07-09
文章目录
  1. leetcode题经
    1. 算法题知识点总结

leetcode题经

重在将自己解题的思路发上来,当然有时候是最快的解法加上一些我对它的想法,希望大家都能有所收获。若题目有更好的解法,欢迎一起交流。具体github项目专门放了我对算法的看法,leetcode结题,还有源码分析等等,持续更新中。

/**
* 注意HashMap的put(Key,Value)是可以替换旧值的,所以当我们遇到测试数据中包含重复数值时可以无视它。
* 所以其实判断条件里面的map.get(supplement)!=i其实也是可以无视的,因为我们的value已经更新为后面的值了
* @param nums
* @param target
* @return
*/
public int[] twoSum(int[] nums, int target) {
    HashMap<Integer, Integer> map = new HashMap<>(16);
    for (int i = 0; i < nums.length; i++) {
        int supplement = target - nums[i];
        if (map.containsKey(supplement) && map.get(supplement) != i) {
            return new int[]{i, map.get(supplement)};
        }
        map.put(nums[i], i);
    }
    return null;
}

算法题知识点总结

以下内容合并自 kgNotes/notes/算法题知识点总结.md。原 Obsidian 图片附件未在知识库中找到,已保留为“图示”文件名提示。

数组类

两数之和

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-两数之和.png

  • 由于数组有序
  • 设置快慢指针,通过比较大小,来驱动指针向中间游动
    public int[] twoSum(int[] numbers, int target) {

        int left =0;

        int right=numbers.length - 1;

        while(left<right){

            int sum=numbers[left]+numbers[right];

            if(sum==target){

               break;

            }

  

            if(sum>target){

                right--;

            }else{

                left++;

            }

        }

        return new int[]{left+1, right+1};

    }
三数之和

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-三数之和.png

  • 设置三个指针,而内部的两个指针其实就是双数之和
  • 优化点
    • 如果数字相同,则指针向前移动
    • 如果三个最小数和已经大于0,则没必要继续处理了
    • 如果两个最大值+当前的左指针小于0了,则左指针向前
 public List<List<Integer>> threeSum(int[] nums) {

        Arrays.sort(nums);

        List<List<Integer>> resultList=new ArrayList<>();

        int length=nums.length;

        for(int i=0;i<length-2;i++){

            int x=nums[i];

            if(i>0 && x==nums[i-1]){

                // 重复数字跳过

                continue;

            }

            // 最小的三个数已经大于0了

            if(x+nums[i+1]+nums[i+2]>0){

                break;

            }

            // 最小的+两个最大值如果小于0,则左指针继续往下

            if(x+nums[length-2]+nums[length-1]<0){

                continue;

            }

            int j=i+1, k=length-1;

            while(j<k){

                int sum=x+nums[j]+nums[k];

                if(sum>0){

                    k--;

                }else if(sum<0){

                    j++;

                }else{

                    resultList.add(List.of(x,nums[j],nums[k]));

                    for(j++;j<k && nums[j]==nums[j-1];++j);

                    for(k--;j<k && nums[k]==nums[k+1];k--);

                }

            }

        }

        return resultList;

    }
盛水最多的容器

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-盛水最多的容器.png

    1. right 左移使右侧变蓝 (判断条件为 true )
  1. left 右移使左侧变红 (判断条件为 false )
  2. 故确定二分处 ( mid ) 的染色条件是关键
  • 关键的面积计算公式是
    • 宽度 * 两根线的minHeight
  • 基于这条公式
    • 我们先算出来左右两个指针的面积
  • 如果说要比我们最开始最大宽度的面积要大,那么高度一定要上升
    • 要么右边的高度比左边的高了,我们左边指针右移试试
    • 要么左边的高度比右边的高了,我们右边指针左移试试看
    public int maxArea(int[] height) {

        int result=0;

        int left=0;

        int right=height.length-1;

        while(left<right){

            int area=(right-left)*Math.min(height[left],height[right]);

            result=Math.max(result,area);

            if(height[left]<height[right]){

                left++;

            }else{

                right--;

            }

        }

        return result;

    }
接雨水

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-接雨水.png

  • 核心的关键抽象怎么计算出水单位
  • 我们可以发现水的单位是由前面的墙高度和后面墙的高度,取两者的最小值
  • 这里我们就会发现这里就是一堆的盛水最多容器的变种题目
  • 那么我们做两个遍历
    • 第一遍我们去计算所有节点的前面墙高度是多少
    • 第二遍我们去计算所有节点的后面墙高度是多少
  • 最后我们遍历整个数组
    • 首先取墙的最低高度,因为这个决定了水到底能装多少,因为超过这个的会漏出去
    • 然后用墙的最低高度-当前的数组高度= 实际水单位
    public int trap(int[] height) {
        // 时间复杂度On
        // 空间复杂度On
        int l=height.length;

        int[] preMax=new int[l];

        preMax[0]=height[0];

        for(int i=1;i<l;i++){

            preMax[i]=Math.max(preMax[i-1],height[i]);

        }

  

        int[] sufMax=new int[l];

        sufMax[l-1]=height[l-1];

        for(int j=l-2;j>=0;j--){

            sufMax[j]=Math.max(sufMax[j+1],height[j]);

        }

  

        int result=0;

        for(int n=0;n<l;n++){

            result+=Math.min(preMax[n],sufMax[n]) -height[n];

        }

        return result;

    }
  • 如果不使用空间存储呢,我们优化两个指针,表示前序最高的高度以及后序的最高高度
  • 如果前序的小,那么此时能装水的高度就为前序
  • 我们就直接拿前序减去此时的高度即可
    public int trap(int[] height) {

        int l=height.length;

        int result=0;

        int left=0;

        int right=l-1;

        int preMax=0;

        int sufMax=0;

        // 如果相等,也要计算水

        while(left<=right){

            preMax=Math.max(preMax,height[left]);

            sufMax=Math.max(sufMax,height[right]);

            if(preMax<sufMax){

                result+=preMax-height[left++];

            }else{

                result+=sufMax-height[right--];

            }

        }

        return result;

    }
长度最小的子数组

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-长度最小子数组.png

  • 核心可以转为,维护一个最左节点
  • 我们从0开始迭代数组
    • 如果发现迭代和减掉最左节点值,依然大于target【即满足题意要求的话】,那左节点持续右移
    • 直到不满足且当前和大于target时,我们存储结果值【当然要判断此时是不是最优解】
    public int minSubArrayLen(int target, int[] nums) {

        int l=nums.length;

        int result=l+1;

        int sum=0;

        int left=0;

        for(int right=0;right<l;right++){

            sum+=nums[right];

            while(sum-nums[left]>=target){

                sum-=nums[left++];

                // 左端点右移

            }

            if(sum>=target){

                result=Math.min(result,right-left+1);

            }

        }

        return result<=l?result:0;

    }
乘积小于k的子数组

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-乘积小于k的子数组.png

  • 只要最长的子数组满足了
  • 那么我们就可以得到right-left+1的节点就肯定是结果数组个数一部分,再然后我们就继续右移right节点即可
    public int numSubarrayProductLessThanK(int[] nums, int k) {

        if(k<=1){

            return 0;

        }

  

        int result=0;

        int prod=1;

        int left=0;

        for(int right=0;right<nums.length;right++){

            prod *= nums[right];

            while(prod>=k){

                prod/=nums[left];

                left+=1;

            }

            // 只要满足了,那么结果就为l到r之间的每一个和,因为他们都是小于target的

            // 所以直接right-left,再把位点+1加上即可

            result+=right-left+1;

        }

        return result;

    }
无重复字符的最长字串

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-无重复字符的最长子串.png

  • 由于题干说了是数字、字符,那么就是ASCII码,128的数组即可秒
  • 由于是判断是否重复,我们可以初始化boolean的128位数组
  • 首先定义一个left节点,作为滑动窗口起始
  • 开始遍历数组
    • 当发现有重复字符的时候
    • 将滑动窗口的左节点持续向右移,直到不重复为止
    • 记录最大的子串长度结果
    public int lengthOfLongestSubstring(String s) {

        int result=0;

        int left=0;

        boolean[] has=new boolean[128];

        for(int right=0;right<s.length();right++){

            char c=s.charAt(right);

            while(has[c]){

                has[s.charAt(left++)]=false; //左移窗口,并且置为false

            }

            has[c]=true;

            result=Math.max(result,right-left+1);

        }

        return result;

    }
获取单值网络中最小操作数

2033. 获取单值网格的最小操作数 - 力扣(LeetCode)

class Solution:

    def minOperations(self, grid: List[List[int]], x: int) -> int:

        flat = []

        first = grid[0][0]

        for row in grid:

            for num in row:

                if (num-first) % x != 0:

                    return -1

                flat.append(num)

        flat.sort()

        medium = flat[len(flat)//2]

        res=0

        for num in flat:

            res += abs(num-medium)//x

  

        return res
class Solution {
    public int minOperations(int[][] grid, int x) {
        int totalEle=grid.length * grid[0].length;
        int[] temp=new int[totalEle];
        int num=0;
        for(int row=0;row<grid.length;row++){
            for(int i=0;i<grid[row].length;i++){
                int j=grid[row][i];
                // m每个元素都必须是x的倍数,不然不可能达标
                if((j-grid[0][0])%x!=0){
                    return -1;
                }
                // 转为一维数组,其实也可以不转,变成动态下标也能直接玩
                temp[num++] = grid[row][i];
            }
        }
        // 需要排序 或者 搞成第k大算法来找中位数
        Arrays.sort(temp);
        int result=0;
        for(int ele=0;ele<temp.length;ele++){
            // 通过每个元素和中位数进行比较,除以x,就代表每个元素到达中位数需要的次数
            // 全部加起来就是结果了
            result += Math.abs(temp[ele] - temp[totalEle/2])/ x;
        }
        return result;
    }
}

查找

排序数组查找第一个和最后一个

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-查找元素第一个和最后一个.png

  • 观察题干需要什么数字
    • 第一个等于target的数字
      • 转化为>=target的区间
    • 最后一个等于target的数字
      • 转换为> = target+1的
  • 使用LowerBound实现
  • 定义一个闭区间为【L,R】即【0,nums.length-1】
  • 每一次取中间值mid= L+(R-L)/2
    • 除以2直接向下取整,对于偶数场景那就是左边的数字
  • 判断中间值与target的关系
    • 假如说中间值< target
      • 那么中间值以及左边的数据都是小于target的
      • 即我们的区间可以缩小到【mid+1,right】
    • 如果说中间值>=target
      • 那么中间值以及右边的数据都是大于等于target的
      • 即我们的区间可以缩小到【left,mid-1
  • 当我们判断完上面的值,得到最后的【left,right】区间时
  • 我们的left一定是>=target的,因为最后一步操作里面left=mid+1的
  • 通过这样子的方法,我们可以找到第一个大于等于target的数字
  • 那么怎么找到最后一个等于target的数字呢?
    • =target+1

    • 则得到数字-1
    public int[] searchRange(int[] nums, int target) {

        int start=lowerBound(nums,target);

        if(start==nums.length|| nums[start] != target){

            return new int[]{-1,-1};

        }

        int end=lowerBound(nums,target+1)-1;

        return new int[]{start,end};

    }

  

    // 闭区间

    public int lowerBound(int[] nums, int target){

        int left=0,right=nums.length-1;

        while(left<=right){

            int mid=left+(right-left)/2;

            if(nums[mid]<target){

                left=mid+1; // [mid+1,right]

            }else{

                right=mid-1; // [left,mid-1]

            }

        }

        return left;

    }
二分查找数据

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-二分查找数据.png

  • 都是把问题转换为>=target,然后找下标的过程
  • 注意,如果遍历完找不到数据,那么此时返回的下标一定是数组长度的,即越界了
public int search(int[] nums, int target) {

        // 转换题目 找到>=target的值并返回下标

        int left = 0, right=nums.length-1;

        while(left<=right){

            int mid=left+(right-left)/2;

            if(nums[mid]<target){

                left=mid+1;

            }else{

                right=mid-1;

            }

        }

        if(left == nums.length ||  nums[left] != target){

            return -1;

        }

        return left;

    }
找封顶值

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-找封顶值.png

  • 封顶即一定是大于左右两边的值
  • 那么我们可以抽象为nums【top】>= nums【top+1】
    public int findPeakElement(int[] nums) {

        int left=0,right=nums.length-2;

        while(left<=right){

            int mid=left+(right-left)/2;

            // 重点在于爬坡的过程,封顶那么左边的值一定是小于封顶的

            // 即mid<mid+1

            // 由于找任意,所以我们随便找个地方开始计算即可

            if(nums[mid]<nums[mid+1]){

                left=mid+1;

            }else{

                right=mid-1;

            }

        }

        return left;

    }
寻找旋转排序数组的最小值

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-旋转数组找最小.png

  • 核心在于,由于它有可能是两段有序的,所以我们用lowerBound实现时,并不能很好地找到边界
  • 但我们可以做个判断
  • 首先如果起始位置的值比结束位点的小,那么就证明没有旋转,那么结果就是0下标的值
  • 假如有旋转过
  • 我们可以每次染色比较时和第0位元素进行比较
    • 假设mid的值比第0位要小,那就说明mid到right的数字都是比最小值要大的,因为递增的原因
      • 所以我们可以把right=mid-1
    • 假如比第0位大于or 等于,那就说明left到mid之间的数字肯定是比最小值要大的,同样是因为递增的原因
      • 所以我们可以把left=mid+1
    public int findMin(int[] nums) {

        // 首先是抽象题目

        // 要找到最小的值,由于这个是相对有序的

        // 那么也就是说 >= 最小的值

        // 假设最后一个数字是最小的

        if(nums[0]<=nums[nums.length-1]){

            return nums[0];

        }

  

        int left=0,  right=nums.length-1;

        while(left<=right){

            int mid=left+(right-left)/2;

            if(nums[mid]<nums[0]){

                right=mid-1;

            }else{

                left=mid+1;

            }

        }

        return nums[left];

    }
求平方数

AA

    public boolean isPerfectSquare(int num) {

        int left = 0, right=num;

        while(left<=right){

            int mid=left+(right-left)/2;

            long square = (long) mid * mid;

            if (square < num) {

                left = mid + 1;

            } else if (square > num) {

                right = mid - 1;

            } else {

                return true;

            }

        }

        return false;

    }
寻找旋转数组里面相等的值

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-旋转数组里找数字.png

  • 先说两次二分查找
    • 先找到最小值,这块就用和尾节点、or 开始节点查询即可
    • 然后接lowerBound可破
    • 紧接着是判断target和尾节点的关系
      • 如果target>尾节点,那么一定在起始节点到minINdex间,要么就不存在
      • 反之,一定在minINdex到结尾节点上
    • 这里就是接lowerBound,把上面的区间范围传入即可
  • 再说只用一次查找的写法
    • 核心在于isBlue的判断
      • 如果是翻转数组
        • 如果target>尾节点 且 当前指针比target>=的话,那么右边就是蓝色,right可以右移到mid-1
      • 如果是正向数组
        • 如果target>尾节点
          • 那么一定都是蓝色的,右移
        • 如果当前指针比target大于等于的话,右边也是蓝色
    public int search(int[] nums, int target) {

        int minIndex=findMin(nums);

        int left=0,right=nums.length-1;

  

        // 如果target比起始位置小,那么一定是在minIndex的左边

        // 如果相反的话,则不能确认了,因为翻转的原因,一定在起始位置右边,而且由于翻转无法排除,所以right不能优化

        // 如果是跟结尾节点比呢?

        // 如果比结尾节点大,那么一定是在起始位置到最小值的节点,因为这里才有可能找到比结尾节点还大的值【考虑翻转】

        // 如果是比结尾节点<=,那么就是在尾节点的左边,且在minIndex间

        if(target>nums[nums.length-1]){

            // 如果比起始位置大,那么数字一定是从0开始往右边递增的数字里面

            // 哪怕最小值就是0,那也是0和minIndex间

            left=0;

            right=minIndex;

        }else{

            // 如果比起始位置要小,那就证明这个值一定是在最小值的位置~结尾的地方

            left=minIndex;

            right=nums.length-1;

        }

        return lowerBound(nums, left, right, target);

    }

  

    public int lowerBound(int[] nums,int left, int right, int target){

        int tempL=left;

        int tempR=right;

        while(left<=right){

            int mid=left+(right-left)/2;

            if(nums[mid]<target){

                left=mid+1;

            }else{

                right=mid-1;

            }

        }

        if(left<=tempR && nums[left]==target){

            return left;

        }

        return -1;

    }

  

    public int findMin(int[] nums){

        if(nums[0]<=nums[nums.length-1]){

            return 0;

        }

  

        int left=0, right=nums.length-2;

        while(left<=right){

            int mid=left+(right-left)/2;

            if(nums[mid]<nums[0]){

                right=mid-1;      

            }else{

                left=mid+1;
            }

        }

        return left;

    }
    public int search(int[] nums, int target) {

        int left=0;

        int right=nums.length-1;

        while(left<=right){

            int mid=left+(right-left)/2;

            if(isBlue(nums,target,mid)){

                right=mid-1;

            }else{

                left=mid+1;

            }

        }

        if(left<nums.length && nums[left]==target){

            return left;

        }

        return -1;

    }

  

    public boolean isBlue(int[] nums, int target, int index){

        int end=nums[nums.length-1];

        if(nums[index]>end){

            return target>end && nums[index]>=target;

        }

  

        return target >end || nums[index]>=target;

    }

递归

二叉树最大深度实现方式

AA
对于这类题目 . - 力扣(LeetCode) 二叉树的最大深度

图示:算法题知识点总结-二叉树最大深度题图.png

  1. 我们不需要上来考虑树的结构或者纠结于题目,要学会抽象
  2. 比如这道题目可以转为这样子的思考

    图示:算法题知识点总结-二叉树最大深度子问题.png

  3. 再往下“递“的过程中,总会有尽头,我们要做的就是边界值的定义
  4. 以及在遇到尽头后,怎么把值返回回去,而这就是“归”的过程
    public int maxDepth(TreeNode root) {

        // 定义退出边界值

        if(root==null){

            return 0;

        }

  
        // 求出左边的和 + 右边的和 = 最后输出结果
        int maxLeftDepth=maxDepth(root.left);

        int maxRightDepth=maxDepth(root.right);

        return Math.max(maxLeftDepth, maxRightDepth) +1;

    }

当然也要另外一种思路,就是我们维护一个全局变量,在递归的时候,就把值传下去,每一次递的过程,就对全局变量进行操作维护。

相同的树

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-相同的树题图.png
首先依然是对题目的抽象

  1. 可以抽象为每个节点的左子树是否相同
  2. 以及每个的右子树是否相同
  3. 接下来就是找到尽头,边界值问题
  4. 尽头就是两个子树都走到了相同的节点,我们判断两者的值是否相等

    图示:算法题知识点总结-相同的树实现.png

    public boolean isSameTree(TreeNode p, TreeNode q) {

        if(p==null || q==null){

            return p==q;

        }

        return p.val==q.val &&  isSameTree(p.left,q.left) && isSameTree(p.right,q.right);

    }
轴对称树

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-轴对称树题图.png

  • 首先依然是抽象问题,轴对称
  • 根节点肯定满足可跳过
  • 每一次判断时就是节点的左子树是否与节点的右子树相等
  • 注意:这里一定是传入两个轴对称的树根节点进来判断的
    public boolean isSame(TreeNode left, TreeNode right){

        if(left == null || right==null){

            return left == right;

        }

        return left.val==right.val && isSame(left.left, right.right) && isSame(left.right, right.left);

    }

    public boolean isSymmetric(TreeNode root) {

        return isSame(root.left, root.right);

    }
是否平衡二叉树

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-平衡二叉树图解.png

  • 首先是对问题的抽象
  • 计算左子树的最大深度 以及 右子树的最大深度的绝对差值是否大于1
  • 如果大于1,我们就返回-1,视为树是不平衡的
  • 计算树的高度
    • 计算子树的深度
    • 边界值为null时,代表走到尽头,往回return 1
    • 在归的过程也就是+1,表示深度+1
    • 同时我们需要判断每次计算节点的时候,是否遇到了已经不平衡的情况
    • 如果已经不平衡了,返回-1
    public boolean isBalanced(TreeNode root) {

        return getHeight(root) != -1;

    }

  

    public int getHeight(TreeNode root){

        if(root==null){

            return 1;

        }

        int leftHeight=getHeight(root.left);

        if(leftHeight==-1){

            return -1;

        }

        int rightHeight=getHeight(root.right);

        if(rightHeight==-1){

            return -1;

        }

  

        if(Math.abs(leftHeight-rightHeight)>1){

            return -1;

        }

  

        return Math.max(leftHeight, rightHeight) +1;

    }
前序遍历二叉树

前序遍历实现

AA

  • 外围节点即左子树优先
  • 执行顺序上是
    • 先处理自己
    • 再处理左子树
    • 再处理右子树
      . - 力扣(LeetCode)

      图示:算法题知识点总结-前序遍历二叉树.png

  • 抽象题目开始
  • 每一个节点都应该在一个范围区间,比如往1节点遍历时,则一定在MIN_VALUE到2之间;而3一定在2到MAX_VALUE之间
  • 而每次归的过程,我们可以把范围给代入进行设置,比如1,2;2,3的范围
  • 退出条件为
    • 遍历到null时,返回true
    • 而当位枝节点时,就可以判断值是否在范围内
    public boolean isValidBST(TreeNode root) {

        return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);

    }

  

    public boolean isValidBST(TreeNode root, long left, long right){

        if(root==null){

            return true;

        }

  

        long x=root.val;

        return left < x && x <right && isValidBST(root.left, left, x) && isValidBST(root.right, x, right);

    }

中序遍历

AA

  • 先左子树
  • 再根节点
  • 最后右子树
  • 代码的话,就是先递归左子树,处理自己,递归右子树
    . - 力扣(LeetCode)

    图示:算法题知识点总结-前序遍历二叉树.png

  • 对于中序遍历,即顺序为左节点、根节点、右节点
  • 对于这道题而言
  • 左节点<根节点<右节点; 即每一个节点是大于上一个节点的值
  • 那么退出条件是什么
    • 当节点为空
      • 返回true,因为没有值
    • 当节点的值如果比上一个节点pre的值要大
      • 返回false,说明不满足
  • 递归的动作是
    • 先递归左树
    • 设置值
    • 递归右树
    • 这样子我们的的顺序就永远是
      • 优先最左节点
      • 最左节点的上个节点
      • 上个节点的右节点
        • 右节点又开始了左节点的递归顺序
    long preVal=Long.MIN_VALUE;

    public boolean isValidBST(TreeNode root) {

        if(root==null){

            return true;

        }

  

        if(!isValidBST(root.left)){

            return false;

        }

        if(root.val<=preVal){

            return false;

        }

        preVal=root.val;

        return isValidBST(root.right);

        // return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);

    }

后序遍历

AA
. - 力扣(LeetCode)

  • 后序的话,就是先递归左右子树
  • 再处理节点的值

    图示:算法题知识点总结-前序遍历二叉树.png

  • 抽象问题
  • 后序遍历与前序遍历差别在哪呢?
    • 前序遍历是每次都会先进行判断
      • 在判断的时候,会把判断值的范围往下传
      • 相当于是每次先判断,再递归;【理解为for就好】
    • 而后序遍历是先进行递归
      • 先走到根节点,再把判断值的范围往归的地方传
      • 在收到归传回来的范围后,再进行判断
  • 对于这道题而言
    • 每一个节点都应该大于左子树的最大值以及右子树的最小值
    • 所以每次我们需要返回最大值以及最小值的范围
    public boolean isValidBST(TreeNode root) {

        return dfs(root)[1]!=Long.MAX_VALUE;

    }

  

    public long[] dfs(TreeNode root){

        if(root==null){

            return new long[]{Long.MAX_VALUE, Long.MIN_VALUE};

        }

  

        long[] left=dfs(root.left);

        long[] right=dfs(root.right);

        int x=root.val;

        if(x<= left[1] || x>=right[0]){

            return new long[]{Long.MIN_VALUE,Long.MAX_VALUE};

        }

        return new long[]{Math.min(left[0],x), Math.max(right[1],x)};

    }

二叉树层序遍历

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-二叉树层序遍历.png

  • 维护一个队列,先把当层节点加入
  • 只要队列不为空,就将每一层的左右节点依次压入队列
  • 然后再进行pop
    public List<List<Integer>> levelOrder(TreeNode root) {

        if(root==null)return List.of();

        List<List<Integer>> ans=new ArrayList();

        Queue<TreeNode> q=new ArrayDeque<>();

        q.add(root);

        while(!q.isEmpty()){

            List<Integer> vals=new ArrayList();

            int n=q.size();

            while(n-->0){

                TreeNode node=q.poll();

                vals.add(node.val);

                if(node.left!=null)q.add(node.left);

                if(node.right!=null)q.add(node.right);

            }

            ans.add(vals);

        }

        return ans;

    }
计算最小深度

AA
深度优先法
. - 力扣(LeetCode)

图示:算法题知识点总结-dfs计算最小深度.png

    private int result=Integer.MAX_VALUE;

    public int minDepth(TreeNode root) {

        dfs(root,0);

        return root!=null?result:0;

    }

  

    public void dfs(TreeNode root, int cnt){

        if(root==null){

            return;

        }

  

        cnt++;

        if(root.left==root.right){

            result=Math.min(result,cnt);

            return;

        }

  

        dfs(root.left,cnt);

        dfs(root.right,cnt);

    }
二叉树最近公共祖先

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-二叉树公共节点.png

  • 考虑退出条件
    • null 退出
    • p退出
    • q退出
  • 先找左,再找右
  • 接着输出结果
    • 如果两边都找到了,则当前节点为祖先,return
    • 如果只在一边有,则返回一边

链表

翻转链表

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-翻转链表.png

  • 要翻转,先考虑最简单的
  • 首先要记录已经翻转的,记为pre,最开始的节点是null
  • 然后是需要迭代,cur=head
  • 还需要一个节点记录next,记为nxt=cur.next
  • 接下来就是交换的操作,我们在cur上直接翻转,由于记录了next,所以原地翻转
      1. nxt=cur.next
      1. cur.next=pre
      1. pre=cur;
      1. cur=nxt;
    public ListNode reverseList(ListNode head) {

        ListNode cur=head, pre=null;

        while(cur!=null){

            ListNode nxt=cur.next;

            cur.next=pre;

            pre=cur;

            cur=nxt;

        }

        return pre;

    }
翻转链表Ⅱ

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-翻转链表2.png

  • 先暂存一下head节点,初始化dummy=new ListNode(0,head);
  • 首先要找到头节点left的位置,并记录
    • 比如left=2,相当于从哨兵节点开始遍历left-1次的next
  • 然后我们要翻转2~4之间的节点,还是和之前的步骤一样
    • 初始化pre=null
    • next=cur.next
    • cur.next=pre
    • pre=cur
    • cur=next
  • 这一段翻转完后,此时pre是指向4的,cur是指向5的
  • 最开始哨兵节点dummy以及p0我们是没有动过的
    • p0.next.next 翻转后的节点要指向最新的
    • p0.next 要指向翻转的节点pre
    • return dummy.next
    public ListNode reverseBetween(ListNode head, int left, int right) {

        ListNode dummy=new ListNode(0,head), p0=dummy;

        for(int i=0;i<left-1;i++){

            p0=p0.next;

        }

  

        //reverse

        ListNode pre=null, cur=p0.next;

        for(int i=0;i<right-left+1;i++){

            ListNode nxt=cur.next;

            cur.next=pre;

            pre=cur;

            cur=nxt;

        }

  

        // 链接节点

        p0.next.next=cur;

        p0.next=pre;

        return dummy.next;

    }
k个一组翻转链表

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-k个翻转链表.png

  • 翻转的写法和之前的一样,关键在于每一次要把p0推进到下一组

    图示:算法题知识点总结-翻转链表的debug图.png

  • 输出结果
    public ListNode reverseKGroup(ListNode head, int k) {

        int n=0;

        // 先记录总长度

        for(ListNode cur=head;cur!=null;cur=cur.next){

            n++;

        }

  

        ListNode dummy=new ListNode(0,head),p0=dummy;

        ListNode pre=null, cur=head;

        for(;n>=k;n-=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.next=cur;

            p0.next=pre;

            p0=nxt;

        }

        return dummy.next;

    }
两数相加【迭代版本】

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-两数相加.png

    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {

        ListNode r1=reverseList(l1);

        ListNode r2=reverseList(l2);

        ListNode rr=addTwo(r1,r2);

        return reverseList(rr);

    }

  

    public ListNode addTwo(ListNode l1, ListNode l2){

        ListNode dummy=new ListNode();

        ListNode cur=dummy;

        int carry=0;

        while(l1!=null || l2!= null || carry!=0){

            if(l1!=null){

                carry+=l1.val;

            }

            if(l2!=null){

                carry+=l2.val;

            }

            cur.next=new ListNode(carry%10);

            carry/=10;

            cur=cur.next;

            if(l1!=null){

                l1=l1.next;

            }

            if(l2!=null){

                l2=l2.next;

            }

        }

        return dummy.next;

    }

  

    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;

    }

链表

删除倒数N个节点

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-删除倒数第n个节点.png

  • 倒数第n个节点,我们可以优化成,先让一个节点right走n步
  • 然后再让一个节点left从起点陪同一起出发
  • 当尾节点到最后一个节点时,left就是倒数第n个节点
  • 因为他们之间的n距离就是步长,而步长就是倒数第n个节点
    public ListNode deleteDuplicates(ListNode head) {

        if(head==null){

            return head;

        }

  

        ListNode cur=head;

        while(cur.next!=null){

            if(cur.next.val==cur.val){

                cur.next=cur.next.next;

            }else{

                cur=cur.next;

            }

        }

        return head;

    }
删除出现过重复的链表

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-删除重复元素.png

  • 由于要删掉出现过重复的,即头节点可能被删
  • 被删就要考虑dummy的节点
  • 通过cur指针,判断next和next.next是否一致,如果一致删除,并继续
  • 如果不相等了,则跳过
    public ListNode deleteDuplicates(ListNode head) {

        ListNode dummy=new ListNode(0,head),cur=dummy;

        while(cur.next != null && cur.next.next != null){

            int val=cur.next.val;

            if(val==cur.next.next.val){

                while(cur.next!=null && val==cur.next.val ){

                    cur.next=cur.next.next;

                }

            }else{

                cur=cur.next;

            }

        }

        return dummy.next;

    }

回溯

电话号码的字母组合

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-回溯电话号码.png

  • 回溯是一种增量构造答案的过程,往往我们会用递归来实现
  • 递归我们只需要关注边界条件和非边界条件,然后相信数学归纳即可
  • 回溯三问
    • 当前操作?
    • 子问题?
    • 下一个子问题?
  • dfs(i)+dfs(i+1)
    private static final String[] MAPPING = new String[]{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

  

    private List<String> ans=new ArrayList();

    private char[]digits,path;

  

    public List<String> letterCombinations(String digits) {

        int n=digits.length();

        if(n==0)return List.of();

        this.digits=digits.toCharArray();

        path=new char[n];

        dfs(0);

        return ans;

    }

  

    public void dfs(int i){

        // exit

        // if dfs to end

        if(i==digits.length){

            ans.add(new String(path));

            return;

        }

  

        // iterate all digits then dfs next

        for(char c:MAPPING[digits[i]-'0'].toCharArray() ){

            path[i]=c;

            dfs(i+1);

        }

    }
子集型回溯

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-子集型回溯问题.png

  • 回溯三问
    • 当前操作?
      • 枚举第i个节点选、不选?
    • 子问题?
      • 从下标>=i的数字构造子集
    • 下一个子问题?
      • 从下表>=i+1的数字构造子集
  • dfs(i) + dfs(i+1)
  • 从选or不选考虑
    • 如果不选,则当前数字跳过
    • 如果选,则当前数字要加到路径中,再跳过当前数字
    private final List<List<Integer>> ans = new ArrayList<>();

    private int[] nums;

  

    public List<List<Integer>> subsets(int[] nums) {

        this.nums=nums;

        dfs(0,new ArrayList());

        return ans;

    }

  

    public void dfs(int i , List<Integer> path){

        List<Integer> temp=new ArrayList(path);

  

        if(i==nums.length){

            ans.add(new ArrayList<>(temp));

            return;

        }

  

        // mock no need

        dfs(i+1,temp);

        // mock need

        temp.add(nums[i]);

        dfs(i+1,temp);

    }
    public void dfs(int i ){

        // add every result to ans

        ans.add(new ArrayList(path));

        if(i==nums.length)return;

        // must choose every number

        // then use j>=i

        // add nums[j]

        // next sub question will be j+1

        for(int j=i;j<nums.length;++j){

            path.add(nums[j]);

            dfs[j+1];

            path.remove(path.size()-1);

        }

    }
组合问题

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-组合类问题图.png

    List<List<Integer>> ans=new ArrayList();

    List<Integer> path=new ArrayList();

    private int k;

    public List<List<Integer>> combine(int n, int k) {

        this.k=k;

        dfs(n);

        return ans;

    }

  

    public void dfs(int i){

        // 剪枝,比如一共需要2个数字k,那么就是用需要数字-当前路径=还需要选的数字

        // 如果当前要选择的数字i<要选的数字,那就不需要再选了

        int d=k-path.size();

        // if(i<d){

        //     return;

        // }

  

        // exit 条件,没有要选的,则退出

        if(d==0){

            ans.add(new ArrayList(path));

            return;

        }

  

        // 上面的语句也可以拆到下面来

        for(int j=i;j>=d;j--){

            path.add(j);

            dfs(j-1);

            path.remove(path.size()-1);

        }

    }
   // 选不选的思路
    public void dfs(int i){

        int d=k-path.size();

        // 剪枝

        if(i<d){

            return ;

        }      

  

        // exit

        if(d==0){

            ans.add(new ArrayList(path));

            return;

        }

  

        // 先写主体,使用选or不选

        dfs(i-1);

  

        // 选

        path.add(i);

        dfs(i-1);

        path.remove(path.size()-1);

    }
组合Ⅱ问题

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-组合问题.png

    List<List<Integer>> ans=new ArrayList();

    List<Integer> path=new ArrayList();

    int k,n;

  

    public List<List<Integer>> combinationSum3(int k, int n) {

        this.k=k;

        this.n=n;

        dfs(9, n);

        return ans;

    }

  

    public void dfs(int i, int temp){

        int d=k-path.size();

        // 剪枝

        if(i<d){

            return;

        }

  

        // exit

        if(d==0){

            if(temp==0){

                ans.add(new ArrayList(path));

            }

            return;

        }

  

        // 主体

        // 枚举要选的数字

        for(int j=i;j>=0;j--){

            path.add(j);

            dfs(j-1,temp-j);

            path.remove(path.size()-1);

        }

    }
生成括号组合

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-括号生产.png

class Solution {

    List<String> ans=new ArrayList();

    char[] path;

    int n;

  

    public List<String> generateParenthesis(int n) {

        this.n=n;

        this.path=new char[n*2];

        dfs(0,0);

        return ans;

    }

  

    public void dfs(int i, int open){

        if(i==n*2){

            ans.add(new String(path));

            return;

        }

  

        // 主体

        // 选左还是选右

        // 同时要记录左括号的字符个数n

        // 右括号的个数就为i-n

        if(open<n){

            path[i]='(';

            dfs(i+1,n+1);

        }

        if(i-open<open){

            path[i]=')';

            dfs(i+1,open);

        }

    }

}
组合总和

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-组合总和问题.png

class Solution {

    List<List<Integer>> ans=new ArrayList();

    List<Integer> path=new ArrayList();

    int [] candidates;

  

    public List<List<Integer>> combinationSum(int[] candidates, int target) {

        Arrays.sort(candidates);

        this.candidates=candidates;

        dfs(0, target);

        return ans;

    }

  

    public void dfs(int i, int target){

        //exit

        if(target==0){

            ans.add(new ArrayList(path));

            return;

        }

  

        // cutoff

        if(i>=candidates.length|| candidates[i]>target){

            return;

        }

  

        // for

        for(int j=i;j<candidates.length;j++){

            path.add(candidates[j]);

            // 不需要推进下一个问题,只需要在当前数字,如果处理完了,则回退,通过迭代推进

            dfs(j, target-candidates[j]);

            path.remove(path.size()-1);

        }

    }

}
全排列

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-全排列.png

class Solution {

    List<List<Integer>> ans=new ArrayList();

    List<Integer> path;

    boolean[] onPath;

    int[] nums;

  

    public List<List<Integer>> permute(int[] nums) {

        this.nums=nums;

        path= new ArrayList();

        onPath=new boolean[nums.length];

        dfs(0);

        return ans;

    }

  

    private void dfs(int i){

        // exit;

        if(i==nums.length){

            ans.add(new ArrayList(path));

            return;

        }

  
  
  

        // 挑选数字

        for(int j=0;j<nums.length;j++){

            if(!onPath[j]){

                path.add(nums[j]);

                onPath[j]=true;

                dfs(i+1);

                path.remove(path.size()-1);

                onPath[j]=false;

            }

        }

    }

}
n皇后问题

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-N皇后问题.png

class Solution {

    private int n;

    private int[] col;

    private boolean[] onPath, diag1, dig2;

    private List<List<String>> ans = new ArrayList();

  

    public List<List<String>> solveNQueens(int n) {

        this.n=n;

        col=new int[n];

        onPath=new boolean[n];

        diag1=new boolean[n*2-1];

        dig2=new boolean[n*2-1];

        dfs(0);

        return ans;

    }

  

    private void dfs(int r){

        if(r==n){

            List<String> board=new ArrayList(n);

            for(int c: col){

                char[] row=new char[n];

                Arrays.fill(row,'.');

                row[c]='Q';

                board.add(new String(row));

            }

            ans.add(board);

            return;

        }

  

        for(int c=0;c<n;c++){

            // r -c 代表是行下标与列下标的差值,由于n皇后一定是斜线的,所以rc值一定相等

            // 加上n-1的原因是因为,如果放在右上角位置,此时的值就为n-1,我们给它加回来,就不是负数了

            int rc= r - c + n - 1;

            // 维护几个数组

            // 当前列是否设置过

            // diag1 表示r+c的值

            // dig2 表示r-c的值

            // 只要都没设置过,则我们给它放进去

            if(!onPath[c] && !diag1[r+c] && !dig2[rc]){

                col[r]=c;

                onPath[c] = diag1[r+c]=dig2[rc]=true;

                dfs(r+1);

                onPath[c] = diag1[r+c] = dig2[rc] =false;

            }

        }

    }

}

动态规划

打家劫舍

AA
. - 力扣(LeetCode)

图示:算法题知识点总结-打家劫舍.png

class Solution {

    private int[] nums, memo;

    public int rob(int[] nums) {

        this.nums=nums;

        int n=nums.length;

        memo=new int[n];

        Arrays.fill(memo,-1);

        return dfs(n-1);

    }

  

    private int dfs(int i){

        if(i<0){

            return 0;

        }

        if(memo[i]!=-1){

            return memo[i];

        }

  

        // DP

        // 1. 回溯要怎么写

        // 1.1 入参和返回值

        // 1.2 递归到哪里

        // 1.3 递归边界和入口

        // 2. 记忆化搜索

        // 3. 1:1翻译成递推

        // main opeartion‘

        // 回溯三问

        // 1. 当前i的房子选不选

        // 2. 子问题==》从前i个房子里面得到的最大金额

        // 3. 下一个子问题==》从前i-1个得到的值 or 从前i-2个得到的值

        // 因为不允许相邻,所以dfs(i-2)时是加上当前的nums[i]

        int res=Math.max(dfs(i-1), dfs(i-2) + nums[i]);

        memo[i]=res;

        return res;

    }

}

// 1:1递推
class Solution {

    public int rob(int[] nums) {

        int n=nums.length;

        // int[] f=new int[n+2];

        // for(int i=0;i<n;i++){

        //     f[i+2]=Math.max(f[i+1], f[i]+nums[i]);

        // }

        // return f[n+1];

  

        // 如何把数组优化掉

        int f0 =0, f1 = 0;

        int result=0;

        for(int i=0;i<n;i++){

            result=Math.max(f1, f0+nums[i]);

            f0=f1;

            f1=result;

        }

        return result;

    }

}
爬楼梯

AA

class Solution {

    int n;

    int[] memo;

  

    public int climbStairs(int n) {

        this.n = n;

        memo = new int[n + 1];

        Arrays.fill(memo, -1);

        return dfs(n);

    }

  

    public int dfs(int i) {

        if (i <= 1) {

            return 1;

        }

  

        if (memo[i] != -1) {

            return memo[i];

        }

  

        // 从0爬到i

        // dfs(i) 代表从0到i有多少种方法

        // 如果最后一步爬了1个台阶,要先爬到i-1,则问题变成从0爬到i-1有多少种

        // 如果最后一步爬了2个台阶,要先爬到i-2,则问题变成从0爬到i-2有多少种

        // 由于两个子问题互相独立,我们可以根据加法原理算到一起

        int result = dfs(i - 1) + dfs(i - 2);

        memo[i] = result;

        return result;

    }

}

class Solution {

    public int climbStairs(int n) {

        int[] f=new int[n+2];

        f[0]=f[1]=1;

        // 因为f0 f1 也是我们的答案,所以这里是要算进去的

        for(int i=2;i<=n;i++){

           f[i]=f[i-1]+f[i-2];

        }

        return f[n];

    }

    public int climbStairs(int n) {

        int[] f=new int[n+2];

        f[0]=f[1]=1;

        // 因为f0 f1 也是我们的答案,所以这里是要算进去的

  

        int f0= 1, f1=1;

        for(int i=2;i<=n;i++){

           int temp=f0+f1;

           f0=f1;

           f1=temp;

        }

        return f1;

    }
}
0-1背包

AA
. - 力扣(LeetCode)
有n个物品,第i个物品体积为w[i], 价值为v[i],每个物品至多选一个,求体积和不超过capacity的最大价值和

  • 回溯三问
    • 当前操作:枚举第i个物品选还是不选
      • 选了,则capacity-w[i]
      • 不选,没变化
    • 子问题
      • 在剩余容量为c时,从前i个物品得到的最大价值和
    • 下一个子问题:
      • 不选:
        • 在生剩余容量为c时,从前i-1个得到的最大价值和选
      • 选
        • 在剩余c-w【i】,从前i-1 个物品得到最大价值和
class Solution {

    private int[] nums;

    private int[][] cache;

  

    public int findTargetSumWays(int[] nums, int target) {

        // 假设正数的和为p

        // 那么负数的和为 所有元素的数和s - p

        // 我们希望正数的和-负数的绝对值和 p - (s-p) = target

        // 2p - s = target

        // p = (s+target)/2;

        // 这样子我们就变成从nums的数组中,我们要挑选p个正数出来

        for(int x:nums) target+=x;

        // 如果数组为负数,且最后是奇数,那么一定不会有结果

        if(target<0 || target%2==1)return 0;

        target/=2;

  

        this.nums=nums;

        int n=nums.length;

        // 我们要挑选p个正数,让它等于target

        // n-1,是为了要获取数组里面的值

        return  dfs(n-1, target);

    }

  

    private int dfs(int i, int target){

        // 如果遍历完了,且target刚好等于0,那么就是正数,不然就不选

        if(i<0) return target==0?1:0;

        // 如果当前元素大于target,跳过不选

        if(target<nums[i])return dfs(i-1, target);

        // 从0到i,就代表了有多少种方法可以恰好等于target

        // 子问题思考

        // 如果最后一步我们选得是正数,则target-=i

        // 如果是负数,不处理

        // 由于这两个方案是独立的,我们可以按加法原理相加

        return dfs(i-1, target)+ dfs(i-1, target-nums[i]);

    }



}

class Solution {

    public int findTargetSumWays(int[] nums, int target) {

        // p

        // s-p

        // p - (s-p) =t

        // p=(t+s)/2

        // 要选n个p数字处理

        for(int x:nums){

            target+=x;

        }

        if(target<0 || target%2==1)return 0;

        target/=2;

  

        int n=nums.length;

        int[][] f= new int[n+1][target+1];

        // 当i=0时,且target=0了,那么就是要选正数

        f[0][0] = 1;

        for(int i=0;i<n;i++){

            for(int c=0;c<=target;c++){

                // 如果当前数字比target小,跳过不选

                if(c<nums[i]) f[i+1][c]=f[i][c];

                else f[i+1][c] = f[i][c] + f[i][c-nums[i]];

            }

        }

        return f[n][target];

    }

}
单词划分

. - 力扣(LeetCode)

class Solution {

    public boolean wordBreak(String s, List<String> wordDict) {

        Set<String> wordDictSet=new HashSet(wordDict);

        boolean[] dp=new boolean[s.length()+1];

        dp[0]=true;

        // 动归思想,把所有的标题都试一遍

        // 每次都测试i,j间的字符串是否满足

        // dp数组表示的是切分开来的每一个小数组

        // 对于dp0 来说,就代表0,1字符是否在dict中

        // 比如leetcode的词组

        // 当i=3,j=0的时候,匹配上了,dp[i]=true,那么接下来只要看dp[i+1]开始的小数组能不能满足

        // 当j=3, i=7的时候,匹配上了,dp[j]之前就是true的,继续设置dp[i]=true,那么此时dp[结尾数组]已经满足了

        for(int i=1;i<=s.length();i++){

            for(int j=0;j<i;j++){

                if(dp[j] && wordDictSet.contains(s.substring(j,i))){

                    dp[i]=true;

                    break;

                }

            }

        }

        return dp[s.length()];

    }

}
零钱兑换

. - 力扣(LeetCode)

class Solution {

    int[] coins;

    int[][]memo;

    public int coinChange(int[] coins, int amount) {

        int n=coins.length;

        this.coins=coins;

        memo=new int[n][amount+1];

        for(int[] row:memo){

            Arrays.fill(row,-1);

        }

        int ans=dfs(n-1,amount);

        return ans<Integer.MAX_VALUE/2?ans:-1;

    }

  

    public int dfs(int i,int c){

        // 作为一个剪枝内容进行透出

        if(i<0){

            return c==0?0:Integer.MAX_VALUE/2;

        }

        // 记忆数组

        if(memo[i][c]!=-1)return memo[i][c];

  

        // 剪枝

        if(c< coins[i])return memo[i][c]=dfs(i-1,c);

        // 第i个硬币选还是不选

        // 子问题是 选or不选最小组合是什么

        // 如果不选,那就跳到下一个硬币进行组合

        // 如果选,那么就将target减去硬币组合,并且组合数+1

        // 初始化memo数组,二元是为了作为完全背包的完全记忆模式,比如选了XX硬币时,当前target的组合是多少

        int result= Math.min(dfs(i-1,c),dfs(i,c-coins[i])+1);

        memo[i][c]=result;

        return result;

    }

}

字符串类

两个超大的小数的相加
   public static String sumTwoLongNumber(String num1, String num2) {
        int len1 = num1.length() - 1;
        int len2 = num2.length() - 1;

        int add = 0;
        StringBuilder ans = new StringBuilder();
        while (len1 >= 0 || len2 >= 0 || add != 0) {
            int i = len1 >= 0 ? num1.charAt(len1) - '0' : 0;
            int j = len2 >= 0 ? num2.charAt(len2) - '0' : 0;
            int sum = i + j + add;
            ans.append(sum % 10);
            add = sum / 10;
            len1--;
            len2--;
        }

        return ans.reverse().toString();
    }

    public static void main(String[] args) {
        String a = "1233.512142";
        String b = "123.124124124";

        String mainSum = sumTwoLongNumber(a.split("\\.")[0], b.split("\\.")[0]);
        String subString1 = a.split("\\.")[1];
        String subString2 = b.split("\\.")[1];

        int maxLen = Math.max(subString1.length(), subString2.length());
        subString1 = padRightZeros(subString1,maxLen);
        subString2 = padRightZeros(subString2,maxLen);
        String subSum = sumTwoLongNumber(subString1, subString2);
        if (subSum.length() > a.length() && subSum.length() > b.length()) {
            mainSum = sumTwoLongNumber(mainSum, "1");
            subSum = subSum.substring(1);
        }
        System.out.println(mainSum + "." + subSum);
    }

    public static String padRightZeros(String str, int length) {
        if (str.length() >= length) {
            return str;
        }
        return str + "0".repeat(length - str.length());
    }
判断字符串有没有重复的字母

面试题 01.01. 判定字符是否唯一 - 力扣(LeetCode)

class Solution {

    public boolean isUnique(String astr) {

        if(astr==null || astr.equals("")){

            return true;

        }

  

        for(int i=0;i<astr.length()-1;i++){

            for(int j=i+1;j<astr.length();j++){

                if((astr.charAt(i)) != (astr.charAt(j))){

                    continue;

                }else{

                    return false;

                }

            }

        }

        return true;

    }

}

正确写法

class Solution {

    public boolean isUnique(String astr) {

        if(astr==null || astr.equals("")){

            return true;

        }

  

        int result=0;

        for(int i=0;i<astr.length();i++){

            int move=astr.charAt(i)-'a';

            int temp=1<<move;

            if((result & temp)!=0){

                return false;

            }else{

                result = result | (1<<move);

            }

        }

        return true;

    }

}
找字符串里面最大嵌套的括号深度

1614. 括号的最大嵌套深度 - 力扣(LeetCode)

class Solution:

    def maxDepth(self, s: str) -> int:

        ans, size=0,0

        for ch in s:

            if ch =='(':

                size=size+1

                ans=max(size,ans)

            if ch ==')':

                size=size-1

        return ans
class Solution {

    public int maxDepth(String s) {

        int result=0;

        int countLeft=0;

        for(int i=0; i<s.length();i++){

            char c=s.charAt(i);

            if(c=='('){

                countLeft++;

                result = Math.max(result, countLeft);

            }

            if(c==')'){

                countLeft--;

            }

            continue;

        }

        return result;

    }

}

贪心

跳跃算法

45. 跳跃游戏 II - 力扣(LeetCode)

class Solution {

    public int jump(int[] nums) {

        //  i 需要几次

        int jumps=0, curEnd=0, farthest=0;

        // 最后一位不需要处理,肯定在前面就能跳到位置了

        for(int i=0;i<nums.length-1;i++){

            farthest=Math.max(farthest, i+nums[i]);

            if(i==curEnd){

                // 必须跳

                jumps+=1;

                curEnd = farthest;

            }

        }

        return jumps;

    }

}

排序

计数排序

274. H 指数 - 力扣(LeetCode)

class Solution {

    public int hIndex(int[] citations) {

        int n = citations.length;

        int[] counter = new int[n+1];

        for(int c: citations){

            if(c>=n){

                counter[n]++;

            }else{

                counter[c]++;

            }

        }

  

        int total=0;

        // 核心原理是,我先统计引用次数分别有多少个,其中大于数组n的都记为n

        for(int i=n;i>=0;i--){

            total += counter[i];

            // 然后倒过来统计,比如都大于等于n,就相当于五篇论文都被统计了5次以上,则为5

            // 假如不满足,则再往下看4有多少个,加上刚刚5的够不够现在的合计四篇

            if(total>=i){

                return i;

            }

        }

        return 0;

    }

}

算法题常用工具类和方法

AA

  • Queue
    • ArrayDeque
  • Collections.reverse
  • Integer.MAX_VALUE

相关文档和插件

  • 代码可视化:Online Java Compiler, Visual Debugger, and AI Tutor - Learn Java programming by visualizing code
  • b站算法up主:看到递归就晕?带你理解递归的本质!_哔哩哔哩_bilibili
  • 二叉树遍历:http://www.hangdaowangluo.com/archives/2979
  • 算法题目单:leetcode.cn/circle/discuss/SqopEo/
leetcode题目详细解答【java版】持续更新
/archives/96d7c0a1/
作者
tyrantqiao
发布于
2018-10-18
更新于
2023-07-09
许可协议
CC BY-NC-SA 4.0
赏

蟹蟹大佬的打赏,大家一起进步

支付宝
微信
  • Java
  • 算法

扫一扫,分享到微信

微信分享二维码
ClassLoader源码解析
HttpTCP系列第一章
© 2026 tyrantqiao 本站总访问量次 本站访客数人次 载入天数...载入时分秒...
  • 文章检索
  • 作品与友邻
  • 关于我

展开标签

  • 随笔
  • 旅行
  • Java
  • 面经
  • 源码
  • 计算机网络
  • 技术
  • 架构
  • Hadoop
  • IDEA
  • maven
  • JDK
  • Linux
  • MySQL
  • 性能
  • 数据库
  • Redis
  • MQ
  • Spring
  • SquirrelFin
  • 记账
  • Android
  • 个人财务
  • unity
  • TIDB
  • chatgpt
  • 前端
  • Vue
  • Git
  • Hexo
  • shadowsocks
  • 问题排查
  • 业务
  • JVM
  • 算法
  • nginx
  • ssh
  • Python
  • 爬虫
  • test
  • vim
  • 影视剧
  • 中间件
  • 事务
  • 表达
  • 运动
  • mybatis
  • AI
  • 产品
  • 家装
  • 土巴兔
  • 域名
  • github
  • 软件
  • 个人项目
  • Leaves
  • Chrome
  • 浏览器插件
  • 开源
  • Balance Voice
  • 穿搭
  • 幽默
  • 画画
  • 缓存
  • 摄影
  • c
  • 课程设计
  • 调酒
  • 设计模式
  • 数学

Collection

作品与友邻

我做的小工具、朋友的站点和一些长期值得回访的灵感来源。

  • 土 土巴兔 AI 导购交互演示 可点击体验用户导购、平台运营、装修公司、材料商及 AI 话术知识库的多端增长流程。 /demos/tubatu-ai-guide/
  • L Leaves 行程记录 我做的行程记录页面小工具,用来沉淀旅行路线、日程安排和途中的灵感。 https://leaves.tyrantqiao.com/
  • 本 本网站主题yilia设计者的主页 http://litten.me/
tyrantqiao的头像

tyrantqiao

github rss qq mail facebook linkedin

About

关于我

代码、海浪和持续折腾,都是生活的一部分。

<div class="aboutme-card"> <p class="aboutme-eyebrow">tyrantqiao</p> <h3>Java 全栈开发</h3> <p>常年和 Java、后端服务、前端工程、数据与部署打交道,喜欢把复杂问题拆开、写清楚,也把踩坑过程整理成文章留给后来的人。</p> <div class="aboutme-tags"> <span>Java</span> <span>Full Stack</span> <span>Backend</span> <span>Frontend</span> <span>Extreme Sports</span> </div> <p>工作之外偏爱冲浪等极限运动,享受在浪上找平衡,也享受在代码里找边界。</p> <div class="aboutme-contact"> <a href="mailto:[email protected]">Gmail</a> <a href="mailto:[email protected]">QQ Mail</a> </div> </div>