LeetCode100题总结

LeetCode100题总结

  • 前言
  • LeetCode100题总结
    • 题型梳理
    • 双指针
      • 11. 盛最多水的容器
      • 234.回文链表
      • 75.颜色分类
      • 206.反转链表
      • 142.环形链表2
      • 15.三数之和
    • 滑动窗口
      • 3. 无重复字符的最长子串
      • 209. 长度最小的子数组
      • 438. 找到字符串中所有字母异位词
    • 广搜
      • 102. 二叉树的层序遍历
      • 200. 岛屿数量
      • 617.合并二叉树
    • 深搜
      • 104.二叉树的最大深度
      • 543.二叉树的直径
      • 111. 二叉树的最小深度
      • 110.平衡二叉树
      • 226.翻转二叉树
    • 回溯
      • 39.组合总和
      • 77.组合
      • 46.全排列
      • 79.单词搜索
      • 22.括号生成
      • 17.电话号码的字母组合
    • 排序
        • 二分
        • 704.二分查找
          • 35.搜索插入位置
    • 动态规划
      • 基础题目
        • 509.斐波那契数
        • 70.爬楼梯
        • 746.使用最小花费爬楼梯
        • 62.不同路径
        • 63.不同路径2
        • 343.整数拆分
        • 96.不同的二叉搜索树
        • 343.整数拆分
        • 96.不同的二叉搜索树

前言

这是陈旧已久的草稿2022-10-20 00:07:01发布一下

这个是我的同学自己写的算法记录,我看了一看。
当时,准备寒假实习,后面也没有找到。

现在2024-5-12 22:06:18,发布到[LeetCode]专栏中。

LeetCode100题总结

题型梳理

在这里插入图片描述

双指针

思想:应对一组数据的走向有两种变化方案:向左或向右

通过不断改变左右边界来去最优解。

类型题目解决方案:
找到适合使用双指针的遍历点 + 找到适合题目的最优解方案的底层逻辑。

难点在于如何找到合适的底层逻辑,很多结果数据的处理很棘手。

11. 盛最多水的容器

这个题的底层逻辑:判断左移还是右移使得容量变大,从底层逻辑来看,纯纯数学问题。

我的想法:用假设发生的结果来决定接下来真实发生什么

题目想法:用真实发生的结果来决定接下路真实发生什么

我的逻辑用假设发生的结果来决定接下来真实发生什么
题解的逻辑用真实发生的结果来决定接下路真实发生什么
我的问题切入点先判断左移、右移、同时移的影响,来决定接下来如何处理左右指针
题解的问题切入点根据已知的两个指针所指结果,只改变两指针中数组值小的。
缺点(我)考虑的情况要有6种,而且假设移动的写法,得保证数组不能越界。很难处理,没写出来。
优点(题解)设计很巧妙,也比较难想这个问题的思考角度,需要较深的数学水平。
  • 暴力解法:O(n^2)

    /*
    思路:暴力解法
    两层遍历,依次算出对应的面具area。比较选出最大的返回
     */
    
    class Solution {
        public int maxArea(int[] height) {
            int fro,last,area;
            fro=0;
            area=0;
            last=height.length;
            for (int i=0;i<height.length;i++)
            {
                for (int j=i+1;j<height.length;j++)
                {
                    area=Math.max(area,Math.min(height[i],height[j])
                            *(j-i));
                }
            }
            return area;
        }
    }
    
  • 双指针解法(我的)

     /*
        影响水体积的因素有: 所选数组的长度 & 所选数组所能决定的高度
        数组的长度是第一时间好确定的,改变长度的方式有两种,左边界+1 or 右边界—1,
        因为存在这两种变化,所以采用双指针的思想。
    
        不断去找适合本题的指针移动逻辑,    采用对的方法+找到每个题的逻辑=ac
        */
        public  int maxArea(int[] height) {
            int  n=height.length;
            int left=0;
            int right=n-1;
    
            //用dp0,dp2/dp1来帮助实现比较逻辑
            int dp0,dp1,dp2;
            dp0=Math.min(height[left],height[right]);
            int area=dp0*(right-left);
            if(n==2)
                return area;
            while(left!=right){
                //6种情况
                //a>b>c
                //area(height,left+1,right),area(height,left,right-1),area(height,left+1,right-1)
                if (left+1<n&&right-1>0&&
                        area(height,left+1,right)==max3(area(height,left+1,right),
                        area(height,left,right-1),
                        area(height,left+1,right-1)))
                {
                    left++;
                    area=Math.max(area,area(height,left+1,right));
                }
                else  if (left+1<n&&right-1>0&&
                        area(height,left,right-1)==max3(area(height,left+1,right),
                        area(height,left,right-1),
                        area(height,left+1,right-1)))
                {
                    right--;
                    area=Math.max(area,area(height,left,right-1));
                }
                else  if (left+1<n&&right-1>0&&
                        area(height,left+1,right-1)==max3(area(height,left+1,right),
                        area(height,left,right-1),
                        area(height,left+1,right-1)))
                {
                    left++;
                    right--;
                    area=Math.max(area,area(height,left+1,right-1));
                }
                else
                    left++;
            }
            return area;
        }
    
        private int area(int []h,int l,int r){
            return (r-l)*(Math.min(h[l],h[r]));
        }
        //三数最大值
        private int max3(int a,int b,int c){
            if (a>b){
                if (c>a)
                    return c;
                else
                    return a;
            }
            return b;
        }
    
        public static void main(String[] args) {
            int a[]={2,3,4,5,18,17,6};
            int b=new Solution().maxArea(a);
            System.out.println(b);
        }
    }
    
  • 双指针:O(n)

    /*
    思路
    如何看待这个问题?
    可以将我们要计算的数字公式转化为直观的物理意义-面积,控制这个几何图形面积的因素有两个:底、高
    我们可以用两个指针来底因素,左指针指向最左,右指针指向最右。
    通过控制指针的移动来直接改变底,间接改变高,那该如何控制这个指针走向。
    分析可得:
    假设我们的左指针移动了,那么我们的底肯定小了,如果这时候高返回小了,那就没有移动的必要了,这样只让area越来越小。
    所以可以确定的一点是,指针移动势必会让底变小,想尽快找到答案就必须让高适当增高!
    那我们该如何控制高的问题呢?
    本题而言,高这个因素是由最小的hight决定的,那明确了这个点好办了,
    我们通过简单的判断高的问题,来控制左右指针的走向,指针发生移动之后,就可以通过计算面积,取对应的最大值来进行下一步的判断了,
    当整个循环走完的时候,就我们的问题找出答案的时候。
     */
    public class Demo01 {
        public int maxArea(int[] height) {
            int f,l,area;
            f=0;
            area=0;
            l=height.length-1;
            while (f<l)
            {
                //Math.min(height[f],height[l])表示几何图形的高
                area=Math.max(area,Math.min(height[f],height[l])
                    *(l-f));
                if(height[f]<height[l])
                {
                    f++;
                }
                else  l--;
            }
    //        for (int i=0;i<height.length;i++)
    //        {
    //            for (int j=i+1;j<height.length;j++)
    //            {
    //                area=Math.max(area,Math.min(height[i],height[j])
    //                *(j-i));
    //            }
    //        }
    
            return area;
        }
    
        public static void main(String[] args) {
            int []a={1,8,6,2,5,4,8,3,7};
            Demo01 demo01 = new Demo01();
            demo01.maxArea(a);
            System.out.println(demo01.maxArea(a));
        }
    
    }
    
  • 总结:

    感觉这个移动有点博弈论的味了,每次都移动自己最差的一边,虽然可能变得更差,但是总比不动(或者减小)强,动最差的部分可能找到更好的结果,但是动另一边总会更差或者不变,兄弟们,这不是题,这是人生,逃离舒适圈!!
    

234.回文链表

  • 双指针切入点:数组的双指针遍历

  • 本题的应用逻辑,将链表转化成双指针,变成了常见的双指针遍历问题。

  • 双指针:

    	public boolean isPalindrome(ListNode head) {
            List<Integer> list=new ArrayList<>();
            while (head!=null){
                list.add(head.val);
                head=head.next;
            }
            int n=list.size();
            int front=0;
            int last=n-1;
            while (front<last){
                if (list.get(front)!=list.get(last))
                    return false;
                front++;
                last--;
            }
            return true;
        }
    

75.颜色分类

也叫荷兰国旗问题 -Dijkstra提出的

  • 冒泡排序:

    时间复杂度:O(n^2)

     //插入排序
       public void sortColors(int[] nums) {
            int temp=0;
            for (int i=0;i<nums.length;i++){
                for (int j=0;j<nums.length-1;j++){
                    if (nums[j]>nums[j+1])
                    {
                        temp=nums[j+1];
                        nums[j+1]=nums[j];
                        nums[j]=temp;
                    }
                }
            }
        }
    
  • 两次单指针

    时间复杂度为O(n),在遍历外部加一个指针,遍历中进行交换,如果交换指针递增。
    逻辑:先让0放在数组的前面,再让1放在0的后面。

    //两次单指针
        public void sortColors(int[] nums){
            int ptr=0;
            int temp=0;
            for (int i=0;i<nums.length;i++){
                if (nums[i]==0){
                    temp=nums[i];
                    nums[i]=nums[ptr];
                    nums[ptr]=temp;
                    ptr++;
                }
            }
            for (int i=0;i<nums.length;i++){
                if (nums[i]==1){
                    temp=nums[i];
                    nums[i]=nums[ptr];
                    nums[ptr]=temp;
                    ptr++;
                }
            }
        }
    
  • 双指针

    需要开辟一个新的数组空间,复杂度为O(n)
    本质上还是是将0放在前面,2放在后面

    public static void sortColors(int[] nums){
            int b[]=new int[nums.length];
            for (int i = 0; i < nums.length; i++) {
                b[i]=1;
            }
            int front=0;
            int back=nums.length-1;
            for (int i=0;i<nums.length;i++){
                if (nums[i]==0) {
                    b[front] = 0;
                    front++;
                }
                if (nums[i]==2) {
                    b[back] = 2;
                    back--;
                }
            }
            for (int i=0;i<nums.length;i++){
                nums[i]=b[i];
            }
        }
    

206.反转链表

  • 利用栈先进后出的特性,这处理前后反序的问题。

    class Solution {
     public ListNode reverseList(ListNode head) {
            Stack<ListNode> stack=new Stack<ListNode>();
            ListNode top=head,tail,pre;
            while(top!=null){
                stack.push(top);
                top=top.next;
            }
            if (stack.isEmpty())
            return null;
            tail=stack.pop();
            pre=tail;
            while (true){
                if (!stack.empty()) {
                    pre.next = stack.pop();
                    pre = pre.next;
                }
                else break;
            }
            pre.next=null;
            return tail;
        }
    }
    
    
  • 迭代

    创建一个前驱结点pre:
    迭代过程记录一个后继结点n,
    让当前结点指向前驱结点,让当前结点变成后继结点。
    
    public static  ListNode reverseList1(ListNode head) {
            if(head==null)
                return null;
            ListNode pre=null;
            ListNode cur=head;
            while(cur!=null){
                ListNode n=cur.next;
                cur.next=pre;
                pre=cur;
                if (n==null)
                    return cur;
                cur=n;
            }
            return cur;
        }
    
  • 递归

    和迭代的思想一样,能迭代的一般都能递归,写出来练练手。

    public static  ListNode reverseList(ListNode head){
            ListNode pre=null;
            ListNode cur=head;
            return method(pre,cur);
    
        }
        private static ListNode method(ListNode pre,ListNode cur){
            if (cur.next==null) {
                cur.next=pre;
                return cur;
            }
            ListNode nextnode=cur.next;
            cur.next=pre;
            return method(pre,nextnode);
    
        }
    

142.环形链表2

  • 用栈写:

    时间复杂度:O(n^2)

    空间复杂度:O(n)

     public ListNode detectCycle(ListNode head) {
            Stack<ListNode> stack=new Stack<>();
            while (head!=null){
                if (stack.contains(head))
                    return head;
                else
                    stack.add(head);
                head=head.next;
            }
            return null;
        }
    
  • 双指针方法

    了解一下思路得了,这个问题是采用了快慢指针的模型,通过两次相遇来确定进入环的入口。开扩一下思路就好。

  • 哈希(时间优话,空间复杂)

    这个比较实在,栈的时间复杂度是O(n^2),可以用哈希优化成O(n)

    public ListNode detectCycle(ListNode head) {
            Set<ListNode> set=new HashSet<>();
            while (head!=null){
                if (set.contains(head))
                    return head;
                else
                    set.add(head);
                head=head.next;
            }
            return null;
        }
    

15.三数之和

这个题目跟两数之和有点像,介于三数和为0 ,遍历第一个数,只需要找到接下来范围内两数和为-绝对值第一个数即可。

  • 双指针

    对于两数之和部分用双指针,能使时间复杂度从O(n^3)降到O(n方)

    List<List<Integer>> res=new ArrayList<>();
        public List<List<Integer>> threeSum(int[] nums) {
            if (nums.length<3)
                return null;
            Arrays.sort(nums);
            if (nums[0]>0)
                return res;
            for (int i=0;i<nums.length-2;i++){
                int begin=i+1;
                int end=nums.length-1;
                while (begin<end){
                    if (nums[begin]+nums[end]<Math.abs(nums[i]))
                        begin++;
                    else if (nums[begin]+nums[end]>Math.abs(nums[i]))
                        end--;
                    else if (nums[begin]+nums[end]==Math.abs(nums[i])){
                        List<Integer> list=new ArrayList<>();
                        list.add(nums[i]);
                        list.add(nums[begin]);
                        list.add(nums[end]);
                        Collections.sort(list);
                        if (!res.contains(list))
                            res.add(list);
                        begin++;
                        end--;
                    }
                }
            }
            return res;
        }
    
    解法分析:
    难度在于如何去除重复解。
    这种处理是采用排序数组,再检验是否res集中是否存在这样的数组。
    大大影响实际复杂度。数组排序O(N logN),遍历数组O(n),双指针遍历O(n)
    
    优化方向:
    既然找到了慢的原因是处理重复结果导致的,那就从这个方向开始优化,
    我是从数组生成的时候进行筛选的,如果从源头筛选,就能优化很多时间。
    
  • 双指针(优化版)

    2030ms到18ms的巨大飞跃

    从重复的原因角度出发,处理了两个点,即第一次遍历时重复情况,第二次双指针出现重复。大大减少时间负担

    优化部分:

     //第一次遍历到大于0的就不用继续走了.
     if(nums[i]>0)
                return res; 
    //如果有连着两个 直接用一个
                    if(i > 0 && nums[i] == nums[i-1])
      //优化了,如果连着挨着的一样,那就跳过。
                        while (begin<end&&nums[begin+1]==nums[begin])
                            begin++;
                        while (begin<end&&nums[end-1]==nums[end])
                            end--;
    

    优化完整版:

    List<List<Integer>> res=new ArrayList<>();
        public List<List<Integer>> threeSum(int[] nums) {
            if (nums.length<3)
                return null;
            Arrays.sort(nums);
            if (nums[0]>0)
                return res;
            for (int i=0;i<nums.length;i++){
                if(nums[i]>0)
                return res; 
    
                //如果有连着两个0直接用一个
                if(i > 0 && nums[i] == nums[i-1])
                    continue;
                int begin=i+1;
                
                int end=nums.length-1;
                while (begin<end){
                   
                    if (nums[begin]+nums[end]<Math.abs(nums[i]))
                        begin++;
                    else if (nums[begin]+nums[end]>Math.abs(nums[i]))
                        end--;
                    else if (nums[begin]+nums[end]==Math.abs(nums[i])){
                        List<Integer> list=new ArrayList<>();
                        list.add(nums[i]);
                        list.add(nums[begin]);
                        list.add(nums[end]);
                        res.add(list);
                        //优化了,如果连着挨着的一样,那就跳过。
                        while (begin<end&&nums[begin+1]==nums[begin])
                            begin++;
                        while (begin<end&&nums[end-1]==nums[end])
                            end--;
                        begin++;
                        end--;
                    }
                }
            }
            return res;
        }
    

滑动窗口

其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!

3. 无重复字符的最长子串

  • 暴力解法:

    时间复杂度:On方

    空间复杂度:On

    超时

    public int lengthOfLongestSubstring(String s) {
            int count=0;
            for (int i=0; i<s.length();){
                Set<Character> set=new HashSet<>();
                for (int j=0;j<s.length();j++)
                if (!set.contains(s.charAt(j)))
                    set.add(s.charAt(j));
                else {
                    count=count>set.size()? count:set.size();
                    break;
                }
            }
            return  count;
        }
    
  • 双指针

    减少了从头遍历的情况,时间上优化了不少,但还有优化的空间,这里面的left指针没怎么用上。

     public int lengthOfLongestSubstring(String s) {
            int left,right;
            left=0;
            right=0;
            int count=0;
            List<Character> list=new ArrayList<>();
            while (left<=right&&right<s.length()&&left>=0){
                if (!list.contains(s.charAt(right))) {
                    while (true) {
                        if (right<s.length()&&!list.contains(s.charAt(right))) {
                            list.add(s.charAt(right));
                            right++;//right越界问题
                        }
                        else {
                            count=Math.max(count,list.size());
                        
                            break;
                        }
                    }
                }
                else {
                    while (list.contains(s.charAt(right))){
                        list.remove(0);
                    }
                    left++;
                }
            }
            return count;
        }
    
  • 双指针优化版

    while (list.contains(s.charAt(right))){
    list.remove(0);
    }

    优化开始的地方,这个采用删除想要数组的索引之前的元素,然后统计数组个数的策略来返回最大值。

    改为用hashmap来存储索引和字符,用字符来确定索引,从而确定左指针的值!

     public int lengthOfLongestSubstring(String s) {
            int left,right;
            left=0;
            right=0;
            int count=0;
            Map<Character,Integer> map=new HashMap<>();
            List<Character> list=new ArrayList<>();
            for (int i = 0; i <s.length() ; i++) {
                if (!map.containsKey(s.charAt(i))) {
                    map.put(s.charAt(i), i);
                    //right越界问题
                }
                else{
                    left=Math.max(map.get(s.charAt(i))+1,left);
                    map.put(s.charAt(i), i);
                }
                count=Math.max(i-left+1,count);
            }
            return count;
        }
    

209. 长度最小的子数组

做懂了第三题,再做类似的题有种酣畅淋漓的感觉。贼爽。

滑动窗口

public int minSubArrayLen(int target, int[] nums) {
        int left=0;
        int len=Integer.MAX_VALUE;
        int sum=0;
        for (int i=0;i<nums.length;i++){
            sum+=nums[i];
            while (sum>=target){
                len=Math.min(len,i-left+1);
                sum-=nums[left];
                left=left+1;
            }
        }
        len=len==Integer.MAX_VALUE? 0:len;
        return  len;
    }

438. 找到字符串中所有字母异位词

标准长度的滑动窗口,比较简单,难点在于处理是否是异位词这个点,如果能想到分割子串、排序、再比较的方式就更简单了,

就是排序比较的过程加长了时间复杂度。

  • 滑动窗口

     public List<Integer> findAnagrams(String s, String p) {
            List<Integer> list= new ArrayList<>();
            if (s.length()<p.length())
                return list;
            if (p.length()==0)
                return list;
            int n=p.length();
            char[] sp=p.toCharArray();
            Arrays.sort(sp);
            for (int i=0;i<=s.length()-p.length();i++){
                char[] ss=s.substring(i,i+n).toCharArray();
                Arrays.sort(ss);
                if (xiangdeng(ss,sp)) 
                    list.add(i);
            }
            return list;
        }
        public boolean xiangdeng(char[] a,char[] b){
            boolean f=true;
            for (int i=0;i<b.length;i++){
                if (a[i]!=b[i])
                    return false;
           
            } return f;
        }
    

广搜

按照数据结构中广搜的定义,bfs也是遍历的一种思想,核心策略采用当前节点能走的全都走。

102. 二叉树的层序遍历

  • bfs:

    这里有一行错误代码,i <queue.size() 。

    错误原因,循环体内容要操作queue的size,动态变化的size不适合做循环判断条件

    /*
    102. 二叉树的层序遍历
    
    思路:
    这个题跟基础的算法层序遍历很像,基础算法可以用层序遍历,但区别于层序遍历,我们
    缺少对返回值List<List<Integer>>这个条件的控制,直接不分组的sout无法满足这个题的需求。
    
    针对这个需求有以下实现思路:
    1.通过进队列的时候我们放入list集合,可以用类似于之前的后序遍历,采用双栈,我想用一个队列
    加一个栈,队列来控制层序遍历,栈来控制加入list,但是每写出来。
    2.可以通过栈当前数量来加入,我们肯定要加入集合中,但是加入几个是个问题,获取队列的长度来
    代表我们加入集合的个数。每次加完一个集合都代表栈对应着更新一次。同步对应。很巧妙的设计。
     */
    public List<List<Integer>> levelOrder(TreeNode root) {
            List<List<Integer>> res=new ArrayList<>();
            if (root==null)
                return res;
            Queue<TreeNode> queue=new LinkedList<>();
            queue.add(root);
            while (!queue.isEmpty()){
                List<Integer> list=new ArrayList<>();
                int count=queue.size();
                for (int i = 0; i<count ; i++) {
                     TreeNode node=queue.poll();
                    list.add(node.val);
                    if (node.left!=null)
                        queue.add(node.left);
                    if (node.right!=null)
                        queue.add(node.right);
                }
                res.add(list);
            }
            return  res;
        }
    

200. 岛屿数量

bfs和递归结合,分为两组逻辑,进入逻辑和递归逻辑

递归逻辑,这片岛为1的全变成2

进入逻辑,遍历到当前为1的情况进入递归,让这片都为2 计数++;

  • bfs+递归

    /*
    200. 岛屿数量
    输入:grid = [
      ["1","1","1","1","0"],
      ["1","1","0","1","0"],
      ["1","1","0","0","0"],
      ["0","0","0","0","0"]
    ]
    输出:1
    思路:遍历岛这个二维数组,如果当前数为1,则进入感染函数并将岛个数+1
    感染函数:其实就是一个递归标注的过程,它会将所有相连的1都标注成2。
    为什么要标注?这样就避免了遍历过程中的重复计数的情况,一个岛所有的1都变成了2后,
    遍历的时候就不会重复遍历了。
     */
    
     public static int numIslands(char[][] grid) {
            int row=grid.length;
            int column=grid[0].length;
            int sum=0;
            for (int i=0;i<row;i++){
                for (int j=0;j<column;j++){
                    //进入递归的逻辑
                    if (grid[i][j]=='1'){
                        digui(grid,i,j);
                        sum++;
                    }
    
                }
            }
            return  sum;
        }
        public static void digui(char[][] grid,int i,int j){
            //终止条件
            if (i<0||i>=grid.length||j<0||j>=grid[0].length){
                return;
            }
            if(grid[i][j]!='1')
            return;
            if (grid[i][j]=='1')
                grid[i][j]='2';
            digui(grid,i-1,j);
            digui(grid,i+1,j);
            digui(grid,i,j-1);
            digui(grid,i,j+1);
        }
    

617.合并二叉树

  • 递归实现

    递归创造新的节点,确定好每一步的返回值即可。

    出现了好多次空参报错都是因为返回点没有找好。

       public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
            TreeNode res=sum(root1,root2);
            return res;
        }
        public  TreeNode sum(TreeNode root1, TreeNode root2){
            TreeNode res=null;
            //终止条件
            if (root1==null&&root2==null)
                return null;
            if (root1==null&&root2!=null)
            {
                res= root2;
            }
            if (root2==null&&root1!=null)
            {
                res=root1;
            }
            if (root1!=null&&root2!=null) {
                res= new TreeNode(root1.val+root2.val);
                res.left=sum(root1.left,root2.left);
                res.right=sum(root1.right,root2.right);
            }
            return res;
        }
    
  • 迭代bfs

    想把每一步的操作结果都体现在root1树上,迭代需要我们的数据结构-队列来支持,跟层序遍历的思路一样,对于每一步进队列和出队列的情况加上我们的逻辑限制,就能解决本题。

    哪体现了bfs思想呢

    本身在二叉树的遍历思想上,层序遍历本身也是一种能走就全走的思想,只不过这个能走是横向的,在同一层的都进入队列。

    某种意义上来讲,二叉树的遍历都可以归类为bfs或dfs,前中后三序遍历可看做是dfs,层序遍历可看成bfs

    public TreeNode mergeTrees(TreeNode root1, TreeNode root2){
            //挺美观的条件判断处理,学习一手
            if (root1==null||root2==null)
                return root1==null ?root2:root1;
        
            Queue<TreeNode> queue=new LinkedList<>();
            queue.add(root1);
            queue.add(root2);
            while (queue.size()>0){
                TreeNode r1=queue.poll();
                TreeNode r2=queue.poll();
                r1.val+=r2.val;
                if (r1.left!=null&&r2.left!=null){
                    queue.add(r1.left);
                    queue.add(r2.left);
                }
                //隐藏了一步逻辑,就是当右子树为空,r1还等于原值,不用写出来
                if (r1.left==null)
                    r1.left=r2.left;
                if (r1.right!=null&&r2.right!=null){
                    queue.add(r1.right);
                    queue.add(r2.right);
                }
                if (r1.right==null)
                    r1.right=r2.right;
    
            }
            return root1;
        }
    

深搜

104.二叉树的最大深度

  • dfs递归

    思想很简单,只要能走,就一走到底,不能走的情况用我们的递归终止条件控制返回深度即可。

    public int maxDepth(TreeNode root) {
            int ans=depth(root);
            return ans;
        }
        public  int depth(TreeNode node){
            if (node==null)
                return 0;
            return Math.max(depth(node.left),depth(node.right))+1;
        }
    
  • bfs递归

    /*
    104. 二叉树的最大深度
    用了广度优选,感觉这个题用广度优先不如深度优先好,深度优先能减少一些时间复杂度
     */
    public class Solution {
        public int maxDepth(TreeNode root) {
            Queue<TreeNode> queue=new LinkedList<>();
            List<List<Integer>> a=new ArrayList<List<Integer>>();
            if (root==null)
                return 0;
            queue.add(root);
            TreeNode node=root;
            int max=0;
            while (!queue.isEmpty()) {
                List<Integer> b=new ArrayList<>();
                int count=queue.size();
                max++;
                for (int i=0;i<count;i++){
                    node = queue.poll();
                    b.add(node.val);
                    if (node.left != null)
                        queue.add(node.left);
                    if (node.right != null)
                        queue.add(node.right);
                }
                a.add(b);
            }
            return max;
        }
    }
    

543.二叉树的直径

这题第二次做又有了一个新理解的点,就是用一个全局变量,dfs的过程去记录我想要的结果,避免了dfs求深度之后再遍历二叉树拿值的情况。

  • 特殊情况:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cotXebxO-1666195548221)(C:\Users\Onlylove\AppData\Roaming\Typora\typora-user-images\image-20220726220506903.png)]

  • 错误写法:

    以为只需要根节点的深度-1和左右子树深度和比较取max就可以

    其实最大值不一定出现了

     public int diameterOfBinaryTree(TreeNode root) {
            int c=depth(root.right)+depth(root.left);
            int b=depth(root)-1;
            return Math.max(b,c);
        }
        
        public int depth(TreeNode node){
            if (node==null)
                return 0;
            return Math.max(depth(node.left),depth(node.right))+1;
            
        }
    
  • dfs:

    /*
    543.二叉树的直径
    深度优先搜索
    1.递归函数的三个关键点:退出条件、递归内容、相似条件。
    2.树的定义深度
    3.树的直径定义:树中任意两节点最短路径的最大值
    4.两节点间路径的定义:他们之间边的数目
    
    两叶子节点之间路径=祖先节点左右儿子的深度和
     */
     int ans;
        public int diameterOfBinaryTree(TreeNode root) {
            ans=1;
            depth(root);
            return ans-1;
        }
    
        public int depth(TreeNode node){
            if (node==null)
                return 0;
            int l=depth(node.left);
            int r=depth(node.right);
            ans=Math.max(ans,l+r+1);//记录每一步能走的最大值,避免出现最大值不在根节点子树的情况。
            return Math.max(l,r)+1;
    
        }
    

111. 二叉树的最小深度

  • dfs+递归

    时间On,空间On

    /*
    111. 二叉树的最小深度
    最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
    1.写出结束条件
    2.不要把树复杂化,就当做树是三个节点,根节点,左子节点,右子节点
    3.只考虑当前做什么,不用考虑下次应该做什么
    4.每次调用应该返回什么
     */
    public int minDepth(TreeNode root) {
            int ans=0;
            if (root==null)
                return ans;
             ans=depth(root);
            return ans;
        }
        public int depth(TreeNode node){
            if (node.left==null&&node.right==null)
                return 1;
           else if (node.left==null&&node.right!=null)
               return depth(node.right)+1;
           else if (node.right==null&&node.left!=null)
               return depth(node.left)+1;
           else
               return Math.min(depth(node.left),depth(node.right))+1;
        }
    

110.平衡二叉树

  • dfs+递归

    这是一个双重遍历问题,用深搜求树的深度,再用遍历帮助我们求一下是否满足条件。

    public boolean isBalanced(TreeNode root) {
           if (root==null)
               return true;
           if (Math.abs(depth(root.right)-depth(root.left))>1)
               return false;
           return isBalanced(root.right)&&isBalanced(root.left);
    
        }
        //求深度
        public int depth(TreeNode node){
            if (node==null)
                return 0;
            return Math.max(depth(node.left),depth(node.right))+1;
        }
    
  • dfs+迭代

    时间On 空间On

    这段代码是第一次写的,求深度这步写的比较蠢,

    整体思路还是层序遍历+dfs求深度

    /*
    110. 平衡二叉树
    高度=叶子节点到根节点的最长距离
    不可能从叶子到根反向寻找,只能是从根向下寻找
    用递归能少写个栈
    他说每个结点,那还得再遍历一下二叉树
    递归部分:
    1.结束条件:当前节点为叶子节点
    2.递归内容: 计数且进入下一次比较
     */
    public class Solution2 {
        int h=0;
        boolean f=true;
        public boolean isBalanced(TreeNode root) {
            Queue<TreeNode> queue=new LinkedList<>();
            if (root==null)
                return true;
            TreeNode cur=root;
            queue.add(cur);
            while (!queue.isEmpty()) {
                cur=queue.poll();
                if (cur.left!=null)
                    queue.add(cur.left);
                if (cur.right!=null)
                    queue.add(cur.right);
                f=ok(cur);
                if (f==false) {
                    break;
                }
            }
            return f;
        }
        //求高度的函数
        public int high(TreeNode node){
            if (node==null)
                return 0;
            if (node.left==null&&node.right==null)
                h=1;
            if (node.left!=null&&node.right==null)
                h=high(node.left)+1;
            if (node.right!=null&&node.left==null)
                h=high(node.right)+1;
            if (node.right!=null&&node.left!=null)
               h=Math.max(high(node.left),high(node.right))+1;
            return  h;
        }
        public  boolean ok(TreeNode node){
            if (Math.abs(high(node.left)-high(node.right))>1)
                return false;
            else
                return true;
        }
    }
    

226.翻转二叉树

  • dfs+递归

    时间On 空间On

    在我能走的基础上一直深搜,交换左右两边的值,操作,写好递归终止条件即可。

    public TreeNode invertTree(TreeNode root) {
            change(root);
            return root;
        }
        private void change(TreeNode node){
            TreeNode cur=node;
            if (node==null)
                return;
            else if (node.left==null&&node.right!=null){
                node.left=node.right;
                node.right=null;
            }
            else if (node.right==null&&node.left!=null){
                node.right=node.left;
                node.left=null;
            }
            else {
                cur=node.right;
                node.right=node.left;
                node.left=cur;
            }
            change(node.left);
            change(node.right);
    
        }
    

回溯

回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。

回溯算法理论基础

遍历阶段伪代码

for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
    处理节点;
    backtracking(路径,选择列表); // 递归
    回溯,撤销处理结果
}

递归部分伪代码

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

可能遇到的结果处理

res.add(new ArrayList<>(list));

39.组合总和

  • 回溯树:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lgTXnaDx-1666195548223)(F:\截图\IMG_20220729_222638.jpg)]

  • 回溯

    /*
    39. 组合总和
    核心三步骤:(回溯)22 23凑
                list.add(candidates[i]);
                back(candidates,list,target-candidates[i],i);
                list.remove(list.size()-1);
    剪枝:
     if (candidates[start]>target)//上来就大于目标值,直接不用考虑
                return;
     if(i > 0 && candidates[i] == candidates[i-1])   continue; 连续元素重复了,直接跳过一个
                //[2,2,3,5]
    
     */
    public class Solution3 {
        List<List<Integer>> res=new ArrayList<>();
        public List<List<Integer>> combinationSum(int[] candidates, int target) {
            List<Integer> list=new ArrayList<>();
            Arrays.sort(candidates);
            back(candidates,list,target,0);
            return res;
        }
       public void back(int[] candidates,  List<Integer> list,int target,int start){
            if (target==0)
            {
                res.add(new ArrayList<>(list));
                return;
            }
            for (int i=start;i<candidates.length;i++){
                if (candidates[start]>target)
                return;
                //[2] [2,2] [2,2,3]   [7]
                list.add(candidates[i]);
                /*
                 tar:5,start:1
                 [2,2]  back3 [2,3] back2
                 */
                back(candidates,list,target-candidates[i],i);
                //[]
                list.remove(list.size()-1);
            }
       }
    }
    

77.组合

  • 回溯

    我的思路是先将n转化为平时操作的数组,在for遍历上扩宽集合,在backtracking上深入集合,

    判断条件 长度

    剪枝角度 要放的必须比之前的得大(小的话一定已经放过了,为了避免重复)

    时间:173ms

    List<List<Integer>> res=new ArrayList<>();
        public List<List<Integer>> combine(int n, int k) {
            int [] a=new int[n];
            for (int i=0;i<n;i++){
                a[i]=i+1;
            }
            List<Integer> list=new ArrayList<>();
            backtracking(a,list,k);
            return res;
        }
        void backtracking(int []nums,List<Integer> list,int k){
            //递归终止条件
           if (list.size()>=k){
                 { res.add(new ArrayList<>(list));
                return;}
            }
            for (int i=0;i<nums.length;i++){
                //剪枝操作
                if (list.size()!=0&&nums[i]<=list.get(list.size()-1))
                    continue;
                //深度和宽度的逻辑实现
                list.add(nums[i]);
                backtracking(nums,list,k);
                list.remove(list.size()-1);
            }
        }
    
  • 优化版:

    既然上面都已经想到了剪枝操作,那可以从产生条件上就避免剪枝情况的出现,因为我放进入的数组必然是递增的。

    时间17ms,

    List<List<Integer>> res=new ArrayList<>();
        public List<List<Integer>> combine(int n, int k) {
            int [] a=new int[n];
            for (int i=0;i<n;i++){
                a[i]=i+1;
            }
            List<Integer> list=new ArrayList<>();
            backtracking(a,list,k,0);
            return res;
        }
        void backtracking(int []nums,List<Integer> list,int k,int start){
            //递归终止条件
            if (list.size()>=k){
                { res.add(new ArrayList<>(list));
                    return;}
            }
            for (int i=start;i<nums.length;i++){
                //剪枝操作
                // if (list.size()!=0&&nums[i]<=list.get(list.size()-1))
                //     continue;
                
                //深度和宽度的逻辑实现
                list.add(nums[i]);
                backtracking(nums,list,k,i+1);
                list.remove(list.size()-1);
            }
        }
    
  • 优化版plus

    仔细想想这题还有能优化的地方,从遍历的次数来减少是一个很好的出发点。

    如果从1开始,k=3,那我们只需要遍历到4就OK了,没有必要再去遍历5、6、7…这样的数据了!

    时间:1ms

    i<=nums.length-(k-list.size())
    

    完整版

    List<List<Integer>> res=new ArrayList<>();
        public List<List<Integer>> combine(int n, int k) {
            int [] a=new int[n];
            for (int i=0;i<n;i++){
                a[i]=i+1;
            }
            List<Integer> list=new ArrayList<>();
            backtracking(a,list,k,0);
            return res;
        }
        void backtracking(int []nums,List<Integer> list,int k,int start){
            //递归终止条件
            if (list.size()>=k){
                { res.add(new ArrayList<>(list));
                    return;}
            }
            for (int i=start;i<=nums.length-(k-list.size());i++){
                //剪枝操作
                // if (list.size()!=0&&nums[i]<=list.get(list.size()-1))
                //     continue;
                //深度和宽度的逻辑实现
                list.add(nums[i]);
                backtracking(nums,list,k,i+1);
                list.remove(list.size()-1);
            }
        }
    

46.全排列

  • 回溯

    避免重复数据,遍历一下是否已经存在nums[i]

    if(list.contains(nums[i]))
                 continue;
    
        List<List<Integer>> res=new ArrayList<>();
        public List<List<Integer>> permute(int[] nums) {
            List<Integer> list=new ArrayList<>();
            backtracking(nums,list);
            return res;
        }
        void backtracking(int []nums,List<Integer> list){
            if (list.size()>=nums.length){
                res.add(new ArrayList<>(list));
                return;
            }
            for (int i=0;i<nums.length;i++){
                if(list.contains(nums[i]))
                 continue;
                list.add(nums[i]);
                backtracking(nums,list);
                list.remove(list.size()-1);
            }
        }
    

79.单词搜索

  • dfs错误写法

    看题目想到的是dfs,正常的dfs写完,发现面对abcb这样的样例无法通过,于是采用了跟LeetCode200题一样的策略,将已经遍历过的字符设为一个不相干的,来避免下次再遍历到。

    改进之后 发现 [[“C”,“A”,“A”],[“A”,“A”,“A”],[“B”,“C”,“D”]] “AAB” 这个测试是致命的,也就说用dfs就无法解决这样的情况

    public boolean exist(char[][] board, String word) {
            int m=board.length;
            int n=board[0].length;
            for (int i=0;i<m;i++){
                for (int j=0;j<n;j++){
                    if (board[i][j]==word.charAt(0)&&dfs(board,word,0,i,j))
                        return true;
                }
            }
            return false;
        }
        boolean dfs(char[][] board, String word,int start,int i,int j){
            boolean flag=false;
            if (i<0||i>=board.length||j<0||j>=board[0].length)
                return true;
            if (board[i][j]!=(word.charAt(start))){
                return  false;
            }
            board[i][j]='0';
            flag=true;
            return flag&&(dfs(board,word,start+1,i+1,j)||
                    dfs(board,word,start+1,i-1,j)||
                    dfs(board,word,start+1,i,j-1)||
                    dfs(board,word,start+1,i,j+1));
        }
    
  • 回溯

    用一个二维Boolean数组来表示当前节点走没走没,走过之后再撤销一下。

    /**
         * 回溯法:相比于DFS,多了一步『撤销修改节点状态』
         */
            private boolean find;  // 定义为成员变量,方便以下两个成员方法使用和修改
    
            public boolean exist(char[][] board, String word) {
                if (board == null) return false;
                int m = board.length, n = board[0].length;
                boolean[][] visited = new boolean[m][n];
                find = false;
    
                for (int i = 0; i < m; i++){
                    for (int j = 0; j < n; j++){
                        backtracking(i, j, board, word, visited, 0);  // 从左上角开始遍历棋盘每个格子
                    }
                }
                return find;
            }
    
            /**
             * i,j,board:棋盘格及当前元素的坐标
             * word: 要搜索的目标单词
             * visited:记录当前格子是否已被访问过
             * pos: 记录目标单词的字符索引,只有棋盘格字符和pos指向的字符一致时,才
             * 有机会继续搜索接下来的字符;如果pos已经过了目标单词的尾部了,那么便说明找到目标单词了
             */
            public void backtracking(int i, int j, char[][] board, String word,
                                     boolean[][] visited, int pos){
                // 超出边界、已经访问过、已找到目标单词、棋盘格中当前字符已经和目标字符不一致了
                if(i<0 || i>= board.length || j<0 || j >= board[0].length || visited[i][j] ||
                        find || board[i][j]!=word.charAt(pos))  return;
    
                if(pos == word.length()-1){
                    find = true;
                    return;
                }
    
                visited[i][j] = true;  // 修改当前节点状态
                backtracking(i+1, j, board, word, visited, pos+1);  // 遍历子节点
                backtracking(i-1, j, board, word, visited, pos+1);
                backtracking(i, j+1, board, word, visited, pos+1);
                backtracking(i, j-1, board, word, visited, pos+1);
                visited[i][j] = false; // 撤销修改
            }
    
    

22.括号生成

对检验函数的处理思路:

模拟了我们正常人脑检验括号时采用的消去法,即一个左括号和一个右括号能抵消,统计左右括号的个数,保证左括号的个数不小于右括号的个数即可。

字符串数据的处理:

不能用Stringbuffer,因为stringbuffer的处理是连续的,不能返回一个新的集合处理。

  • 回溯:

     List<String> res=new ArrayList<String>();
        public List<String> generateParenthesis(int n) {
    //        List<String> list=new ArrayList<>();
            backtracking(new char[2*n],0,res);
            return res;
        }
        boolean check(char []a){ 
            int count=0;
            for (int i = 0; i <a.length ; i++) {
               
                if (a[i]=='(')
                    count++;
                else
                    count--;
                if (count<0)
                    return false;
            }
            return (count==0);
        }
        void backtracking(char [] current,int pos,List<String> result){
                if (pos == current.length) {
                    if (check(current)) {
                        result.add(new String(current));
                    }
                } else {
                    current[pos] = '(';
                    backtracking(current, pos + 1, result);
                    current[pos] = ')';
                    backtracking(current, pos + 1, result);
                }
            }
    

17.电话号码的字母组合

  • 回溯:

    数据的处理需要花时间想想,回溯的部分还是比较基础的

    先采用hashmap集合来将我们要用的abc这样的字符放进去,

    看来stringbuffer也能删除,只是我没用对。deleteCharAt才是真正的删除函数

    static Map<Character,String> map=new HashMap();
        StringBuffer sb=new StringBuffer();
        List<String> res=new ArrayList<>();
        public List<String> letterCombinations(String digits) {
    
            map.put('2',"abc");
            map.put('3',"def");
            map.put('4',"ghi");
            map.put('5',"jkl");
            map.put('6',"mno");
            map.put('7',"pqrs");
            map.put('8',"tuv");
            map.put('9',"wxyz");
            int n=digits.length();
            backtracking(digits,0);
            return res;
        }
        void backtracking(String digits,int index){
            if (sb.length()==digits.length())
            {
                res.add(sb.toString());
                return;
            }
            String val=map.get(digits.charAt(index));
            for (char  ch:val.toCharArray()) {
                sb.append(ch);
                backtracking(digits,index+1);
                sb.deleteCharAt(sb.length()-1);
            }
        }
    

排序

二分

前提我数组必须是已经升序的

时间复杂度O logn

  • 模板:

     public int searchInsert(int[] nums, int target) {
                int left = 0, right = nums.length - 1; // 注意
                while(left <= right) { // 注意
                    int mid = (left + right) / 2; // 注意
                    if(nums[mid] == target) { // 注意
                        // 相关逻辑
                    } else if(nums[mid] < target) {
                        left = mid + 1; // 注意
                    } else {
                        right = mid - 1; // 注意
                    }
                }
                // 相关返回值
                return 0;
            }
    
704.二分查找
  • 解法:

    public int search(int[] nums, int target) {
            int left=0;
            int right=nums.length-1;
            while(left<=right){
                int mid=(left+right)/2;
                if (target==nums[mid])
                    return mid;
                else if (target>nums[mid]){
                    left=mid+1;
                }
                else {
                    right=mid-1;
                }
            }
            return -1;
        }
    
35.搜索插入位置
  • 二分:

    public int searchInsert(int[] nums, int target) {
            int left=0;
            int right=nums.length-1;
            int mid;
            while (left<=right){
                mid=(left+right)/2;
                if (target==nums[mid])
                    return mid;
                if (target<nums[mid])
                     right=mid-1;
                if (target>nums[mid]){
                    left=mid+1;
                }
            }
            return left;
        }
    

动态规划

动规五部曲

- 确定dp数组(dp table)以及下标的含义

- 确定递推公式

- dp数组如何初始化

- 确定遍历顺序

- 举例推导dp数组

基础题目

509.斐波那契数
  • 动规

    public int fib(int n) {
            if (n==0)
                return 0;
            if (n==1)
                return 1;
            int dp[]=new int[n];
            dp[0]=dp[1]=1;
            for (int i=2;i<n;i++){
                dp[i]=(dp[i-1]+dp[i-2])%1000000007;
            }
            return dp[n-1];
        }
    
70.爬楼梯
 public int climbStairs(int n) {
        if (n==1)
            return 1;
        if (n==2)
            return 2;
        int dp[]=new int[n+1];
        dp[1]=1;
        dp[2]=2;
        for (int i=3;i<=n;i++){
            dp[i]=(dp[i-1]+dp[i-2]);
        }
        return dp[n];
    }
746.使用最小花费爬楼梯
/*dp[0]代表吃 dp[1]代表不吃
我觉得这个题的描述应该改改:每个阶梯都有一定数量坨屎,一次只能跨一个或者两个阶梯,走到一个阶梯就要吃光上面的屎,问怎么走才能吃最少的屎?开局你选前两个阶梯的其中一个作为开头点,并吃光该阶梯的屎。
*/
    public int minCostClimbingStairs(int[] cost) {
        int n=cost.length;
        if(n==0)
            return 0;
        if(n==1)
            return cost[0];
        int [][]dp=new int[n+1][2];
        dp[1][0]=cost[0];
        dp[1][1]=0;
        for(int i=2;i<=n;i++){
            dp[i][0]=Math.min(dp[i-1][0]+cost[i-1],dp[i-1][1]+cost[i-1]);
            dp[i][1]=dp[i-1][0];
        }
        return Math.min(dp[n][0],dp[n][1]);
    }
62.不同路径
public int uniquePaths(int m, int n) {
        int [][]dp=new int[m+1][n+1];
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                if(i==1&&j==1){
                    dp[i][j]=1;
                    continue;
                }
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m][n];
    }
63.不同路径2
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int m=obstacleGrid.length;
        int n=obstacleGrid[0].length;
        int [][]dp=new int[m+1][n+1];
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                if(obstacleGrid[i-1][j-1]==1){
                    dp[i][j]=0;
                    continue;
                }
                if(i==1&&j==1){
                    dp[i][j]=1;
                    continue;
                }
                
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m][n];
    }
343.整数拆分

对于的正整数 nn,当 n \ge 2n≥2 时,可以拆分成至少两个正整数的和。令 kk 是拆分出的第一个正整数,则剩下的部分是 n-kn−k,n-kn−k 可以不继续拆分,或者继续拆分成至少两个正整数的和。由于每个正整数对应的最大乘积取决于比它小的正整数对应的最大乘积,因此可以使用动态规划求解。

 public int integerBreak(int n) {
        int [] dp=new int [n+1];
        dp[0]=dp[1]=1;
        for(int i=2;i<=n;i++){
            int curmax=0;
            for(int j=1;j<i;j++){
            curmax=Math.max(curmax,Math.max(j*(i-j),j*dp[i-j]));
            dp[i]=curmax;
            }
        }
        return dp[n];
    }
96.不同的二叉搜索树

1开头的情况、2开头的情况、3开头的情况。。。以此类推

 public int numTrees(int n) {
        //表示下标为i的ans
        int []dp =new int[n+1];
        dp[0]=1;
        dp[1]=1;
        for(int i=1;i<=n;i++){
            int size=0;
            for(int j=1;j<=i;j++){
                size+=dp[j-1]*dp[i-j];
            }
            dp[i]=size;
        }
        return dp[n];
    }

t [][]dp=new int[m+1][n+1];
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(i1&&j1){
dp[i][j]=1;
continue;
}
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m][n];
}


#### 63.不同路径2

~~~java
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int m=obstacleGrid.length;
        int n=obstacleGrid[0].length;
        int [][]dp=new int[m+1][n+1];
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                if(obstacleGrid[i-1][j-1]==1){
                    dp[i][j]=0;
                    continue;
                }
                if(i==1&&j==1){
                    dp[i][j]=1;
                    continue;
                }
                
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m][n];
    }
343.整数拆分

对于的正整数 nn,当 n \ge 2n≥2 时,可以拆分成至少两个正整数的和。令 kk 是拆分出的第一个正整数,则剩下的部分是 n-kn−k,n-kn−k 可以不继续拆分,或者继续拆分成至少两个正整数的和。由于每个正整数对应的最大乘积取决于比它小的正整数对应的最大乘积,因此可以使用动态规划求解。

 public int integerBreak(int n) {
        int [] dp=new int [n+1];
        dp[0]=dp[1]=1;
        for(int i=2;i<=n;i++){
            int curmax=0;
            for(int j=1;j<i;j++){
            curmax=Math.max(curmax,Math.max(j*(i-j),j*dp[i-j]));
            dp[i]=curmax;
            }
        }
        return dp[n];
    }
96.不同的二叉搜索树

1开头的情况、2开头的情况、3开头的情况。。。以此类推

 public int numTrees(int n) {
        //表示下标为i的ans
        int []dp =new int[n+1];
        dp[0]=1;
        dp[1]=1;
        for(int i=1;i<=n;i++){
            int size=0;
            for(int j=1;j<=i;j++){
                size+=dp[j-1]*dp[i-j];
            }
            dp[i]=size;
        }
        return dp[n];
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/618760.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ppt保存文件奇怪问题

我发现ppt中的形状保存成jpg,png和pdf时&#xff0c;格式不一样 比如 当右键单击时&#xff0c;然后选择另存为图片 png格式 jpg格式 pdf格式 感觉还是很奇怪&#xff0c;就pdf的格式比较靠谱一点

LLM-KERec

1、LLM-KERec整体框架 LLM-KERec系统包括传统推荐模块和基于LLM的互补知识增强模块。传统推荐模块负责召回候选商品、粗排过滤、精排和重排。LLM互补知识增强模块则包括实体提取器、互补图构造、E-E-I权重决策模型等&#xff0c;以整合互补知识&#xff0c;增强推荐效果。 2、…

CSS滑动门

CSS滑动门使各种特殊形状的背景能够自动拉伸滑动&#xff0c;以适应元素内部的文本内容&#xff0c;其原理是&#xff1a;利用CSS精灵和盒子撑开宽度适应不同字数的导航栏。 特点&#xff1a; 1.可以根据导航字数自动调节宽度&#xff1b; 2.可以以简单的背景图实现炫彩的导航条…

【数据结构与算法】递归

// 计算 1-100 累加 function add(a, b) {return a b ? a : add(a, b - 1) b }console.log(add(1, 100))// 计算阶乘 function factorial(n) {return n < 1 ? 1 : n * factorial(n - 1) }console.log(factorial(5)) // 120理论上所有递归都可以用循环实现。 注意防止栈…

【基于element ui的color选择器】基于element ui的color选择器

技术版本如下&#xff1a; vue 2.6.14 less 3.13.1 element-ui 2.15.6 less-loader 5.0.0需求&#xff1a; 支持RGB、HEX编码、支持吸管吸取颜色、颜色选择器、颜色模板、透明度、色板、线性渐变颜色 效果图&#xff1a; 1.引入选择器的color-all文件 <template><…

STC8增强型单片机开发day03

中断系统INT 中断的概念 中断系统是为使 CPU 具有对外界紧急事件的实时处理能力而设置的。 当中央处理机 CPU 正在处理某件事的时候外界发生了紧急事件请求&#xff0c;要求 CPU 暂停当前的工作,转而去处理这个紧急事件&#xff0c;处理完以后&#xff0c;再回到原来被中断的…

Linux 信号保存

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux知识分享⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d; 目录 前言 阻塞信号 1. 信号其他相关常见…

分享一个处理大文件效率拉满的神器

&#x1f3c3;‍♂️ 微信公众号: 朕在debugger© 版权: 本文由【朕在debugger】原创、需要转载请联系博主&#x1f4d5; 如果文章对您有所帮助&#xff0c;欢迎关注、点赞、转发和订阅专栏&#xff01; 前言 系统当天有些表的数据需要恢复成前一天的样子&#xff0c;幸好有…

Chrome的常用操作总结

Chrome的常用操作总结 最近的自己真的好忙啊,好久真好久没有写博客了,今天我就趁着周末的这段时间总结一下最近自己的用的Chrome浏览器常用的命令 不得不说: 就是特么的丝滑!吊打一切浏览器(不接受反驳哈哈哈)因为反驳我也不听嘻嘻 用好快捷键,就是事半功倍!!!重要的事儿说一遍…

【超详细】跑通YOLOv8之深度学习环境配置2

环境配置2下载安装内容如下&#xff1a; CUDA&#xff1a;https://developer.nvidia.com/cuda-toolkit-archive cudnn&#xff1a;https://developer.nvidia.com/rdp/cudnn-archive 版本&#xff1a;CUDA11.3 cudnn8.9.7 CUDA安装 简介 CUDA&#xff08;Compute Unified De…

考研操作系统-1.计算机系统概述

王道考研操作系统-1.计算机系统概述 操作系统 是指控制和管理整个计算机系统的硬件和软件资源&#xff0c;合理地组织调度计算机的工作和资源的分配&#xff1b;提供给用户和软件方便的接口和环境&#xff1b;是计算机系统中最基本的系统软件。 应包括&#xff1a; 1&#xf…

清华发布Temporal Scaling Law,解释时间尺度对大模型表现的影响

众所周知&#xff0c; 语言模型调参&#xff01; 预训练语言模型调参&#xff01;&#xff01; 预训练大语言模型调参&#xff01;&#xff01;&#xff01; 简直就是一个指数级递增令人炸毛的事情&#xff0c;小编也常常在做梦&#xff0c;要是只训练几步就知道现在的超参…

拌合楼管理系统(十九)ini配置文件本地加密

前 言&#xff1a; 项目中&#xff0c;数据库服务器与程序不在一起&#xff0c;且不允许通过互联网直接访问数据库。 解决方法是通过web服务来做中间件来解决数据交互的问题。但如果web服务交互又存在身份验证问题&#xff0c;需要实现访问对应的接口是经过授权的&#xff0c;未…

考研数学|强化阶段怎么刷《660》《880》《1000》?

强化阶段想要刷好题&#xff0c;首先要选一本适合自己的题集&#xff01; 一般在强化阶段&#xff0c;大家用多个最多的题集就是660题&#xff0c;880题还有1000题 660题的特点是只训练客观题&#xff0c;虽然题目的质量很高&#xff0c;但是训练面还是比较窄 880题是综合训…

华为交换机配置导出备份python脚本

一、脚本编写思路 &#xff08;一&#xff09;针对设备型号 主要针对华为&#xff08;Huawei&#xff09;和华三&#xff08;H3C&#xff09;交换机设备的配置备份 &#xff08;二&#xff09;导出前预处理 1.在配置导出前&#xff0c;自动打开crt软件或者MobaXterm软件&am…

C++ int 学习

在C语言中 & 是取地址符号&#xff1b; 在C中有 int& 这样的&#xff0c;这里的&不是取地址符号&#xff0c;而是引用符号&#xff1b; 引用是C对C的一个补充&#xff1b; 变量的引用就是变量的别名&#xff0c;讲的通俗一点就是另外一个名字&#xff1b; a的值…

transformer与beter

transformer与beter 解码和编码器含义tokizer标记器和one-hot独热编码编码解码--语义较好的维度空间矩阵相乘--空间变换编码理解如何构造降维的嵌入矩阵--实现到达潜空间上面是基础&#xff0c;下面是transformer正文自注意力机制注意力分数--上下文修正系数为什么需要KQ两个矩…

GO+树莓派+E53_IA1智慧农业模块

简介 之前手头上有小熊派的开发板&#xff0c; 有一个E53_IA1模块&#xff0c; 刚好用到树莓派上&#xff0c; 使用GO进行控制&#xff0c;实现智慧农业模块功能。 模块介绍 模块电路介绍 按硬件分成五块&#xff0c; 其中四块在本次用上了&#xff0c; 分别是 1. 补光模块&…

SpringBoot结合Canal 实现数据同步

1、Canal介绍 Canal 指的是阿里巴巴开源的数据同步工具&#xff0c;用于数据库的实时增量数据订阅和消费。它可以针对 MySQL、MariaDB、Percona、阿里云RDS、Gtid模式下的异构数据同步等情况进行实时增量数据同步。 当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.…

数据分析需要注意哪些法律法规

数据分析 前言一、数据处理过程二、数据收集阶段的法律规则数据收集应具备合法、正当、透明原则数据收集应坚持最小必要原则数据收集应遵守知情-同意规则数据收集应遵守目的明确性要求 三、数据储存的法律规则四、数据使用与处理的阶段的法律规则数据安全保护义务按照数据分级分…