代码随想录算法训练营第十八天

LeetCode题目:

  • [669. 修剪二叉搜索树](#669. 修剪二叉搜索树)
  • [108. 将有序数组转换为二叉搜索树](#108. 将有序数组转换为二叉搜索树)
  • [538. 把二叉搜索树转换为累加树](#538. 把二叉搜索树转换为累加树)
  • [2179. 统计数组中好三元组数目(每日一题)](#2179. 统计数组中好三元组数目(每日一题))
  • [307. 区域和检索 - 数组可修改(树状数组)](#307. 区域和检索 - 数组可修改(树状数组))
  • [122. 买卖股票的最佳时机 II](#122. 买卖股票的最佳时机 II)
  • [309. 买卖股票的最佳时机含冷冻期](#309. 买卖股票的最佳时机含冷冻期)
  • [188. 买卖股票的最佳时机 IV](#188. 买卖股票的最佳时机 IV)

其他:

今日总结
往期打卡


669. 修剪二叉搜索树

跳转:
学习: 代码随想录公开讲解

问题:

给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案

所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。

思路:

这题需要利用二叉搜索树的性质

遍历,如果小于边界搜右边代替自己返回,如果大于自己搜左边代替自己返回

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码(前序遍历):

java 复制代码
class Solution {
    public TreeNode trimBST(TreeNode root, int low, int high) {
        if(root==null) return null;
        if(root.val<low) return trimBST(root.right,low,high);
        if(root.val>high) return trimBST(root.left,low,high);
        root.left = trimBST(root.left,low,high);
        root.right = trimBST(root.right,low,high);
        return root;        
    }
}

108. 将有序数组转换为二叉搜索树

跳转:
学习: 代码随想录公开讲解

问题:

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。

思路:

平衡二叉树 是指该树所有节点的左右子树的高度相差不超过1

所以这题需要对数组二分.

每次平分数组向下遍历到 l>=r 即可

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码(前序遍历):

java 复制代码
class Solution {
    TreeNode buildTree(int[] nums,int left,int right){
        if(left>=right) return null;
        int mid = (left+right)/2;
        TreeNode root = new TreeNode(nums[mid]);
        if(right-left==1) return root;
        root.left = buildTree(nums,left,mid);
        root.right = buildTree(nums,mid+1,right);
        return root;
    }
    public TreeNode sortedArrayToBST(int[] nums) {
        return buildTree(nums,0,nums.length);
    }
}

538. 把二叉搜索树转换为累加树

跳转:
学习: 代码随想录公开讲解

问题:

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

提醒一下,二叉搜索树满足下列约束条件:

  • 节点的左子树仅包含键 小于 节点键的节点。
  • 节点的右子树仅包含键 大于 节点键的节点。
  • 左右子树也必须是二叉搜索树。

思路:

二叉树搜索树中序遍历有序,这题要把节点替换为大于等于当前节点的累加和,所以可以逆反中序遍历,然后额外使用一个遍历记录上一次的值.

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码(逆反中序遍历):

java 复制代码
class Solution {
    int pre = 0;
    public TreeNode convertBST(TreeNode root) {
        if(root==null) return null;
        convertBST(root.right);
        root.val+=pre;
        pre = root.val;
        convertBST(root.left);
        return root;        
    }
}

2179. 统计数组中好三元组数目(每日一题)

跳转:
灵茶山艾府题解

问题:

给你两个下标从 0 开始且长度为 n 的整数数组 nums1nums2 ,两者都是 [0, 1, ..., n - 1]排列

好三元组 指的是 3互不相同 的值,且它们在数组 nums1nums2 中出现顺序保持一致。换句话说,如果我们将 pos1v 记为值 vnums1 中出现的位置,pos2v 为值 vnums2 中的位置,那么一个好三元组定义为 0 <= x, y, z <= n - 1 ,且 pos1x < pos1y < pos1zpos2x < pos2y < pos2z 都成立的 (x, y, z)

请你返回好三元组的 总数目

思路:

这题直接做是找公共子序列个数,因为都是 0 到 n-1 的排序 ,所以可以通过映射转化为求递增子序列问题(获得一个索引哈希,另一个顺序枚举中间,看看之前有多少比自己小的合法值,就能算出之后有多少比自己大的合法值)

知道前面合法的 l e s s y less_y lessy,后面合法的就是 n − 1 − y − ( i − l e s s y ) n−1−y−(i−less_y) n−1−y−(i−lessy)

合记:

但哪怕使用前缀和,如果直接暴力,也会超时

所以需要使用树状数组这种特殊的数据结构来计算合法前缀和

复杂度:

  • 时间复杂度: O ( n l o g n ) O(nlog_n) O(nlogn)
  • 空间复杂度: O ( n ) O(n) O(n)

代码:

java 复制代码
class FenwickTree {
    private final int[] tree;

    public FenwickTree(int n) {
        tree = new int[n + 1]; // 使用下标 1 到 n
    }

    // a[i] 增加 val
    // 1 <= i <= n
    public void update(int i, long val) {
        for (; i < tree.length; i += i & -i) {
            tree[i] += val;
        }
    }

    // 求前缀和 a[1] + ... + a[i]
    // 1 <= i <= n
    public int pre(int i) {
        int res = 0;
        for (; i > 0; i &= i - 1) {
            res += tree[i];
        }
        return res;
    }
}

class Solution {
    public long goodTriplets(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int[] p = new int[n];
        for (int i = 0; i < n; i++) {
            p[nums1[i]] = i;
        }
        long ans = 0;
        FenwickTree ft = new FenwickTree(n);
        for(int i=0;i<n;i++) {
            int y = p[nums2[i]];
            long less = ft.pre(y);
            ans+=less*(n-1-y-i+less);
        	ft.update(y+1,1);
        }
        return ans;
    }
}

307. 区域和检索 - 数组可修改(树状数组)

跳转:
学习: 动画讲解讲解

问题:

给你一个数组 nums ,请你完成两类查询。

  1. 其中一类查询要求 更新 数组 nums 下标对应的值
  2. 另一类查询要求返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的 ,其中 left <= right

实现 NumArray 类:

  • NumArray(int[] nums) 用整数数组 nums 初始化对象
  • void update(int index, int val)nums[index] 的值 更新val
  • int sumRange(int left, int right) 返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的 (即,nums[left] + nums[left + 1], ..., nums[right]

思路:

对于更新操作,通过索引可以定位到其对应的最底层,然后直接向上修改即可.上层在后面,每次向上刚好是加上bit最左位 lowbit .

查询操作也是先定位底层,不过是要加前缀,所以每次向上是减去bit最左位 lowbit.

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

代码:

java 复制代码
class NumArray {
    private final int[] tree;
    private final int[] nums;
    public NumArray(int[] nums) {
        int n= nums.length;
        this.nums = nums;
        tree = new int[n+1];
        for(int i=1;i<=n;i++){
            tree[i]+=nums[i-1];
            int nxt = i+(i&-i);
            if(nxt<=n) tree[nxt] += tree[i];
        }
    }
    
    public void update(int index, int val) {
        int delta = val - nums[index];
        nums[index] = val;
        for(int i=index+1;i<tree.length;i+=i&-i){
            tree[i] += delta;
        }
    }
    
    private int prefixSum(int i) {
        int s = 0;
        for (; i > 0; i &= i - 1) { // i -= i & -i 的另一种写法
            s += tree[i];
        }
        return s;
    }

    public int sumRange(int left, int right) {
        return prefixSum(right+1)-prefixSum(left);
    }
}

122. 买卖股票的最佳时机 II

跳转:
学习: 灵茶山艾府题解 & 公开讲解

问题:

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润

思路:

可以考虑股票持有和未持有两种状态,每种状态都可以基于前一天持有或未持有变化而来

这题如果递归必须要加记忆化搜索

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码(递推):

java 复制代码
class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int p1 = 0;
        int p2 = Integer.MIN_VALUE;
        for(int i=1;i<n;i++){
            int tmp = p1;
            p2 = Math.max(p1-prices[i-1],p2);
            p1 = Math.max(p2+prices[i-1],tmp);
            // System.out.println(p1+" "+p2);
        }
        return Math.max(p2+prices[n-1],p1);
    }
}

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

代码(递归):

java 复制代码
class Solution {
    private int[][] memo;
    public int dfs(int[] prices,int day,int choose){
        if(day==0){
            return choose==1?-100000:0;
        } 
        if(memo[day][choose]!=-1)
        return memo[day][choose];
        int a = dfs(prices,day-1,0);
        int b = dfs(prices,day-1,1);
        if(choose==1){
            return memo[day][1] =  Math.max(a-prices[day-1],b);
        }
        else{
            return memo[day][0] = Math.max(b+prices[day-1],a);
        }
    }
    public int maxProfit(int[] prices) {
        int n = prices.length;
        memo = new int[n+1][2];
        for(int[] row:memo){
            Arrays.fill(row,-1);
        }
        return dfs(prices,n,0);
    }
}

309. 买卖股票的最佳时机含冷冻期

跳转:
学习: 灵茶山艾府题解 & 公开讲解

问题:

给定一个整数数组prices,其中第 prices[i] 表示第 *i* 天的股票价格 。

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

思路:

可以考虑股票持有和未持有两种状态,每种状态都可以基于前一天持有或未持有变化而来

这题如果递归必须要加记忆化搜索

这题就是比上题多个冷冻期,考虑买入时不能是刚卖出即可

如果写递推,因为设计前天,需要三个变量

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码(递推):

java 复制代码
class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int p1 = 0;
        int pre_p1 = 0;
        int p2 = Integer.MIN_VALUE;
        for(int i=0;i<n;i++){
            int tmp = p1;
            p2 = Math.max(pre_p1-prices[i],p2);
            p1 = Math.max(p2+prices[i],tmp);
            pre_p1 = tmp;
            // System.out.println(p1+" "+p2);
        }
        return p1;
    }
}

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

代码(递归):

java 复制代码
class Solution {
    private int[][] memo;

    public int dfs(int[] prices, int day, int choose) {
        if (day < 0) {
            return choose == 1 ? -100000 : 0;
        }
        if (memo[day][choose] != -1)
            return memo[day][choose];
        if (choose == 1) {
            return memo[day][1] = Math.max(dfs(prices, day - 2, 0) - prices[day], dfs(prices, day - 1, 1));
        }
        return memo[day][0] = Math.max(dfs(prices, day - 1, 1) + prices[day], dfs(prices, day - 1, 0));
    }

    public int maxProfit(int[] prices) {
        int n = prices.length;
        memo = new int[n+1][2];
        for (int[] row : memo) {
            Arrays.fill(row, -1);
        }
        return dfs(prices, n-1, 0);
    }
}

188. 买卖股票的最佳时机 IV

跳转:
学习: 灵茶山艾府题解 & 公开讲解

问题:

给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。

**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

思路:

可以考虑股票持有和未持有两种状态,每种状态都可以基于前一天持有或未持有变化而来

这题如果递归必须要加记忆化搜索

这题限制交易次数,所以需要额外加一维状态标识

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码(迭代):

java 复制代码
class Solution {
    public int maxProfit(int k, int[] prices) {
        int[][] f = new int[k + 2][2];
        for (int j = 0; j <= k + 1; j++) {
            f[j][1] = Integer.MIN_VALUE / 2;
        }
        for (int p : prices) {
            for (int i=k+1;i>0;i--) {
                f[i][1] = Math.max(f[i][0] - p, f[i][1]);
                f[i][0] = Math.max(f[i-1][1] + p, f[i][0]);
                // System.out.println(p1+" "+p2);
            }
        }
        return f[k+1][0];
    }
}

复杂度:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

代码(递归):

java 复制代码
class Solution {
    private int[][][] memo;

    public int dfs(int[] prices, int day, int choose,int k) {
        if(k<0) return -100000;
        if (day < 0) {
            return choose == 1 ? -100000 : 0;
        }
        if (memo[day][k][choose] != -1)
            return memo[day][k][choose];
        if (choose == 1) {
            return memo[day][k][1] = Math.max(dfs(prices, day - 1, 0,k) - prices[day], dfs(prices, day - 1, 1,k));
        }
        return memo[day][k][0] = Math.max(dfs(prices, day - 1, 1,k-1) + prices[day], dfs(prices, day - 1, 0,k));
    }
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        memo = new int[n][k+1][2];
        for (int[][] row : memo) {
            for(int[] row2 : row)
                Arrays.fill(row2, -1);
        }
        return dfs(prices, n-1, 0,k);
    }
}

总结

今天继续练习二叉树,复习了平衡二叉树和累加树

了解了树状数组和状态机动规的思路

往期打卡

代码随想录算法训练营第十七天

代码随想录算法训练营周末三

代码随想录算法训练营第十六天

代码随想录算法训练营第十五天

代码随想录算法训练营第十四天

代码随想录算法训练营第十三天

代码随想录算法训练营第十二天

代码随想录算法训练营第十一天

代码随想录算法训练营周末二

代码随想录算法训练营第十天

代码随想录算法训练营第九天

代码随想录算法训练营第八天

代码随想录算法训练营第七天

代码随想录算法训练营第六天

代码随想录算法训练营第五天

代码随想录算法训练营周末一

代码随想录算法训练营第四天

代码随想录算法训练营第三天

代码随想录算法训练营第二天

代码随想录算法训练营第一天

*[669. 修剪二叉搜索树]: LeetCode
*[188. 买卖股票的最佳时机 IV]: LeetCode
*[309. 买卖股票的最佳时机含冷冻期]: LeetCode
*[2179. 统计数组中好三元组数目]: LeetCode
*[122. 买卖股票的最佳时机 II]: LeetCode
*[108. 将有序数组转换为二叉搜索树]: LeetCode
*[538. 把二叉搜索树转换为累加树]: LeetCode
*[307. 区域和检索 - 数组可修改]: LeetCode

相关推荐
JK0x071 小时前
代码随想录算法训练营 Day40 动态规划Ⅷ 股票问题
算法·动态规划
Feliz..1 小时前
关于离散化算法的看法与感悟
算法
水蓝烟雨2 小时前
1128. 等价多米诺骨牌对的数量
算法·hot 100
codists2 小时前
《算法导论(第4版)》阅读笔记:p11-p13
算法
Kidddddult4 小时前
力扣刷题Day 43:矩阵置零(73)
算法·leetcode·力扣
大龄Python青年6 小时前
C语言 交换算法之加减法,及溢出防范
c语言·开发语言·算法
啊我不会诶6 小时前
CF每日5题
算法
朱剑君7 小时前
排序算法——基数排序
算法·排序算法
COOCC17 小时前
PyTorch 实战:从 0 开始搭建 Transformer
人工智能·pytorch·python·深度学习·算法·机器学习·transformer
拾忆-eleven8 小时前
C++算法(19):整数类型极值,从INT_MIN原理到跨平台开发实战
数据结构·c++·算法