做题记录 – IT SHARE https://blogtao.com Sat, 15 Jul 2023 06:37:21 +0000 zh-CN hourly 1 https://wordpress.org/?v=6.3.2 https://blogtao.com/wp-content/uploads/2023/02/cropped-2-32x32.png 做题记录 – IT SHARE https://blogtao.com 32 32 力扣 56. 合并区间 https://blogtao.com/archives/457 https://blogtao.com/archives/457#respond Sat, 15 Jul 2023 06:28:45 +0000 https://blogtao.com/?p=457 Read More Read More

]]>
我在做这道题56. 合并区间时遇到一些问题,记录一下。这道题仍然是寻找重复的区间,我一开始理解错题意,以为要把合并之后的区间里面每一个元素都加到结果数组里面。具体讲一下这道题。我们可以根据一个区间的左边界是否和前一个区间的有边界是否重合(挨在一起也算重合),来决定是否把当前区间和前一个区间合并,在处理重合的情况时,要注意把结果数组的最后一个区间删除掉,然后添加新的区间,当然这个新区间的左边界是结果数组的最后一个区间的左边界。我一开始想的是用一个ArrayList嵌套另一个ArrayList,然后转为数组,这样做有些麻烦,可以直接用ArrayList保存数组,然后使用 arr1.toArray(new int[arr1.size()][] ) 这个方法。代码如下

class Solution {
    public int[][] merge(int[][] intervals) {
        if(intervals.length==1)return intervals;
        Arrays.sort(intervals, (a,b)->Integer.compare(a[0],b[0]));
        LinkedList<int[]> arr1 =new LinkedList<>();

        arr1.add(intervals[0]);

        int start=0;
        int end=0;
        for(int i=1; i<intervals.length; i++){
            if(intervals[i][0]>arr1.getLast()[1]){
                arr1.add(intervals[i]);
            }
            else {
                start=arr1.getLast()[0];
                end=Math.max(arr1.getLast()[1], intervals[i][1]);
                arr1.removeLast();
                arr1.add(new int[]{start, end});
      
            }
        }
        return arr1.toArray(new int[arr1.size()][]);
    }
}

 

]]>
https://blogtao.com/archives/457/feed 0
力扣 763. 划分字母区间 https://blogtao.com/archives/452 https://blogtao.com/archives/452#respond Sat, 15 Jul 2023 02:49:33 +0000 https://blogtao.com/?p=452 Read More Read More

]]>
力扣763. 划分字母区间

题目解法非常巧妙,使用for循环每次保存字母的最大索引位置,然后通过第二个for循环找每一个字母的最大索引位置是否等于当前的索引位置,如果相等,就说明是最长的区间,然后可以保存这个区间的长度,为什么可以通过 字母的最大索引位置是否等于当前的索引位置 判断是否为最长区间,我举一个例子,比如字符串 “ababcbacadefegdehijhklij”=对应的索引是

8 5 8 5 7 5 8 7 8 14 15 11 15 13 14 15 19 22 23 19 20 21 22 23

把待划分区间的右边界right设置为当前字母的最大索引位置和right二者中较大的那一个,如果遇到字母的最大索引位置比right小,就说明这个区间应该更长。题目的这句话 “同一字母最多出现在一个片段中 ”很关键,这说明每一个区间中可以包含很多索引比较小的字母,所以要找到最大的字母下标。

我感觉说的不太清晰,还是看代码

class Solution {
    public List<Integer> partitionLabels(String s) {
        ArrayList<Integer> arr=new ArrayList<>();
        int[] map=new int[27];
        int left=0, right=0;
        for(int i=0; i<s.length(); i++){
            map[s.charAt(i)-'a']=i;
        }
       
        for(int i=0; i<s.length(); i++){
            right=Math.max(map[s.charAt(i)-'a'], right);
            if(right==i){
                arr.add(right-left+1);
                left=i+1;
            }
        }
        return arr;
    }
}

 

]]>
https://blogtao.com/archives/452/feed 0
力扣 452. 用最少数量的箭引爆气球 https://blogtao.com/archives/447 https://blogtao.com/archives/447#respond Tue, 11 Jul 2023 13:19:28 +0000 https://blogtao.com/?p=447 Read More Read More

]]>
这道题通过画图分析,得知当两个气球有重叠部分,可以通过在重叠部分射箭刺破气球,但是在代码的操作中可以通过result计数来记录需要的箭的数量,具体的操作是在遇到不重叠的两个气球时result++,值得注意的地方是,应该从数组的最左边开始,每两个相邻的气球计算一次是否需要箭,这样是局部最优,并且通过这个局部最优可以推导出全局最优。

具体代码如下

class Solution {
    public int findMinArrowShots(int[][] points) {
        if(points.length==0)return 0;
        // 用Integer内置的比较方法,否则会溢出。
        Arrays.sort(points, (a, b) -> Integer.compare(a[0], b[0]));
        int result=1;
        for(int i=1; i<points.length; i++){
            if(points[i][0]>points[i-1][1]){
                result++;
            } else{
                points[i][1]=Math.min(points[i-1][1], points[i][1]);
            }
        }
        return result;
    }
}

 

]]>
https://blogtao.com/archives/447/feed 0
二叉树的遍历顺序衍生的问题 https://blogtao.com/archives/425 https://blogtao.com/archives/425#respond Wed, 28 Jun 2023 14:17:04 +0000 https://blogtao.com/?p=425 Read More Read More

]]>
最近做了一些力扣的二叉树相关的题目,在二叉搜索树的问题中,经常利用遍历顺序解决问题。比如700. 二叉搜索树中的搜索 就利用先序遍历来解决在二叉搜索树中查找一个特定的值。

538.把二叉搜索树转换为累加树这道题巧妙地使用逆中序遍历,先遍历右子树,接着处理当前节点,最后处理左子树,由于要把比当前节点大的元素的值加到当前元素的值里面,所以要使用pre保存前一个遍历到的结点的值。

学到一个新方法,利用pre保存前一个访问到的节点,这个方法对于二叉搜索树中涉及的最值和求值问题很有用,这种处理通常和中序遍历结合。

]]>
https://blogtao.com/archives/425/feed 0
动态规划 力扣 96. 不同的二叉搜索树 https://blogtao.com/archives/418 https://blogtao.com/archives/418#respond Sat, 24 Jun 2023 13:02:16 +0000 https://blogtao.com/?p=418 Read More Read More

]]>
这道题求的是二叉搜索树的个数,题目见 96. 不同的二叉搜索树

我们先从1,2,开始找规律。有1个节点,二叉搜索树的个数是1;有2个节点,二叉搜索树的个数是2。当有3个节点时,二叉搜索树的总数目是由根节点左边的节点组成的二叉搜索树的数目乘以根节点右边的节点组成的二叉搜索树的数目,可以看出结点的数目可以由更少的节点数目推出来。

设dp[i]为节点数目为i的二叉搜索树的个数,递推公式为dp[i]=dp[i]+dp[j]*dp[i-1-j],因为每一次的二叉搜索树个数都是当前的左右子树数目乘积加上前一次的二叉搜索树的数目。

考虑特殊情况,有0个节点,二叉搜索树的个数是1,因为当根节点的某一个子树为空,则该子树的二叉搜索树的数目为1,1乘以另一个不为空的子树的二叉搜索树的个数不影响结果,这是符合实际情况的。代码如下

class Solution {
    public int numTrees(int n) {
        int[] dp=new int[n+1];
        dp[1]=dp[0]=1;
        if(n>=2)dp[2]=2;
        for(int i=3; i<=n; i++){
            for(int j=0; j<i; j++){
                dp[i]=dp[i]+dp[j]*dp[i-j-1];
            }
        }
        return dp[n];
    }
}
]]>
https://blogtao.com/archives/418/feed 0
动态规划 力扣 343. 整数拆分 https://blogtao.com/archives/409 https://blogtao.com/archives/409#respond Sat, 24 Jun 2023 09:51:55 +0000 https://blogtao.com/?p=409 Read More Read More

]]>
题目看这里 343. 整数拆分

动态规划

这道题目有个很巧妙的解法是利用数学规律,但是我先讨论这道题的常规解法:动态规划。

题目要求把一个正整数拆分为两个或两个以上的正整数的和,然后求这些拆分出来的正整数的乘积。因为每一个数都依赖它前面比它小的正整数的值,所以考虑用动态规划。
设dp[i]是构成数字 i 的正整数的乘积,那么dp[i]=max( max( i*(i-j), j*dp[i-j] ), dp[i]),因为这个递推公式在最内层循环,所以每一次取最大值的时候需要把dp[i]也考虑进。
因为0,1都不能拆分,所以初始化dp[0]和dp[1]都是0,因为在后面的循环中会计算到dp[j],所以我没有使用dp[j]*dp[i-j],

class Solution {
    public int integerBreak(int n) {
        if(n<=3)return n-1;
        int[] dp=new int[n+1];
        for(int i=2; i<=n; i++){
            for(int j=1; j<i; j++){
                dp[i]=Math.max(Math.max(j*(i-j), j*dp[i-j]), dp[i]);
            }
        }
        return dp[n];
    }
}

另一种动态规划在上面的动态规划基础上做了一些小改进,在内层循环里面判断的是j<=i-j,并且dp[i]=Math.max(dp[i], dp[j]*dp[i-j]),这是因为使用dp[i]=Math.max(dp[i], dp[j]*dp[i-j])来获得dp[i]的最大值,不需要遍历整个i范围内的j的值。

class Solution {
    public int integerBreak(int n) {
        if(n<=3)return n-1;
        int[] dp=new int[n+1];
        dp[1]=1;
        dp[2]=2;
        dp[3]=3;
        for(int i=4; i<=n; i++){
            for(int j=1; j<=i-j; j++){
                dp[i]=Math.max(dp[i],dp[j]*dp[i-j]);
            }
        }
        return dp[n];
    }
}

数学方法

数学方法首先要证明“把正整数n分成尽可能多的正整数x,这些正整数的乘积最大”,证明的结果是 当把n分成多个3的和,这些3的乘积最大。具体证明可以看力扣官方题解,这个解法比较冷门。

]]>
https://blogtao.com/archives/409/feed 0
力扣 106. 从中序与后序遍历序列构造二叉树 https://blogtao.com/archives/402 https://blogtao.com/archives/402#respond Fri, 16 Jun 2023 12:18:04 +0000 https://blogtao.com/?p=402 Read More Read More

]]>
题目是 [106. 从中序与后序遍历序列构造二叉树(https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/)

使用中序遍历和后序遍历确定二叉树的结构,关键是知道后序遍历的最后一个节点是整个树的根节点。所以每次根据后序遍历的数组中的最后一个元素得到中序遍历中的根节点的索引,从而把中序遍历的数组分成左右两个子树。

注意这里的后序遍历数组的最后一个元素索引每次都会自减,因为每一次递归都会用掉当前后序遍历数组的最后一个元素。

因为要使用后序遍历数组的最后一个元素,所以要先构造出右子树,然后再构造左子树。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    int[] inorder;
    int[] postorder;
    int postRight;
    Map<Integer, Integer> map;
    public TreeNode myBuildTree(int inLeft, int inRight){
        if(inRight<inLeft) return null;
        TreeNode root=new TreeNode(postorder[postRight]);
        int rootVal =postorder[postRight];    //根节点
        int inorderRootIndex=map.get(rootVal);   //在中序遍历中定位根节点的索引
        postRight--;
        root.right=myBuildTree(inorderRootIndex+1, inRight);
        root.left=myBuildTree(inLeft, inorderRootIndex-1);
        return root;
    }
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        this.inorder=inorder;
        this.postorder=postorder;
        this.postRight=postorder.length-1;
        int inlen=inorder.length;
        map=new HashMap<>();
        for(int i=0; i<inorder.length; i++){
            map.put(inorder[i], i);
        }

        return myBuildTree(0, inlen-1);
    }
}
]]>
https://blogtao.com/archives/402/feed 0
力扣 105.从前序与中序遍历构造二叉树 https://blogtao.com/archives/400 https://blogtao.com/archives/400#respond Fri, 16 Jun 2023 11:21:03 +0000 https://blogtao.com/?p=400 Read More Read More

]]>
题目是 105. 从前序与中序遍历序列构造二叉树

这一题和 106. 从中序与后序遍历序列构造二叉树非常像,具体的处理方法是类似的,不同的地方在于,这一题我们把先序遍历的数组左侧的元素当作根节点,在代码中体现为preLeft++,而且先构造左子树,然后构造右子树。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    int[] preorder;
    int[] inorder;
    int preLeft;
    Map<Integer, Integer> map;
    public TreeNode myBuildTree(int inLeft, int inRight){
        if(inRight<inLeft) return null;
        TreeNode root=new TreeNode(preorder[preLeft]);
        int rootVal =preorder[preLeft];    //根节点
        int inorderRootIndex=map.get(rootVal);   //在中序遍历中定位根节点的索引
        preLeft++;
        root.left=myBuildTree(inLeft, inorderRootIndex-1);
        root.right=myBuildTree(inorderRootIndex+1, inRight);
        return root;
    }
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        this.preorder=preorder;
        this.inorder=inorder;
        preLeft=0;
        int inlen=inorder.length;
        map=new HashMap<>();
        for(int i=0; i<inorder.length; i++){
            map.put(inorder[i], i);
        }

        return myBuildTree( 0, inlen-1);
    }
}
]]>
https://blogtao.com/archives/400/feed 0
力扣 112.路径总和 https://blogtao.com/archives/395 https://blogtao.com/archives/395#respond Fri, 16 Jun 2023 07:18:17 +0000 https://blogtao.com/?p=395 Read More Read More

]]>
力扣 112.路径总和

这一题我开始的想法是把所有路径的结果保存到一个HashSet中,然后从HashSet里面查找是否存在目标元素,但是者个方法的遍历了所有的路径,实际上不需要遍历所有的路径,如果在每次到达叶子节点就判断当前路径的数值之和是否等于目标元素,就能减少遍历的次数。
有一个细节要注意:sum,flag不能定义成static,否则里面的值会被不同测试用例所共享,导致结果出错。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    private int sum=0;
    private boolean flag=false;
    private void myPath(TreeNode root, int targetSum){
        if(root==null)return;
        sum+=root.val;
        if(root.left==null && root.right==null){
            if(sum==targetSum)flag=true;
        }
        if(flag)return;
        if(root.left!=null){
            myPath(root.left,targetSum);
            //减去不符合的节点的值
            sum-=root.left.val;
        }
        if(root.right!=null){
            myPath(root.right,targetSum);
            //减去不符合的节点的值
            sum-=root.right.val;
        }
    }

    public boolean hasPathSum(TreeNode root, int targetSum) {
        myPath(root,targetSum);
        return flag;
    }
}
]]>
https://blogtao.com/archives/395/feed 0
使用递归和回溯法找二叉树的所有路径 https://blogtao.com/archives/382 https://blogtao.com/archives/382#respond Fri, 16 Jun 2023 02:32:16 +0000 https://blogtao.com/?p=382 Read More Read More

]]>
题目是 257.二叉树的所有路径

这道题需要我们找到所有从根节点到叶子结点的路径,起初我想的是使用深度优先遍历,但是我在遍历的时候没有找到方法来保存之前已经遍历过的节点。
按照代码随想录的解法,应该用一个List path保存之前已经遍历过的节点,当遍历到一个叶子节点就停止遍历,然后本轮递归的上一层递归把path的最后一个元素删掉。
如果当前节点不是叶子节点,就在递归当前节点的子节点后使用回溯,回溯的操作是删除当前节点的直接子节点,也就是path的最后一个元素,为什么只需要删除最后一个元素,因为在递归过程中已经删除了当前节点的其他子节点。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    private void myPath(List<String> list, List<Integer> path, TreeNode root){
        path.add(root.val);
        if(root.left==null && root.right==null){
            StringBuilder sb=new StringBuilder();
            for(int i=0; i<path.size()-1; i++){
                sb.append(path.get(i));
                sb.append("->");
            }
            sb.append(path.get(path.size()-1));
            list.add(sb.toString());
            return;
        }
        if(root.left!=null){
            myPath(list, path, root.left);
            //回溯到当前的节点
            path.remove(path.size()-1);
        }
        if(root.right!=null){
            myPath(list, path, root.right);
            //回溯到当前的节点
            path.remove(path.size()-1);
        }
    }
    public List<String> binaryTreePaths(TreeNode root) {
        StringBuilder sb;
        List<String> list=new ArrayList<>();
        List<Integer>path=new ArrayList<>();
        myPath(list,path,root);
        return list;
    }
}
]]>
https://blogtao.com/archives/382/feed 0