五月刷题18——树
今日刷题内容: 树
前言
- 一个算法废材的刷题之路开更了, 更新每天刷题的题解内容
- 注重个人理解,看难度更新题目数量
- 题目来源于力扣
- 新开专栏,争取每日都能做出至少一题=v=
- 语言java、python、c\c++
一、今日题目
二、解题思路
1. 2236. 判断根结点是否等于子结点之和★☆☆☆☆
一题可用用来找回自信,开始今日刷题之旅
class Solution {
public boolean checkTree(TreeNode root) {
return root.val == root.left.val + root.right.val;
}
}
2. 面试题 04.10. 检查子树★★☆☆☆
这题学到了两种思路
思路一:是用特定字符串来表示树
- 前序遍历两棵树,用两个字符串各自表示树的遍历的值
- 再判断第二课树的字符串是否为第一个树的子串即可
代码如下:
class Solution {
public void dfs(TreeNode root, StringBuffer s){
if (root == null){
s.append('x'); // 用x来填充空节点
return;
}
s.append(root.val) // 用当前值+'0'来填充当前节点
.append(0);
dfs(root.left, s); // 遍历左右节点
dfs(root.right, s);
}
public boolean checkSubTree(TreeNode t1, TreeNode t2) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
dfs(t1, s1);
dfs(t2, s2);
// 返回s2是否为s1的字串结果
return s1.toString().contains(s2.toString());
}
}
思路二:思路是用hash值来表示一棵树
- 通过后序遍历,计算两棵树每个节点的
hash
值(伪)这是一种思想- 计算完成后,第二棵树的根节点的值即表示该树的
hash
值- 从第二棵树递归查找是否存在相同值的节点,相同则说明有子树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
// 计算整棵树的hash值
public void calcHash(TreeNode t){
if (t == null) return;
calcHash(t.left);
calcHash(t.right);
// 给各节点赋值
int l = t.left == null ? 235889 : t.left.val;
int r = t.right == null ? 879231 : t.right.val;
// 将当前节点的值通过下式计算
t.val = (int)(t.val*16113466 *l + r) % 1231557;
}
public boolean findTree(TreeNode root, int val){
// 递归查找第二棵树的hash值是否在第一棵树的某个节点上
if(root == null) return false;
return (root.val == val) || findTree(root.right, val) || findTree(root.left, val);
}
public boolean checkSubTree(TreeNode t1, TreeNode t2) {
calcHash(t1); // 计算两棵树的各节点hash值
calcHash(t2);
if(t2 == null) return true;
// 返回查找的结果
return findTree(t1, t2.val);
}
}
3. 面试题 04.06. 后继者★★☆☆☆
这题也有两种思路,一种刚学会
先说下我的思路:
思路一:笨法子:用列表来辅助判断
- 通过中序遍历树中的每个节点
- 在列表中找该节点的下一个值
代码如下:
class Solution {
public void midOrder(TreeNode root, ArrayList alist){
if (root == null) return;
midOrder(root.left, alist);
alist.add(root);
midOrder(root.right, alist);
}
public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
ArrayList<TreeNode> alist = new ArrayList<>();
midOrder(root, alist);
if (alist.indexOf(p) != alist.size() - 1){
return alist.get(alist.indexOf(p) + 1);
}
return null;
}
}
思路二:直接在中序遍历中判断
- 用一个辅助标识
flag
来标记是否找到了该节点- 因为要找的是下一个节点,所以在递归的途中,要把
flag
为真时的ret
赋值语句放在flag赋值之前,才会在下次递归(下个元素)时调用- 返回
ret
即可
class Solution {
private boolean flag = false; // 用来标识,是否找到p节点
private TreeNode ret = null; // 结果节点
public void dfs(TreeNode root, TreeNode p){
if(root == null) return;
dfs(root.left, p);
// 下一个找到了,并且ret是空,将ret设置为当前节点
if(flag && ret == null){
ret = root;
}
if(root == p){
flag = true; // 如果当前节点是要找的节点,将flag置为true
}
dfs(root.right, p);
}
public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
dfs(root, p);
return ret;
}
}
4.1110. 删点成林★★★☆☆
思路:用hash+后序遍历
- 用
hash
来记录需要删除的点- 用
parent
来标识节点的父节点,用isleft
来标识当前节点是否为父节点的左子树- 递归中将要删除节点的左右子树置为空
- 最后要判断根节点是否为要删除的节点,决定是否将根节点加入列表
class Solution {
List<TreeNode> ret = new ArrayList<>();
int[] hash = new int[1010]; // 用来标记要删除节点的值
public void segTree(TreeNode parent, TreeNode root, boolean isleft){
if (root == null) return;
segTree(root, root.left, true); // 后序遍历来删除节点
segTree(root, root.right, false);
if (hash[ root.val ] > 0){ // 如果当前节点是已被标记的点
if (parent != null){ // 如果父节点不为空
if (isleft){
parent.left = null; // 删除父节点对当前节点的指向
}else{
parent.right = null;
}
}
if (root.left != null) ret.add(root.left); // 将左右子树加入列表
if (root.right != null) ret.add(root.right);
}
}
public List<TreeNode> delNodes(TreeNode root, int[] to_delete) {
if(root == null) return new ArrayList<>(); // 如果头节点为空,返回空列表
int i;
int length = to_delete.length;
for(i = 0; i < length; i++){
hash[ to_delete[i] ] = 1; // 标记需要删除的点
}
segTree(null, root, false);
if(hash[ root.val ] == 0){ // 如果头节点没被标记,将头节点加入列表
ret.add(root);
}
return ret;
}
}