练习地址
Part 1 : https://blog.csdn.net/qq_41080854/article/details/128829494
Part 2 : https://blog.csdn.net/qq_41080854/article/details/129278336
给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
解析
dp[i][0] 买入股票的利益 dp[i][1] 卖出股票的利益
必须要先卖再买
今天卖的股票 = 今天不卖 + 今天卖(买出的收益 + 今天价格)
今天买的股票 = 今天不买 + 今天买的(卖出的价格 + 冷冻期 - 今天价格)
代码
class Solution {
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length][2];
//dp[i][0] 买入股票的利益 dp[i][1] 卖出股票的利益
dp[0][0] = -prices[0];
dp[0][1] = 0;
//必须要先卖再买
for(int i = 1; i < prices.length; i++){
//今天卖的股票 = 今天不卖 + 今天卖(买出的收益 + 今天价格)
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
//今天买的股票 = 今天不买 + 今天买的(卖出的价格 + 冷冻期 - 今天价格)
dp[i][0] = Math.max(dp[i - 1][0], (i >= 2 ? dp[i - 2][1] : 0) - prices[i]);
}
return Math.max(dp[prices.length - 1][0], dp[prices.length - 1][1]);
}
}
有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。
求所能获得硬币的最大数量。
示例 1:
输入:nums = [3,1,5,8]
输出:167
解释:
nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 315 + 358 + 138 + 181 = 167
解析
固定DP区间,而不固定爆破的气球,dp[i][j]
表示 i 到 j 之间能获得最大金币,不包括i,j边界(戳爆中间,两段不戳爆)
0<= i < k < j <=n + 1 符合这样的位置关系,只管最后一个戳爆的气球!k是最后一个戳爆的气球,分治思想dp[i][k]
前半部分,dp[k][j]
后半部分
class Solution {
public int maxCoins(int[] nums) {
int n = nums.length;
int[] helper = new int[n + 2];
// 固定区间,而不固定爆破的气球
// dp[i][j] 表示 i 到 j 之间能获得最大金币,不包括i,j边界,戳爆中间,两段不戳爆
int[][] dp = new int[n + 2][n + 2];
helper[0] = helper[n + 1] = 1;
for(int i = 1; i < n + 1; i++){
helper[i] = nums[i - 1];
}
// 0<= i < k < j <=n + 1 符合这样的位置关系,从后往前戳破气球!
for(int i = n - 1; i >= 0; i--){
for(int j = i + 2; j < n + 2; j++){
for(int k = i + 1; k < j; k++){
//k是最后一个戳爆的气球
int sum = helper[i] * helper[k] * helper[j];
sum += dp[i][k] + dp[k][j];
dp[i][j] = Math.max(dp[i][j], sum);
}
}
}
return dp[0][n + 1];
}
}
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
解析
完全背包
代码
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];// dp[i] 金额 i所需的最少硬币
Arrays.fill(dp, amount + 1);
dp[0] = 0;//0 元 0 种
for(int i = 0; i < coins.length; i++){//先遍历硬币,物品
for(int j = coins[i]; j < amount + 1; j++){//遍历金额,容量
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
}
return dp[amount] == amount + 1 ? -1 : dp[amount];
}
}
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
输入: root = [3,2,3,null,3,null,1]
输出: 7
解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7
解析
使用后序遍历,对于根的计算分为打劫和不打劫两种
class Solution {
public int rob(TreeNode root) {
if(root == null) return 0;
int[] res = dfs(root);
//int[0] 表示不打劫,int[1] 表示打劫
return Math.max(res[0], res[1]);
}
private int[] dfs(TreeNode node){
if(node == null) return new int[]{0, 0};
//后序遍历
int[] left = dfs(node.left);
int[] right = dfs(node.right);
int[] res = new int[2];
//不打劫当前节点
res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
//打劫当前节点
res[1] = node.val + left[0] + right[0];
return res;
}
}
给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。
示例 1:
输入:n = 2
输出:[0,1,1]
解释:
0 --> 0
1 --> 1
2 --> 10
示例 2:
输入:n = 5
输出:[0,1,1,2,1,2]
解释:
0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101
解析
对于任何整数都有, x = x &(x - 1),最后一位的1变为0,然后计数
代码
class Solution {
public int[] countBits(int n) {
//res[i] 第i个有多少个1
int[] res = new int[n + 1];
for(int i = 0; i < n + 1; i++){
int count = 0;
int temp = i;
while(temp != 0){
count++;
temp = temp & (temp - 1);//将最后一个位从1变为0
}
res[i] = count;
}
return res;
}
}
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
解析
在这里,我们可以利用堆的思想:建立一个大顶堆,然后遍历「出现次数数组」
class Solution {
public int[] topKFrequent(int[] nums, int k) {
int[] res = new int[k];
Map<Integer, Integer> map = new HashMap<>();
for(int num : nums){
map.put(num, map.getOrDefault(num, 0) + 1);
}
//大根堆
PriorityQueue<Integer> pq = new PriorityQueue<>((x, y) -> map.get(y) - map.get(x));
for(Integer key : map.keySet()){
pq.offer(key);
}
for(int i = 0; i < k; i++){
res[i] = (int)pq.poll();
}
return res;
}
}
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例 1:
输入:s = “3[a]2[bc]”
输出:“aaabcbc”
解析
双栈
class Solution {
public String decodeString(String s) {
// 栈
Stack<Integer> stack = new Stack<>();
// 临时结果栈
Stack<StringBuilder> temp = new Stack<>();
temp.push(new StringBuilder());
// 字符串长度、当前的乘数
int len = s.length(), mul = 0;
for (int i=0; i<len; i++) {
char c = s.charAt(i);
if (c >= '0' && c <= '9') {
// 是数字,则累计
mul = mul * 10 + (c - '0');
} else if (c == '[') {
// 遇到左边框,新增一个临时过程
stack.push(mul);
temp.push(new StringBuilder());
mul = 0;
} else if (c == ']') {
// 遇到右边框,处理临时过程到上一个临时过程
StringBuilder t = temp.pop();
temp.peek().append(t.toString().repeat(stack.pop()));
} else {
// 遇到字符串,拼接到当前的临时过程
temp.peek().append(c);
}
}
return temp.pop().toString();
}
}
给你一个变量对数组 equations 和一个实数值数组 values 作为已知条件,其中 equations[i] = [Ai, Bi] 和 values[i] 共同表示等式 Ai / Bi = values[i] 。每个 Ai 或 Bi 是一个表示单个变量的字符串。
另有一些以数组 queries 表示的问题,其中 queries[j] = [Cj, Dj] 表示第 j 个问题,请你根据已知条件找出 Cj / Dj = ? 的结果作为答案。
返回 所有问题的答案 。如果存在某个无法确定的答案,则用 -1.0 替代这个答案。如果问题中出现了给定的已知条件中没有出现的字符串,也需要用 -1.0 替代这个答案。
注意:输入总是有效的。你可以假设除法运算中不会出现除数为 0 的情况,且不存在任何矛盾的结果。
示例 1:
输入:equations = [[“a”,“b”],[“b”,“c”]], values = [2.0,3.0], queries = [[“a”,“c”],[“b”,“a”],[“a”,“e”],[“a”,“a”],[“x”,“x”]]
输出:[6.00000,0.50000,-1.00000,1.00000,-1.00000]
解释:
条件:a / b = 2.0, b / c = 3.0
问题:a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?
结果:[6.0, 0.5, -1.0, 1.0, -1.0 ]
解析
将其转换为图,并使用带权重的并查集来模拟
代码
class Solution {
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
UnionFind union = new UnionFind();
int i = 0;
for(List<String> eq: equations){
String a = eq.get(0);
String b = eq.get(1);
double value = values[i++];
union.union(a, b, value);
}
double[] res = new double[queries.size()];
i = 0;
for(List<String> query: queries){
String x = query.get(0);
String y = query.get(1);
res[i++] = union.query(x, y);
}
return res;
}
//带权重的并查集
private class UnionFind{
private Map<String, String> parent;
private Map<String, Double> weight;
public UnionFind(){
parent = new HashMap<>();
weight = new HashMap<>();
}
public void union(String x, String y, double value){
parent.putIfAbsent(x, x);
parent.putIfAbsent(y, y);
weight.putIfAbsent(x, 1.0d);
weight.putIfAbsent(y, 1.0d);
String xRoot = find(x);
String yRoot = find(y);
if (!xRoot.equals(yRoot)) {
parent.put(xRoot, yRoot);
weight.put(xRoot, weight.get(y) * value / weight.get(x));
}
}
//找到根节点
public String find(String x){
if(!parent.get(x).equals(x)){
String oldPatient = parent.get(x);
parent.put(x, find(parent.get(x)));
weight.put(x, weight.get(oldPatient) * weight.get(x));
}
return parent.get(x);
}
public double query(String x, String y) {
if (!parent.containsKey(x) || !parent.containsKey(y)) {
return -1.0d;
}
String xRoot = find(x);
String yRoot = find(y);
if (xRoot.equals(yRoot)) {
return weight.get(x) / weight.get(y);
} else {
return -1.0d;
}
}
}
}
假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
示例 1:
输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
解析
(套路):一般这种数对,还涉及排序的,根据第一个元素正向排序,根据第二个元素反向排序,或者根据第一个元素反向排序,根据第二个元素正向排序,往往能够简化解题过程。
本题先第一个元素逆序,然后第二个元素升序,接着插队
class Solution {
public int[][] reconstructQueue(int[][] people) {
//数组的第一个元素进行逆序,数组第二个元素正序
Arrays.sort(people, (x, y) -> {
if(x[0] == y[0]){
return x[1] - y[1];//升序
}else{
return y[0] - x[0];//降序
}
});
//再插队,根据下标k进行插队
List<int[]> res = new ArrayList<>();
for(int[] p : people){
res.add(p[1], p);
}
return res.toArray(new int[res.size()][0]);
}
}
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
解析
0,1 背包问题
代码
class Solution {
public boolean canPartition(int[] nums) {
// 0 1背包问题
int n = nums.length;
int sum = 0; //统计nums的总数
for(int num : nums){
sum += num;
}
if(sum % 2 != 0) return false;
int target = sum / 2;
int[] dp = new int[target + 1];//dp[i] 表示第 i 个正整数的元素和
// 0 1 背包模板
for(int i = 0; i < n; i++) {
for(int j = target; j >= nums[i]; j--) {
// 重量和价值都为nums[i]
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
return dp[target] == target;
}
}
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示
解析
先序递归遍历每个节点,再以每个节点作为起始点递归寻找满足条件的路径,双重递归
代码
class Solution {
long path = 0;
public int pathSum(TreeNode root, int targetSum) {
//先序遍历每个结点,然后每个结点作为起始点递归查找
if(root == null) return 0;
sum(root, targetSum);
pathSum(root.left, targetSum);
pathSum(root.right, targetSum);
return (int)path;
}
private void sum(TreeNode root, long targetSum){
if(root == null) return;
targetSum -= root.val;
if(targetSum == 0) path++;
sum(root.left, targetSum);
sum(root.right, targetSum);
}
}
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
示例 1:
输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的异位词。
输入: s = “abab”, p = “ab”
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的异位词。
解析
滑动窗口
代码
class Solution {
public List<Integer> findAnagrams(String s, String p) {
int[] map = new int[128];
for(char c : p.toCharArray()) map[c]++;
List<Integer> res = new ArrayList<>();
for(int left = 0, right = 0; right < s.length(); right++){
map[s.charAt(right)]--;
while(map[s.charAt(right)] < 0){
map[s.charAt(left++)]++;
}
if(right - left + 1 == p.length()){
res.add(left);
}
}
return res;
}
}
给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。
示例 1:
输入:nums = [4,3,2,7,8,2,3,1]
输出:[5,6]
解析
原地哈希,将num - 1 + n 作为判断的依据
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
int n = nums.length;
for(int num : nums){
int x = (num - 1) % n;
nums[x] += n;
}
List<Integer> res = new ArrayList<>();
for(int i = 0; i < n; i++){
if(nums[i] <= n){
res.add(i + 1);
}
}
return res;
}
}
两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。
给你两个整数 x 和 y,计算并返回它们之间的汉明距离。
示例 1:
输入:x = 1, y = 4
输出:2
解释:
1 (0 0 0 1)
4 (0 1 0 0)
↑ ↑
上面的箭头指出了对应二进制位不同的位置。
示例 2:
输入:x = 3, y = 1
输出:1
解析
位操作
代码
class Solution {
public int hammingDistance(int x, int y) {
int s = x ^ y;
int count = 0;
while(s != 0){
count += s & 1;//count + 该位是否不同
s = s >> 1;
}
return count;
}
}
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
示例 2:
输入:nums = [1], target = 1
输出:1
解析
sum(P) 前面符号为+的集合;sum(N) 前面符号为减号的集合,所以题目可以转化为
sum(P) - sum(N) = target
=> sum(nums) + sum(P) - sum(N) = target + sum(nums)
=> 2 * sum(P) = target + sum(nums)
=> sum(P) = (target + sum(nums)) / 2
因此题目转化为01背包,装满背包,也就是能组合成容量为sum(P)的方式有多少种
代码
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for(int num : nums){
sum += num;
}
if(sum < target || (sum + target) % 2 == 1) return 0;
//数学推导
int w = (sum + target) / 2;
if(w < 0) return 0;
//dp[i] 表示第i个目标和由多少个正数方案组成
int[] dp = new int[w + 1];
dp[0] = 1;
//0 1 背包模板
for(int i = 0; i < nums.length; i++){
for(int j = w; j >= nums[i]; j--){
//装满背包
dp[j] += dp[j - nums[i]];
}
}
return dp[w];
}
}
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
节点的左子树仅包含键 小于 节点键的节点。
节点的右子树仅包含键 大于 节点键的节点。
左右子树也必须是二叉搜索树。
解析
中序遍历二叉搜素数是递增的,反序遍历是递减的,这是就可以按照题目累加值并赋值给val
class Solution {
int sum = 0;
public TreeNode convertBST(TreeNode root) {
if(root == null) return null;
convertBST(root.right);
sum += root.val;
root.val = sum;
convertBST(root.left);
return root;
}
}
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
解析
dfs计算子树高度。左右子树高度之和和max的最大值即为直径
class Solution {
int max = 0;
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
return max;
}
private int dfs(TreeNode node){
if(node == null) return 0;
int left = dfs(node.left);
int right = dfs(node.right);
max = Math.max(left + right, max);//左右子树高度之和和max的最大值即为直径
return Math.max(left, right) + 1;//计算子树高度
}
}
给你一个整数数组 nums 和一个整数 k ,请你统计并返回该数组中和为 k 的连续子数组的个数 。
示例 1:
输入:nums = [1,1,1], k = 2
输出:2
解析
前缀和 + 哈希表(存放前缀和–个数)
代码
class Solution {
public int subarraySum(int[] nums, int k) {
int count = 0, pre = 0;//pre 为 前缀和
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0, 1);//前缀和为0, 个数为1
for(int num : nums){
pre += num;
if(map.containsKey(pre - k)){
count += map.get(pre - k);
}
map.put(pre, map.getOrDefault(pre, 0) + 1);
}
return count;
}
}
给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
输入:nums = [2,6,4,8,10,9,15]
输出:5
解释:你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
解析
这个数组分成三段,左段和右段是标准的升序数组,中段数组虽是无序的,但满足最小值大于左段的最大值,最大值小于右段的最小值。
中段的左右边界,分别定义为begin 和 end;
分两头开始遍历:
从左到右维护一个最大值max,在进入右段之前,那么遍历到的nums[i]都是小于max的,我们要求的end就是遍历中最后一个小于max元素的位置;
同理,从右到左维护一个最小值min,在进入左段之前,那么遍历到的nums[i]也都是大于min的,要求的begin也就是最后一个大于min元素的位置。
代码
class Solution {
public int findUnsortedSubarray(int[] nums) {
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
int left = 0, right = -1; //确保 right - left + 1为0
for(int i = 0; i < nums.length; i++){
if(nums[i] < max){//从左到右维护max,max的前一个就是right
right = i;
}else{
max = nums[i];
}
if(nums[nums.length - 1 - i] > min){ //从右到做维护min,min的后一个就是left
left = nums.length - 1 - i;
}else{
min = nums[nums.length - 1 - i];
}
}
return right - left + 1;
}
}
给你两棵二叉树: root1 和 root2 。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
解析
先序遍历
代码
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1 == null) return root2;
if(root2 == null) return root1;
TreeNode node = new TreeNode(root1.val + root2.val);
node.left = mergeTrees(root1.left, root2.left);
node.right = mergeTrees(root1.right, root2.right);
return node;
}
}
给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。
然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。
你需要计算完成所有任务所需要的 最短时间 。
解析
解题思路:
1、将任务按类型分组,正好A-Z用一个int[26]保存任务类型个数
2、对数组进行排序,优先排列个数(count)最大的任务,如题得到的时间至少为
retCount =(count-1)* (n+1) + 1 ==> A->X->X->A->X->X->A(X为其他任务或者待命)
3、再排序下一个任务,如果下一个任务B个数和最大任务数一致,则retCount++ ==> A->B->X->A->B->X->A->B
4、如果空位都插满之后还有任务,那就随便在这些间隔里面插入就可以,因为间隔长度肯定会大于n,在这种情况下就是任务的总数是最小所需时间
class Solution {
public int leastInterval(char[] tasks, int n) {
int[] map = new int[26];
for(char task : tasks){
map[task - 'A']++;
}
Arrays.sort(map);
int res = (n + 1) * (map[25] - 1) + 1;
for(int i = 0; i < 25; i++){
if(map[i] == map[25]){
res++;
}
}
return Math.max(res, tasks.length);
}
}
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例 1:
输入:s = “abc”
输出:3
解释:三个回文子串: “a”, “b”, “c”
解析
中心扩散法,只有满足条件才计数
class Solution {
int res = 0;
public int countSubstrings(String s) {
for(int i = 0; i < s.length(); i++){
count(s, i, i);//奇数
count(s, i, i + 1);//偶数
}
return res;
}
private void count(String s, int left, int right){
while(left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){
res++;
left--;
right++;
}
}
}
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
解析
单调栈,栈内元素递减的
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int[] res = new int[temperatures.length];
Deque<Integer> stack = new LinkedList<>();
for(int i = 0; i < temperatures.length; i++){
while(!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]){
int idx = stack.pop();
res[idx] = i - idx;
}
stack.push(i);
}
return res;
}
}