代码随想录算法训练营第十八天
LeetCode题目:
- 669. 修剪二叉搜索树
- 108. 将有序数组转换为二叉搜索树
- 538. 把二叉搜索树转换为累加树
- 2179. 统计数组中好三元组数目(每日一题)
- 307. 区域和检索 - 数组可修改(树状数组)
- 122. 买卖股票的最佳时机 II
- 309. 买卖股票的最佳时机含冷冻期
- 188. 买卖股票的最佳时机 IV
其他:
今日总结
往期打卡
669. 修剪二叉搜索树
跳转: 669. 修剪二叉搜索树
学习: 代码随想录公开讲解
问题:
给你二叉搜索树的根节点 root
,同时给定最小边界low
和最大边界 high
。通过修剪二叉搜索树,使得所有节点的值在[low, high]
中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
思路:
这题需要利用二叉搜索树的性质
遍历,如果小于边界搜右边代替自己返回,如果大于自己搜左边代替自己返回
复杂度:
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
代码(前序遍历):
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. 将有序数组转换为二叉搜索树
跳转: 108. 将有序数组转换为二叉搜索树
学习: 代码随想录公开讲解
问题:
给你一个整数数组 nums
,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。
思路:
平衡二叉树是指该树所有节点的左右子树的高度相差不超过1
所以这题需要对数组二分.
每次平分数组向下遍历到 l>=r 即可
复杂度:
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
代码(前序遍历):
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. 把二叉搜索树转换为累加树
跳转: 538. 把二叉搜索树转换为累加树
学习: 代码随想录公开讲解
问题:
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node
的新值等于原树中大于或等于 node.val
的值之和。
提醒一下,二叉搜索树满足下列约束条件:
- 节点的左子树仅包含键 小于 节点键的节点。
- 节点的右子树仅包含键 大于 节点键的节点。
- 左右子树也必须是二叉搜索树。
思路:
二叉树搜索树中序遍历有序,这题要把节点替换为大于等于当前节点的累加和,所以可以逆反中序遍历,然后额外使用一个遍历记录上一次的值.
复杂度:
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
代码(逆反中序遍历):
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. 统计数组中好三元组数目(每日一题)
跳转: 2179. 统计数组中好三元组数目
灵茶山艾府题解
问题:
给你两个下标从 0 开始且长度为 n
的整数数组 nums1
和 nums2
,两者都是 [0, 1, ..., n - 1]
的 排列 。
好三元组 指的是 3
个 互不相同 的值,且它们在数组 nums1
和 nums2
中出现顺序保持一致。换句话说,如果我们将 pos1v
记为值 v
在 nums1
中出现的位置,pos2v
为值 v
在 nums2
中的位置,那么一个好三元组定义为 0 <= x, y, z <= n - 1
,且 pos1x < pos1y < pos1z
和 pos2x < 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)
代码:
class FenwickTree {private final int[] tree;public FenwickTree(int n) {tree = new int[n + 1]; // 使用下标 1 到 n}// a[i] 增加 val// 1 <= i <= npublic void update(int i, long val) {for (; i < tree.length; i += i & -i) {tree[i] += val;}}// 求前缀和 a[1] + ... + a[i]// 1 <= i <= npublic 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. 区域和检索 - 数组可修改(树状数组)
跳转: 307. 区域和检索 - 数组可修改
学习: 动画讲解讲解
问题:
给你一个数组 nums
,请你完成两类查询。
- 其中一类查询要求 更新 数组
nums
下标对应的值 - 另一类查询要求返回数组
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)
代码:
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
跳转: 122. 买卖股票的最佳时机 II
学习: 灵茶山艾府题解 & 公开讲解
问题:
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
思路:
可以考虑股票持有和未持有两种状态,每种状态都可以基于前一天持有或未持有变化而来
这题如果递归必须要加记忆化搜索
复杂度:
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
代码(递推):
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)
代码(递归):
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. 买卖股票的最佳时机含冷冻期
跳转: 309. 买卖股票的最佳时机含冷冻期
学习: 灵茶山艾府题解 & 公开讲解
问题:
给定一个整数数组prices
,其中第 prices[i]
表示第 *i*
天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路:
可以考虑股票持有和未持有两种状态,每种状态都可以基于前一天持有或未持有变化而来
这题如果递归必须要加记忆化搜索
这题就是比上题多个冷冻期,考虑买入时不能是刚卖出即可
如果写递推,因为设计前天,需要三个变量
复杂度:
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
代码(递推):
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)
代码(递归):
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
跳转: 188. 买卖股票的最佳时机 IV
学习: 灵茶山艾府题解 & 公开讲解
问题:
给你一个整数数组 prices
和一个整数 k
,其中 prices[i]
是某支给定的股票在第 i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k
笔交易。也就是说,你最多可以买 k
次,卖 k
次。
**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路:
可以考虑股票持有和未持有两种状态,每种状态都可以基于前一天持有或未持有变化而来
这题如果递归必须要加记忆化搜索
这题限制交易次数,所以需要额外加一维状态标识
复杂度:
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
代码(迭代):
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)
代码(递归):
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);}
}
总结
今天继续练习二叉树,复习了平衡二叉树和累加树
了解了树状数组和状态机动规的思路
往期打卡
代码随想录算法训练营第十七天
代码随想录算法训练营周末三
代码随想录算法训练营第十六天
代码随想录算法训练营第十五天
代码随想录算法训练营第十四天
代码随想录算法训练营第十三天
代码随想录算法训练营第十二天
代码随想录算法训练营第十一天
代码随想录算法训练营周末二
代码随想录算法训练营第十天
代码随想录算法训练营第九天
代码随想录算法训练营第八天
代码随想录算法训练营第七天
代码随想录算法训练营第六天
代码随想录算法训练营第五天
代码随想录算法训练营周末一
代码随想录算法训练营第四天
代码随想录算法训练营第三天
代码随想录算法训练营第二天
代码随想录算法训练营第一天