437.给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
递归回溯+前缀和:
/**
* 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;
* }
* }
*/
//前缀和————就是到达当前元素的路径上,之前所有元素的和
//思想核心:如果前缀总和currSum,在节点A和节点B处相差target,则位于节点A和节点B之间的元素之和是target。
class Solution {
public int pathSum(TreeNode root, int sum) {
// key是前缀和, value是大小为key的前缀和出现的次数
Map<Integer, Integer> prefixSumCount = new HashMap<>();
// 前缀和为0的一条路径
prefixSumCount.put(0, 1);
// 前缀和的递归回溯思路
return recursionPathSum(root, prefixSumCount, sum, 0);
}
/**
* @param node 树节点
* @param prefixSumCount 前缀和Map
* @param target 目标值
* @param currSum 当前路径和
* @return 满足题意的解
*/
private int recursionPathSum(TreeNode node, Map<Integer, Integer> prefixSumCount, int target, int currSum) {
// 1.递归终止条件
if (node == null) {
return 0;
}
// 2.本层要做的事情
int res = 0;
currSum += node.val;// 当前路径上的和
//---核心代码
// 看看root到当前节点这条路上是否存在节点前缀和加target为currSum的路径
res += prefixSumCount.getOrDefault(currSum - target, 0);
// 更新路径上当前节点前缀和的个数
prefixSumCount.put(currSum, prefixSumCount.getOrDefault(currSum, 0) + 1);
//---核心代码
// 3.进入下一层
res += recursionPathSum(node.left, prefixSumCount, target, currSum);
res += recursionPathSum(node.right, prefixSumCount, target, currSum);
// 4.回溯结束,回到本层,恢复状态,去除当前节点的前缀和数量,保证其不影响其他分支的结果
prefixSumCount.put(currSum, prefixSumCount.get(currSum) - 1); // 与更新路径上当前节点前缀和的个数对应
return res;
}
}
深度优先搜索(时间复杂度:O(N^2) 不推荐):
class Solution {
public int pathSum(TreeNode root, int targetSum) {
if (root == null) { //二叉树为空则没有路径满足
return 0;
}
//res=从根节点开始的+从左节点开始的+从右节点开始的满足条件的数目和
int res = rootSum(root, targetSum) + pathSum(root.left, targetSum) + pathSum(root.right, targetSum);
return res;
}
public int rootSum(TreeNode root, int targetSum) {
if (root == null) {
return 0;
}
int res = 0;
int val = root.val; //这个节点的数值
if (val == targetSum) { //这个节点本身的数值等于targetSum,路径加一
res++;
}
//从root开始的满足条件的路径数目和=这个节点本身所带来的路径(0或1)+左边节点的路径+右边节点的路径
res = res + rootSum(root.left, targetSum - val) + rootSum(root.right, targetSum - val);
return res;
}
}
438.给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
滑动窗口 + 双指针:
class Solution {
public List<Integer> findAnagrams(String s, String p) {
int n = s.length(), m = p.length();
List<Integer> res = new ArrayList<>();
if(n < m) // n<m则不可能找到子串
return res;
//初始化
int[] pCnt = new int[26];
int[] sCnt = new int[26];
// 得到字符串p中每个元素分别对应的数量
for(int i = 0; i < m; i++){
pCnt[p.charAt(i) - 'a'] ++;
}
//滑动窗口+双指针核心代码
int left = 0;
for(int right = 0; right < n; right++){
int curRight = s.charAt(right) - 'a';
sCnt[curRight]++;// 右边增
while(sCnt[curRight] > pCnt[curRight]){
// 是通过比较s中元素curRight的数目和p当中对应元素的数目来滑动的
// 遇到s有p没有的元素不会进入while循环,cnt++对之后也不会产生任何影响
// 另外像开头cba这样,也不会进入循环,因为sCnt[curRight]处于<=pCnt[curRight]的状态,只有>才会进入循环
int curLeft = s.charAt(left) - 'a';
sCnt[curLeft]--;// 左边减
left++;
}
if(right - left + 1 == m){
res.add(left);
}
}
return res;
}
}
448.给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
//遍历nums,每遇到一个数x,就让nums[x−1](第x个)增加n。由于nums中所有数均在[1,n]中,增加以后,这些数必然大于n。最后我们遍历nums,若nums[i]未大于n,就说明没有遇到过数i+1。这样我们就找到了缺失的数字。
int n = nums.length;
for (int num : nums) {
int x = (num - 1) % n; //得到已经加过n的数字的原来的数字,进而不影响后面数字的加n
nums[x] += n;
}
List<Integer> res = new ArrayList<Integer>();
for (int i = 0; i < n; i++) {
if (nums[i] <= n) {
res.add(i + 1);
}
}
return res;
}
}
461.两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。
给你两个整数 x 和 y,计算并返回它们之间的汉明距离。
内置位计数功能(Integer.bitCount()):
//https://blog.youkuaiyun.com/weixin_43840202/article/details/107401101
class Solution {
public int hammingDistance(int x, int y) {
return Integer.bitCount(x ^ y);
}
}
移位实现位计数(上一方法的手写实现,利用s & 1):
class Solution {
public int hammingDistance(int x, int y) {
int s = x ^ y, res = 0;
while (s != 0) { //相当于求s二进制1的个数
res += s & 1;//和1&,res加不加看最后一位
s >>= 1; //整体右移一位,这样s的最低位将被舍去
}
return res;
}
}
Brian Kernighan 算法(利用x&(x-1)):
class Solution {
public int hammingDistance(int x, int y) {
int s = x ^ y, res = 0;
while (s != 0) { //相当于求s二进制1的个数
s &= s - 1; //对于任意整数,其中x&(x-1),就是将x的二进制数中最后一个1变成0的操作。
res++;
}
return res;
}
}
494.给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
目标和@力扣官方题解⭐
动态规划(0-1背包,注意二维变一维的优化过程):
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int i = 0; i < nums.length; i++) sum += nums[i];
if ((target + sum) % 2 != 0) return 0;
int size = (target + sum) / 2;
if(size < 0) size = -size;
int[] dp = new int[size + 1];
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
for (int j = size; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[size];
}
}
回溯(时间复杂度O(2^n) 不推荐):
class Solution {
int count = 0;
public int findTargetSumWays(int[] nums, int target) {
backtrack(nums, target, 0, 0);
return count;
}
public void backtrack(int[] nums, int target, int index, int sum) {
if (index == nums.length) {
if (sum == target) { //如果回溯完一条路index=nums.length且sum=target,则count+1
count++;
}
} else {
backtrack(nums, target, index + 1, sum + nums[index]); //继续回溯(加下一个数)
backtrack(nums, target, index + 1, sum - nums[index]); //继续回溯(减下一个数)
}
}
}
538.给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
节点的左子树仅包含键 小于 节点键的节点。
节点的右子树仅包含键 大于 节点键的节点。
左右子树也必须是二叉搜索树。
543.给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
深度优先搜索:
/**
* 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 maxd=0;
public int diameterOfBinaryTree(TreeNode root) {
depth(root);
return maxd;
}
public int depth(TreeNode node){
if(node == null){ //递归出口
return 0;
}
int Left = depth(node.left); //求左子树深度
int Right = depth(node.right); //求右子树深度
maxd = Math.max(Left+Right,maxd); //将每个节点最大直径(左子树深度+右子树深度)当前最大值比较并取大者
return Math.max(Left,Right)+1;//返回节点深度
}
}
560.给你一个整数数组 nums 和一个整数 k ,请你统计并返回该数组中和为 k 的连续子数组的个数。
前缀和 + 哈希表优化(类似于437):
public class Solution {
public int subarraySum(int[] nums, int k) {
//count为满足条件的连续子数组的个数;pre为第i个数的前缀和
int count = 0, pre = 0;
//key为前缀和,value为相应的个数
HashMap < Integer, Integer > mp = new HashMap < > ();
mp.put(0, 1); //初始化
for (int i = 0; i < nums.length; i++) {
pre += nums[i]; //得到nums[i]的前缀和
if (mp.containsKey(pre - k)) { //看看mp前面是否已经有数的前缀和为pre-k,有的话相应个数加一
count += mp.get(pre - k);
}
mp.put(pre, mp.getOrDefault(pre, 0) + 1); //每次都把键值对放进mp
}
return count;
}
}
581.给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
请你找出符合题意的 最短 子数组,并输出它的长度。
排序比较(略)
一次遍历:
class Solution {
public int findUnsortedSubarray(int[] nums) {
int n = nums.length;
int maxn = Integer.MIN_VALUE, right = -1;
int minn = Integer.MAX_VALUE, left = -1;
for (int i = 0; i < n; i++) {
//从前往后看最后一段升序
if (maxn > nums[i]) { //如果前一项比nums[i]大(为升序),则right=i;
right = i;
} else {
maxn = nums[i]; //到目前为止的最大值
}
//从后往前看最后一段升序
if (minn < nums[n - i - 1]) { //如果“前一项”比nums[n-i-1]小(从后往前看为升序),则left=n-i-1;
left = n - i - 1;
} else {
minn = nums[n - i - 1]; //到目前为止的最小值
}
}
return right == -1 ? 0 : right - left + 1;
}
}
617.给你两棵二叉树: root1 和 root2 。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。
深度优先搜索:
class Solution {
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
//如果两个二叉树的对应节点只有一个为空,则合并后的二叉树的对应节点为其中的非空节点。(包括了两个二叉树的对应节点都为空,则合并后的二叉树的对应节点也为空的情况。)
if (t1 == null) {
return t2;
}
if (t2 == null) {
return t1;
}
//如果两个二叉树的对应节点都不为空,则合并后的二叉树的对应节点的值为两个二叉树的对应节点的值之和,此时需要显性合并两个节点。
TreeNode merged = new TreeNode(t1.val + t2.val);
merged.left = mergeTrees(t1.left, t2.left);
merged.right = mergeTrees(t1.right, t2.right);
return merged;
}
}
广度优先搜索(略)