目录
226.翻转二叉树
这题比较简单,就不贴代码了,主要是要注意不能中序遍历,可以想象一棵最简单的二叉树,首先是遍历完左孩子,然后执行交换左右孩子,再去遍历右孩子,这时遍历的右孩子其实是没遍历前的左孩子,如果左孩子还有孩子那么在根节点翻转之前已经遍历过了,翻转之后再遍历翻转相当于没翻转。就这样吧,写的很明白了。
98.验证二叉搜索树
根据二叉搜索树的性质吧,中序遍历之后一定是递增数组序列。拿到中序结果后一个个比较,这应该是最笨的方法。不贴代码了,主要是写的时候注意判断节点值相等的情况。
也看了题解,本来自己也想到用两个变量来比较而不像都存储在数组里那样占空间,但是自己不懂Long.min和long.max这种。贴个官方递归的代码
class Solution {
public boolean isValidBST(TreeNode root) {
return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
public boolean isValidBST(TreeNode node, long lower, long upper) {
if (node == null) {
return true;
}
if (node.val <= lower || node.val >= upper) {
return false;
}
return isValidBST(node.left, lower, node.val) && isValidBST(node.right, node.val, upper);
}
}
顺便复习一下中序的迭代方法
class Solution {
public boolean isValidBST(TreeNode root) {
Deque<TreeNode> stack = new LinkedList<TreeNode>();
double inorder = -Double.MAX_VALUE;
while (!stack.isEmpty() || root != null) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
// 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
if (root.val <= inorder) {
return false;
}
inorder = root.val;
root = root.right;
}
return true;
}
}
题解还有很多方法和思想,留个坑吧。
437.路径总和 III
这题是medium,自己ac了,但是是笨法子,看题目肯定是要遍历完所有路径的,2点要求:路径只能往下走,路径开头不一定是根节点。首先想到前序遍历,对每个节点的访问时机就是处理时机,
- 嵌套进行递归,外层的递归遍历是遍历每个节点为路径的起始节点,内层的递归遍历是对于固定起始的根节点进行路径遍历
- 还是什么时候进行递归弹栈(路径和减去当前节点路径)的问题,遍历完右节点。这个点第三次遇到了,这个和官方的嵌套递归还不一样。
- 路径和可能会超过int,要定义为long(第127个测试用例)
贴个自己的代码:
双重递归遍历所有情况
class Solution {
private int routeNum = 0;
private int targetSum = 0;
public int pathSum(TreeNode root, int targetSum) {
this.targetSum = targetSum;
preorder(root);
return routeNum;
}
private void preorder(TreeNode root) {
if(root == null)return;
//
long curLenth = 0;
sure_node_get(root,curLenth);
preorder(root.left);
preorder(root.right);
}
private void sure_node_get(TreeNode node, long curLenth) {
if(node == null)return;
curLenth = curLenth + node.val;
if(curLenth == targetSum)routeNum++;
sure_node_get(node.left, curLenth);
sure_node_get(node.right, curLenth);
curLenth = curLenth - node.val;
}
}
官方的,很巧妙:
class Solution {
public int pathSum(TreeNode root, int targetSum) {
if (root == null) {
return 0;
}
int ret = rootSum(root, targetSum);
ret += pathSum(root.left, targetSum);
ret += pathSum(root.right, targetSum);
return ret;
}
public int rootSum(TreeNode root, int targetSum) {
int ret = 0;
if (root == null) {
return 0;
}
int val = root.val;
if (val == targetSum) {
ret++;
}
ret += rootSum(root.left, targetSum - val);
ret += rootSum(root.right, targetSum - val);
return ret;
}
}
前缀和+DFS:
上面那种方法要递归遍历所有的情况有点浪费时间,而且有的路径长度重复计算了。既然要找到一条起始节点可以不是根节点的固定长度路径,可以用作差的方法。
例如一个一维数组,我们要找到连续子数组的和为固定值的子数组(好像有这道题目),定义两个以数组第一个元素开头的动态连续子数组,对每个子数组求和作差。在这道题中也是一样的道理,二叉树从根节点到某个子节点的路径是唯一的。
所以这个看了题解已经众多评论之后我认为有以下几点需要重点理解:
- 前缀和定义:本题是从根节点到遍历到的当前节点的路径上所有元素之和,包括根节点。也就是说每个节点都有自己的前缀和。
- 前缀和的存储:采用hashmap,key值为不同节点的前缀和的值,value为该前缀和的值出现次数;更具体一点,我认为是从根节点到遍历到的当前节点的位置这条唯一路径上某个具体的前缀和的值出现的次数。
- 整体流程和所谓的“状态恢复”:采用dfs遍历树,首先初始化map集合。从根节点开始,每遍历到一个节点:
- 首先计算当前节点的前缀和,
- 然后看看集合中是否又满足条件的前缀和(集合中是否存在前缀和的值为当前节点前缀和减去所求路径长度)
- 将当前缀和的信息更新or添加到map集合
- 递归遍历左右子树
- 关于最后的状态恢复,其实就是上面说的提到很多次的弹栈恢复,因为只遍历了整个数一次,而且要求所求路径必须向下,我们计算时也以根节点到某个节点的一条也只有一条路径上去计算。因此这个前缀和其实和你自己想的那个解法的路径是一个东西。都需要弹栈,该节点左右子树遍历完后,也标志着该节点的遍历完成,所以要在集合中减去一次该节点的前缀和。
写了好多,有点烦了,贴代码自己理解吧:
这篇文章在ipad题解评论看到了但是在电脑上没找到。
class Solution {
public int pathSum(TreeNode root, int targetSum) {
Map<Long, Integer> prefix = new HashMap<Long, Integer>();
prefix.put(0L, 1);
return dfs(root, prefix, 0, targetSum);
}
public int dfs(TreeNode root, Map<Long, Integer> prefix, long curr, int targetSum) {
if (root == null) {
return 0;
}
int ret = 0;
curr += root.val;
ret = prefix.getOrDefault(curr - targetSum, 0);
prefix.put(curr, prefix.getOrDefault(curr, 0) + 1);
ret += dfs(root.left, prefix, curr, targetSum);
ret += dfs(root.right, prefix, curr, targetSum);
prefix.put(curr, prefix.getOrDefault(curr, 0) - 1);
return ret;
}
}
Java高级之HashMap中的put()方法和putIfAbsent()方法_hashmap putifabsent-CSDN博客