Leetcode_入门_树
递归
1、二叉树的最大深度(104、Easy)
1)题目要求
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/
9 20
/
15 7
返回它的最大深度 3 。
2)我的解法
c++
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==NULL)return 0;
return max(maxDepth(root->left)+1,maxDepth(root->right)+1);
}
};
3)其他解法
我们也可以用「广度优先搜索」的方法来解决这道题目,但我们需要对其进行一些修改,此时我们广度优先搜索的队列里存放的是「当前层的所有节点」。每次拓展下一层的时候,不同于广度优先搜索的每次只从队列里拿出一个节点,我们需要将队列里的所有节点都拿出来进行拓展,这样能保证每次拓展完的时候队列里存放的是当前层的所有节点,即我们是一层一层地进行拓展,最后我们用一个变量 \textit{ans}ans 来维护拓展的次数,该二叉树的最大深度即为 \textit{ans}ans。
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr) return 0;
queue<TreeNode*> Q;
Q.push(root);
int ans = 0;
while (!Q.empty()) {
int sz = Q.size();
while (sz > 0) {
TreeNode* node = Q.front();Q.pop();
if (node->left) Q.push(node->left);
if (node->right) Q.push(node->right);
sz -= 1;
}
ans += 1;
}
return ans;
}
};
作者:LeetCode-Solution
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==NULL)return 0;
return max(maxDepth(root->left)+1,maxDepth(root->right)+1);
}
};
5)学到的东西
广度优先遍历
递归思想
2、平衡二叉树(110、Easy)
1)题目要求
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
3
/
9 20
/
15 7
返回 true 。
示例 2:
给定二叉树 [1,2,2,3,3,null,null,4,4]
1
/ \
2 2
/ \
3 3
/
4 4
返回 false 。
2)我的解法
c++
class Solution {
public:
int height(TreeNode*root)
{
if(root==NULL)return 0;
return (max(height(root->left),height(root->right))+1);
}
bool isBalanced(TreeNode* root) {
if(root==NULL)return true;
else return (abs(height(root->left)-height(root->right))<=1&&isBalanced(root->left)&&isBalanced(root->right))?true:false;
}
};
3)其他解法
class Solution {
private:
// Return whether or not the tree at root is balanced while also storing
// the tree's height in a reference variable.
bool isBalancedTreeHelper(TreeNode* root, int& height) {
// An empty tree is balanced and has height = -1
if (root == NULL) {
height = -1;
return true;
}
// Check subtrees to see if they are balanced. If they are, check if the
// current node is also balanced using the heights obtained from the
// recursive calls.
int left, right;
if (isBalancedTreeHelper(root->left, left) &&
isBalancedTreeHelper(root->right, right) &&
abs(left - right) < 2) {
// Store the current tree's height
height = max(left, right) + 1;
return true;
}
return false;
}
public:
bool isBalanced(TreeNode* root) {
int height;
return isBalancedTreeHelper(root, height);
}
};
作者:LeetCode
链接: link
来源:力扣(LeetCode)
4)自己的优化代码
c++
class Solution {
public:
bool isBalancedHelp(TreeNode*root,int&height)//一定要加上&
{
if(root==NULL){height=0;return true;}
else
{
int left,right;
if(isBalancedHelp(root->left,left)&&isBalancedHelp(root->right,right)&&abs(left-right)<=1)
{
height=max(left,right)+1;
return true;
}
else return false;
}
}
bool isBalanced(TreeNode* root) {
int height;
return isBalancedHelp(root,height);
}
};
5)学到的东西
递归思想,
不同的递归思路
当需要通过改变形参改变实参的值时,一定要用引用(即一定要加上&)
3、二叉树的直径(543、Easy)
1)题目要求
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
示例 :
给定二叉树
1
/ \
2 3
/ \
4 5
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
注意:两结点之间的路径长度是以它们之间边的数目表示。
2)我的解法
c++
class Solution {
public:
int height(TreeNode*root)
{
if(root==NULL)return 0;
return (max(height(root->left),height(root->right))+1);
}
int getmax(int a,int b,int c)
{
return max(c,max(a,b));
}
int diameterOfBinaryTree(TreeNode* root) {
if(root==NULL)return 0;
return getmax(diameterOfBinaryTree(root->left),diameterOfBinaryTree(root->right),
height(root->left)+height(root->right));
}
};
3)其他解法
方法:深度优先搜索
首先我们知道一条路径的长度为该路径经过的节点数减一,所以求直径(即求路径长度的最大值)等效于求路径经过节点数的最大值减一。
而任意一条路径均可以被看作由某个节点为起点,从其左儿子和右儿子向下遍历的路径拼接得到。
class Solution {
int ans;
int depth(TreeNode* rt){
if (rt == NULL) return 0; // 访问到空节点了,返回0
int L = depth(rt->left); // 左儿子为根的子树的深度
int R = depth(rt->right); // 右儿子为根的子树的深度
ans = max(ans, L + R + 1); // 计算d_node即L+R+1 并更新ans
return max(L, R) + 1; // 返回该节点为根的子树的深度
}
public:
int diameterOfBinaryTree(TreeNode* root) {
ans = 1;
depth(root);
return ans - 1;
}
};
作者:LeetCode-Solution
链接: link
来源:力扣(LeetCode)
4)自己的优化代码
c++
class Solution {
public:
int maxd=0;
int geth(TreeNode*root)//求结点高度
{
if(root==NULL)return 0;
int l=geth(root->left);
int r=geth(root->right);
maxd=max((l+r),maxd);//经过的边数
return max(l,r)+1;
}
int diameterOfBinaryTree(TreeNode* root) {
geth(root);
return maxd;
}
};
5)学到的东西
递归思想
调用递归函数,但不返回递归函数值,而是在调用递归的过程中去更新一个全局变量
4、翻转二叉树(226、Easy)
1)题目要求
翻转一棵二叉树。
示例:
输入:
4
/
2 7
/ \ /
1 3 6 9
输出:
4
/
7 2
/ \ /
9 6 3 1
备注:
这个问题是受到 Max Howell 的 原问题 启发的 :
谷歌:我们90%的工程师使用您编写的软件(Homebrew),但是您却无法在面试时在白板上写出翻转二叉树这道题,这太糟糕了。
2)我的解法
c++
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root==NULL)return NULL;
TreeNode* temp=invertTree(root->left);
root->left=invertTree(root->right);
root->right=temp;
return root;
}
};
3)其他解法
(1)递归
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
TreeNode right = invertTree(root.right);
TreeNode left = invertTree(root.left);
root.left = right;
root.right = left;
return root;
}
(2)迭代
我们也可以用迭代方法来解决这个问题,这种做法和深度优先搜索(Breadth-fist Search, BFS)很接近。
算法
这个方法的思路就是,我们需要交换树中所有节点的左孩子和右孩子。因此可以创一个队列来存储所有左孩子和右孩子还没有被交换过的节点。开始的时候,只有根节点在这个队列里面。只要这个队列不空,就一直从队列中出队节点,然后互换这个节点的左右孩子节点,接着再把孩子节点入队到队列,对于其中的空节点不需要加入队列。最终队列一定会空,这时候所有节点的孩子节点都被互换过了,直接返回最初的根节点就可以了。
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.add(root);
while (!queue.isEmpty()) {
TreeNode current = queue.poll();
TreeNode temp = current.left;
current.left = current.right;
current.right = temp;
if (current.left != null) queue.add(current.left);
if (current.right != null) queue.add(current.right);
}
return root;
}
作者:LeetCode
链接: link
来源:力扣(LeetCode)
4)自己的优化代码
c++
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root==NULL)return NULL;
TreeNode* L=invertTree(root->left);
root->left=invertTree(root->right);
root->right=L;
return root;
}
};
5)学到的东西
递归思想
迭代思想
5、合并二叉树(617、Easy)
1)题目要求
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
示例 1:
输入:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/
4 5
/ \ \
5 4 7
2)我的解法
c++
class Solution {
public:
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
TreeNode*result=new TreeNode(-1);
if(t1!=NULL)
{
if(t2!=NULL)
{
result->val=t1->val+t2->val;
result->left=mergeTrees(t1->left,t2->left);
result->right=mergeTrees(t1->right,t2->right);
}
else result=t1;
}
else result=t2;
return result;
}
};
3)其他解法
(1)递归
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Solution {
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
if (t1 == null)
return t2;
if (t2 == null)
return t1;
t1.val += t2.val;
t1.left = mergeTrees(t1.left, t2.left);
t1.right = mergeTrees(t1.right, t2.right);
return t1;
}
}
(2)迭代
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Solution {
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
if (t1 == null)
return t2;
Stack < TreeNode[] > stack = new Stack < > ();
stack.push(new TreeNode[] {t1, t2});
while (!stack.isEmpty()) {
TreeNode[] t = stack.pop();
if (t[0] == null || t[1] == null) {
continue;
}
t[0].val += t[1].val;
if (t[0].left == null) {
t[0].left = t[1].left;
} else {
stack.push(new TreeNode[] {t[0].left, t[1].left});
}
if (t[0].right == null) {
t[0].right = t[1].right;
} else {
stack.push(new TreeNode[] {t[0].right, t[1].right});
}
}
return t1;
}
}
作者:LeetCode
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
class Solution {
public:
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
if(t1==NULL)return t2;
if(t2==NULL)return t1;
t1->val+=t2->val;
t1->left=mergeTrees(t1->left,t2->left);
t1->right=mergeTrees(t1->right,t2->right);
return t1;
}
};
5)学到的东西
递归思想
迭代思想
当一个指针为空,比如TreeNode* t1为空时,则不能使用t1->left之类的,
所以使用时一定要先判断t1是否为空
6、路径总和(112、Easy)
1)题目要求
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。
示例:
给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1
返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2
2)我的解法
c++
class Solution {
public:
int addsum(TreeNode* root, int sum)
{
if(root==NULL)return -100000000;
int l=0,r=0;
if(root->left==NULL&&root->right==NULL)l=root->val;
else l=addsum(root->left,sum-root->val)+root->val;
if(root->left==NULL&&root->right==NULL)r=root->val;
else r=addsum(root->right,sum-root->val)+root->val;
if(l==sum||r==sum)return sum;
else return l;
}
bool hasPathSum(TreeNode* root, int sum) {
if(root==NULL)return false;
if((root->left!=NULL&&root->right==NULL)||(root->left==NULL&&root->right!=NULL))
{
if(root->val==sum)return false;
}
return (addsum(root,sum)==sum)?true:false;
}
};
3)其他解法
(1)递归
class Solution {
public:
bool hasPathSum(TreeNode *root, int sum) {
if (root == nullptr) {
return false;
}
if (root->left == nullptr && root->right == nullptr) {
return sum == root->val;
}
return hasPathSum(root->left, sum - root->val) ||
hasPathSum(root->right, sum - root->val);
}
};
(2)广度优先搜索
首先我们可以想到使用广度优先搜索的方式,记录从根节点到当前节点的路径和,以防止重复计算。
这样我们使用两个队列,分别存储将要遍历的节点,以及根节点到这些节点的路径和即可。
class Solution {
public:
bool hasPathSum(TreeNode *root, int sum) {
if (root == nullptr) {
return false;
}
queue<TreeNode *> que_node;
queue<int> que_val;
que_node.push(root);
que_val.push(root->val);
while (!que_node.empty()) {
TreeNode *now = que_node.front();
int temp = que_val.front();
que_node.pop();
que_val.pop();
if (now->left == nullptr && now->right == nullptr) {
if (temp == sum) return true;
continue;
}
if (now->left != nullptr) {
que_node.push(now->left);
que_val.push(now->left->val + temp);
}
if (now->right != nullptr) {
que_node.push(now->right);
que_val.push(now->right->val + temp);
}
}
return false;
}
};
作者:LeetCode-Solution
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
class Solution {
public:
bool hasPathSum(TreeNode* root, int sum) {
if(root==NULL)return false;
if(root->left==NULL&&root->right==NULL)return root->val==sum;
return hasPathSum(root->left,sum-root->val)||hasPathSum(root->right,sum-root->val);
}
};
5)学到的东西
bool类型函数也可以递归,仅在叶子结点进行真正的判断
递归思想、迭代思想
广度优先遍历(使用队列)
7、另一个树的子树(572、Easy)
1)题目要求
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
示例 1:
给定的树 s:
3
/ \
4 5
/
1 2
给定的树 t:
4
/
1 2
返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。
示例 2:
给定的树 s:
3
/ \
4 5
/
1 2
/
0
给定的树 t:
4
/
1 2
返回 false。
2)我的解法
c++
class Solution {
public:
bool isequal(TreeNode* s, TreeNode* t)
{
if(s==NULL)
{
if(t==NULL)return true;
else return false;
}
else if(t==NULL)return false;
return(s->val==t->val&&isequal(s->left,t->left)&&isequal(s->right,t->right));
}
bool isSubtree(TreeNode* s, TreeNode* t) {
if(s==NULL)return false;
if(s->val==t->val)return isequal(s,t)||isSubtree(s->left,t)||isSubtree(s->right,t);//如果值相等做进一步判断
return isSubtree(s->left,t)||isSubtree(s->right,t);
}
};
3)其他解法
方法一:DFS 暴力匹配
思路和算法
这是一种最朴素的方法 —— DFS 枚举 ss 中的每一个节点,判断这个点的子树是否和 tt 相等。如何判断一个节点的子树是否和 tt 相等呢,我们又需要做一次 DFS 来检查,即让两个指针一开始先指向该节点和 tt 的根,然后「同步移动」两根指针来「同步遍历」这两棵树,判断对应位置是否相等。
class Solution {
public:
bool check(TreeNode *o, TreeNode *t) {
if (!o && !t) return true;
if ((o && !t) || (!o && t) || (o->val != t->val)) return false;
return check(o->left, t->left) && check(o->right, t->right);
}
bool dfs(TreeNode *o, TreeNode *t) {
if (!o) return false;
return check(o, t) || dfs(o->left, t) || dfs(o->right, t);
}
bool isSubtree(TreeNode *s, TreeNode *t) {
return dfs(s, t);
}
};
方法二:DFS 序列上做串匹配
思路和算法
这个方法需要我们先了解一个「小套路」:一棵子树上的点在 DFS 序列(即先序遍历)中是连续的。了解了这个「小套路」之后,我们可以确定解决这个问题的方向就是:把 ss 和 tt 先转换成 DFS 序,然后看 tt 的 DFS 序是否是 ss 的 DFS 序的「子串」。
这样做正确吗? 假设 ss 由两个点组成,11 是根,22 是 11 的左孩子;tt 也由两个点组成,11 是根,22 是 11 的右孩子。这样一来 ss 和 tt 的 DFS 序相同,可是 tt 并不是 ss 的某一棵子树。由此可见「ss 的 DFS 序包含 tt 的 DFS 序」是「tt 是 ss 子树」的 必要不充分条件,所以单纯这样做是不正确的。
为了解决这个问题,我们可以引入两个空值 lNull 和 rNull,当一个节点的左孩子或者右孩子为空的时候,就插入这两个空值,这样 DFS 序列就唯一对应一棵树。处理完之后,就可以通过判断 「ss 的 DFS 序包含 tt 的 DFS 序」来判断答案。
在判断「ss 的 DFS 序包含 tt 的 DFS 序」的时候,可以暴力匹配,也可以使用 KMP 或者 Rabin-Karp 算法,在使用 Rabin-Karp 算法的时候,要注意串中可能有负值。
这里给出用 KMP 判断的代码实现。
class Solution {
public:
vector <int> sOrder, tOrder;
int maxElement, lNull, rNull;
void getMaxElement(TreeNode *o) {
if (!o) return;
maxElement = max(maxElement, o->val);
getMaxElement(o->left);
getMaxElement(o->right);
}
void getDfsOrder(TreeNode *o, vector <int> &tar) {
if (!o) return;
tar.push_back(o->val);
if (o->left) getDfsOrder(o->left, tar);
else tar.push_back(lNull);
if (o->right) getDfsOrder(o->right, tar);
else tar.push_back(rNull);
}
bool kmp() {
int sLen = sOrder.size(), tLen = tOrder.size();
vector <int> fail(tOrder.size(), -1);
for (int i = 1, j = -1; i < tLen; ++i) {
while (j != -1 && tOrder[i] != tOrder[j + 1]) j = fail[j];
if (tOrder[i] == tOrder[j + 1]) ++j;
fail[i] = j;
}
for (int i = 0, j = -1; i < sLen; ++i) {
while (j != -1 && sOrder[i] != tOrder[j + 1]) j = fail[j];
if (sOrder[i] == tOrder[j + 1]) ++j;
if (j == tLen - 1) return true;
}
return false;
}
bool isSubtree(TreeNode* s, TreeNode* t) {
maxElement = INT_MIN;
getMaxElement(s);
getMaxElement(t);
lNull = maxElement + 1;
rNull = maxElement + 2;
getDfsOrder(s, sOrder);
getDfsOrder(t, tOrder);
return kmp();
}
};
作者:LeetCode-Solution
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
(1)运用bf
class Solution {
public:
int lnull=-10000,rnull=-10001;
void getdfs(TreeNode* root,vector<int> &result)
{
if(!root)return ;
else result.push_back(root->val);
if(root->left)getdfs(root->left,result);
else result.push_back(lnull);
if(root->right)getdfs(root->right,result);
else result.push_back(rnull);
}
bool bf(vector<int> s,vector<int> t)
{
for(int i=0;i<s.size();i++)
{
for(int j=0;j<t.size();j++)
{
if(s[i+j]!=t[j])break;//用i+j能避免i值不对的问题
if(j==t.size()-1)return true;
}
}
return false;
}
bool isSubtree(TreeNode* s, TreeNode* t) {
vector<int> s1,t1;
getdfs(s,s1);
getdfs(t,t1);
return bf(s1,t1);
}
};
(2)运用kmp
虽然我在学校学过KMP算法,但我当时掌握地并不好,而且这么长时间过去了,我已经忘了。。所以我先去找了个视频看,并结合之前学校地PPT,终于理解了KMP算法
KMP算法实例详解(易懂)
class Solution {
public:
int lnull=-10000,rnull=-10001;
void getdfs(TreeNode* root,vector<int> &result)
{
if(!root)return ;
else result.push_back(root->val);
if(root->left)getdfs(root->left,result);
else result.push_back(lnull);
if(root->right)getdfs(root->right,result);
else result.push_back(rnull);
}
bool kmp(vector<int> s,vector<int> t)
{
vector<int> next(t.size(),-1);//相同前后缀长为k+1
for (int i = 1, k = -1; i < t.size(); ++i) {
while (k != -1 && t[i-1] != t[k]) k = next[k];
next[i] = ++k;
}
int i = 0, j =0;
while(i < s.size())
{
if (s[i] == t[j]) j++;//当匹配成功,继续匹配
else
{
j=next[j];//匹配不成功,移动模式串的指针
if(j==-1)j++;//如果j==-1,模式串从头开始,目标串指针也往后移
else continue;//如果j!=-1,仅移动模式串指针,不移动目标串指针
}
i++;
if (j == t.size()) return true;
}
return false;
}
bool isSubtree(TreeNode* s, TreeNode* t) {
vector<int> s1,t1;
getdfs(s,s1);
getdfs(t,t1);
return kmp(s1,t1);
}
};
5)学到的东西
深度优先遍历,递归思想
BF匹配算法,KMP算法
8、路径总和 III(437、Easy)
1)题目要求
给定一个二叉树,它的每个结点都存放着一个整数值。
找出路径和等于给定数值的路径总数。
路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。
示例:
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8
10
/ \
5 -3
/ \
3 2 11
/ \
3 -2 1
返回 3。和等于 8 的路径有:
- 5 -> 3
- 5 -> 2 -> 1
- -3 -> 11
2)我的解法
c++
class Solution {
public:
int pasum=0;
void panduan(TreeNode* root, int sum) {
if(!root)return;
if(root->val==sum)pasum++;
panduan(root->left,sum-root->val);
panduan(root->right,sum-root->val);
}
void panduanall(TreeNode* root, int sum)
{
if(!root)return ;
panduan(root,sum);
panduanall(root->left,sum);
panduanall(root->right,sum);
}
int pathSum(TreeNode* root, int sum) {
panduanall(root,sum);
return pasum;
}
};
3)其他解法
1、双重递归
①先序递归遍历每个节点
②以每个节点作为起始节点DFS寻找满足条件的路径
class Solution {
public:
int ans = 0;
void dfs(TreeNode* root, int sum)
{
if(root == nullptr)
return;
sum -= root->val;
if(sum == 0)
ans++;
dfs(root->left, sum);
dfs(root->right, sum);
}
int pathSum(TreeNode* root, int sum) {
if(root == nullptr)
return ans;
dfs(root, sum);
pathSum(root->left, sum);
pathSum(root->right, sum);
return ans;
}
};
作者:Sunny_SMILE
链接:link
来源:力扣(LeetCode)
2、前缀和
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
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);
}
/**
* 前缀和的递归回溯思路
* 从当前节点反推到根节点(反推比较好理解,正向其实也只有一条),有且仅有一条路径,因为这是一棵树
* 如果此前有和为currSum-target,而当前的和又为currSum,两者的差就肯定为target了
* 所以前缀和对于当前路径来说是唯一的,当前记录的前缀和,在回溯结束,回到本层时去除,保证其不影响其他分支的结果
* @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的路径
// 当前节点->root节点反推,有且仅有一条路径,如果此前有和为currSum-target,而当前的和又为currSum,两者的差就肯定为target了
// currSum-target相当于找路径的起点,起点的sum+target=currSum,当前点到起点的距离就是target
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;
}
}
作者:burning-summer
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
class Solution {
public:
int pasum=0;
void panduan(TreeNode* root, int sum) {
if(!root)return;
if(root->val==sum)pasum++;
panduan(root->left,sum-root->val);
panduan(root->right,sum-root->val);
}
int pathSum(TreeNode* root, int sum) {
if(!root)return 0;
panduan(root,sum);
pathSum(root->left,sum);
pathSum(root->right,sum);
return pasum;
}
};
5)学到的东西
双重递归(判断每个结点作为起始结点时能不能成功,这个判断函数也是个递归,所以形成双重递归)
多思考,在脑海中先想一下怎样才能解决问题,再根据这个想法想代码。拿这道题来说,应该先能想到“判断每个结点作为起始结点时能不能成功”这个思路,然后再去想怎么遍历每个结点,怎么对每一个结点做判断,最后用代码实现。
前缀和思想
9、二叉树的最小深度(111、Easy)
1)题目要求
2)我的解法
c++
class Solution {
public:
int getmin(TreeNode* root)
{
int left=9999,right=9999;
if(!root)return 9999;
if(!root->left&&!root->right)return 1;
left=getmin(root->left)+1;
right=getmin(root->right)+1;
return min(left,right);
}
int minDepth(TreeNode* root) {
if(!root)return 0;
return getmin(root);
}
};
3)其他解法
(1)递归
class Solution {
public:
int minDepth(TreeNode* root) {
if (root == nullptr) return 0;
int left = minDepth(root->left);
int right = minDepth(root->right);
if (root->left == nullptr || root->right == nullptr){
return left == 0 ? right+1 : left +1;
} else{
return min(left, right) + 1;
}
}
};
作者:xu-zhou-geng
链接:link
来源:力扣(LeetCode)
(2)深度优先搜索迭代
class Solution {
public int minDepth(TreeNode root) {
LinkedList<Pair<TreeNode, Integer>> stack = new LinkedList<>();
if (root == null) {
return 0;
}
else {
stack.add(new Pair(root, 1));
}
int min_depth = Integer.MAX_VALUE;
while (!stack.isEmpty()) {
Pair<TreeNode, Integer> current = stack.pollLast();
root = current.getKey();
int current_depth = current.getValue();
if ((root.left == null) && (root.right == null)) {
min_depth = Math.min(min_depth, current_depth);
}
if (root.left != null) {
stack.add(new Pair(root.left, current_depth + 1));
}
if (root.right != null) {
stack.add(new Pair(root.right, current_depth + 1));
}
}
return min_depth;
}
}
(3)广度优先搜索迭代
深度优先搜索方法的缺陷是所有节点都必须访问到,以保证能够找到最小深度。因此复杂度是 O(N)O(N) 。
一个优化的方法是利用广度优先搜索,我们按照树的层去迭代,第一个访问到的叶子就是最小深度的节点,这样就不用遍历所有的节点了。
class Solution {
public int minDepth(TreeNode root) {
LinkedList<Pair<TreeNode, Integer>> stack = new LinkedList<>();
if (root == null) {
return 0;
}
else {
stack.add(new Pair(root, 1));
}
int current_depth = 0;
while (!stack.isEmpty()) {
Pair<TreeNode, Integer> current = stack.poll();
root = current.getKey();
current_depth = current.getValue();
if ((root.left == null) && (root.right == null)) {
break;
}
if (root.left != null) {
stack.add(new Pair(root.left, current_depth + 1));
}
if (root.right != null) {
stack.add(new Pair(root.right, current_depth + 1));
}
}
return current_depth;
}
}
作者:LeetCode
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
class Solution {
public:
int minDepth(TreeNode* root) {
int left=9999,right=9999;
if(!root)return 0;
if(!root->left&&!root->right)return 1;
left=minDepth(root->left)+1;
right=minDepth(root->right)+1;
if(left==1)left=9999;
if(right==1)right=9999;
return min(left,right);
}
};
5)学到的东西
10、对称二叉树(101、Easy)
1)题目要求
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/
2 2
/ \ /
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/
2 2
\
3 3
进阶:
你可以运用递归和迭代两种方法解决这个问题吗?
2)我的解法
c++
深度优先、递归
class Solution {
public:
bool isduichen(TreeNode*t1,TreeNode*t2)
{
if(!t1)
{
if(!t2)return true;
else return false;
}
else
{
if(!t2)return false;
}
return (t1->val==t2->val&&isduichen(t1->left,t2->right)&&isduichen(t1->right,t2->left));
}
bool isSymmetric(TreeNode* root) {
if(!root)return true;
return isduichen(root->left,root->right);
}
};
3)其他解法
(1)递归
class Solution {
public:
bool check(TreeNode *p, TreeNode *q) {
if (!p && !q) return true;
if (!p || !q) return false;
return p->val == q->val && check(p->left, q->right) && check(p->right, q->left);
}
bool isSymmetric(TreeNode* root) {
return check(root, root);
}
};
(2)迭代
「方法一」中我们用递归的方法实现了对称性的判断,那么如何用迭代的方法实现呢?首先我们引入一个队列,这是把递归程序改写成迭代程序的常用方法。初始化时我们把根节点入队两次。每次提取两个结点并比较它们的值(队列中每两个连续的结点应该是相等的,而且它们的子树互为镜像),然后将两个结点的左右子结点按相反的顺序插入队列中。当队列为空时,或者我们检测到树不对称(即从队列中取出两个不相等的连续结点)时,该算法结束。
class Solution {
public:
bool check(TreeNode *u, TreeNode *v) {
queue <TreeNode*> q;
q.push(u); q.push(v);
while (!q.empty()) {
u = q.front(); q.pop();
v = q.front(); q.pop();
if (!u && !v) continue;
if ((!u || !v) || (u->val != v->val)) return false;
q.push(u->left);
q.push(v->right);
q.push(u->right);
q.push(v->left);
}
return true;
}
bool isSymmetric(TreeNode* root) {
return check(root, root);
}
};
作者:LeetCode-Solution
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
1、递归
class Solution {
public:
bool isduichen(TreeNode*t1,TreeNode*t2)
{
if(!t1&&!t2)return true;
if(!t1||!t2)return false;
return (t1->val==t2->val&&isduichen(t1->left,t2->right)&&isduichen(t1->right,t2->left));
}
bool isSymmetric(TreeNode* root) {
return isduichen(root,root);
}
};
2、迭代
class Solution {
public:
bool isduichen(TreeNode*t1,TreeNode*t2)
{
queue<TreeNode*> q;
q.push(t1);q.push(t2);
while(!q.empty())
{
TreeNode*u=q.front();q.pop();
TreeNode*v=q.front();q.pop();
if(!u&&!v)continue;
if(!u||!v||u->val!=v->val)return false;
q.push(u->left);
q.push(v->right);
q.push(u->right);
q.push(v->left);
}
return true;
}
bool isSymmetric(TreeNode* root) {
return isduichen(root,root);
}
};
5)学到的东西
递归思想
迭代思想(不管深度优先还是广度优先都使用队列:queue)
深度优先遍历
11、左叶子之和(404、Easy)
1)题目要求
(这个题号是一个很有意义的数字呢)
计算给定二叉树的所有左叶子之和。
示例:
3
/
9 20
/
15 7
在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
2)我的解法
c++
class Solution {
public:
int sum(TreeNode*root)
{
if(!root)return 0;
if(!root->left&&!root->right)return root->val;
int l=sum(root->left);
int r=0;
if(root->right&&!root->right->left&&!root->right->right)r=0;
else r=sum(root->right);
return l+r;
}
int sumOfLeftLeaves(TreeNode* root) {
if(!root)return 0;
if(!root->left&&!root->right)return 0;
return sum(root);
}
};
3)其他解法
1、递归
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if(root == NULL) return 0;
if(root->left!=NULL&&root->left->right==NULL&&root->left->left==NULL) return sumOfLeftLeaves(root->right)+root->left->val;
return sumOfLeftLeaves(root->left)+sumOfLeftLeaves(root->right);
}
};
作者:angel_monica
链接: link
来源:力扣(LeetCode)
2、迭代
public int sumOfLeftLeaves(TreeNode root) {
if (root == null) {
return 0;
}
LinkedList<Pair<TreeNode, Boolean>> stack = new LinkedList<>();
stack.push(new Pair<>(root, false));
int sum = 0;
Boolean flag;//标识是否为左节点
while (!stack.isEmpty()) {
Pair<TreeNode, Boolean> pair = stack.pop();
root = pair.getKey();
flag = pair.getValue();
if (flag && root.left == null && root.right == null) {
sum += root.val;
}
if (root.left != null) {
stack.push(new Pair<>(root.left, true));
}
if (root.right != null) {
stack.push(new Pair<>(root.right, false));
}
}
return sum;
}
作者:lastwhispers
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if(!root)return 0;
if(root->left &&!root->left->left&&!root->left->right)return root->left->val+sumOfLeftLeaves(root->right);
return sumOfLeftLeaves(root->left)+sumOfLeftLeaves(root->right);
}
};
5)学到的东西
递归思想
在判断时可以只判断满足条件的情况,其他让他递归去就好了
12、最长同值路径(687、Easy)
1)题目要求
给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。
注意:两个节点之间的路径长度由它们之间的边数表示。
示例 1:
输入:
5
/ \
4 5
/ \ \
1 1 5
输出:
2
示例 2:
输入:
1
/ \
4 5
/ \ \
4 4 5
输出:
2
注意: 给定的二叉树不超过10000个结点。 树的高度不超过1000。
2)我的解法
c++
class Solution {
public:
int findnode(TreeNode*root,int target,bool tag)
{
if(!root)return 0;
if(tag){//判断是否作为根节点
if(root->val==target)return 1+findnode(root->left,target,false)+findnode(root->right,target,false);
return 0;
}
else{
if(root->val==target)return 1+max(findnode(root->left,target,false),
findnode(root->right,target,false));
return 0;
}
}
int getmax(int a,int b,int c)
{
return max(a,max(b,c));
}
int longestUnivaluePath(TreeNode* root) {
if(!root)return 0;
return getmax(findnode(root,root->val,true)-1,longestUnivaluePath(root->left),
longestUnivaluePath(root->right));
}
};
3)其他解法
class Solution {
int ans;
public int longestUnivaluePath(TreeNode root) {
ans = 0;
arrowLength(root);
return ans;
}
public int arrowLength(TreeNode node) {
if (node == null) return 0;
int left = arrowLength(node.left);
int right = arrowLength(node.right);
int arrowLeft = 0, arrowRight = 0;
if (node.left != null && node.left.val == node.val) {
arrowLeft += left + 1;
}
if (node.right != null && node.right.val == node.val) {
arrowRight += right + 1;
}
ans = Math.max(ans, arrowLeft + arrowRight);
return Math.max(arrowLeft, arrowRight);
}
}
作者:LeetCode
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
class Solution {
public:
int ans;
int asroot(TreeNode*root)
{
if(!root)return 0;
int L=asroot(root->left),R=asroot(root->right);//递归放前面
if(root->left&&root->left->val==root->val)
{
L=1+L;
}
else L=0;
if(root->right&&root->right->val==root->val)
{
R=1+R;
}
else R=0;
ans=max(ans,L+R);
return max(L,R);
}
int longestUnivaluePath(TreeNode* root) {
ans=0;
asroot(root);
return ans;
}
};
5)学到的东西
先递归到最下层,然后从下往上依次计算
深度优先
13、二叉树中第二小的节点(671、Easy)
1)题目要求
给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么该节点的值等于两个子节点中较小的一个。
给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出 -1 。
示例 1:
输入:
2
/
2 5
/
5 7
输出: 5
说明: 最小的值是 2 ,第二小的值是 5 。
示例 2:
输入:
2
/
2 2
输出: -1
说明: 最小的值是 2, 但是不存在第二小的值。
2)我的解法
c++
class Solution {
public:
long long min[2]={4147483647,4147483647};
void findnode(TreeNode*root)
{
if(!root)return ;
if(root->val<=min[0])min[0]=root->val;
else if(root->val<=min[1])min[1]=root->val;
findnode(root->left);
findnode(root->right);
}
int findSecondMinimumValue(TreeNode* root) {
findnode(root);
return min[1]==4147483647?-1:min[1];
}
};
3)其他解法
一次遍历;递归解法。没有进行额外的排序操作。时间复杂度为O(N),空间复杂度为O(h)
解题思路:
- 首先分析题意:每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么这个节点的值不大于它的子节点的值。也就是说当前节点的值是以当前节点为根节点的二叉树中的最小值。
- 同理,当前节点的左子节点的值是左子树中的最小值,右子节点的值是右子树中的最小值。注意当前如果左右子节点和根节点的值相同时,不能直接进行判断,因为左右子树中可能会存在第二小的值。此时,通过递归来找到解。
- 即,如果当前节点的值和左子节点的值相同,那么递归的去找左子树中的第二小的节点;如果当前节点的值和右子节点的值相同,那么递归的去找右子树中的第二小的节点。
- 左右子树递归完成后,仅考虑当前三个节点即可得出结果。若三个节点的值相等,则说明没有第二小节点,否则肯定能找到。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int findSecondMinimumValue(TreeNode* root) {
if(!root || !root->left || !root->right) return -1;//空节点或不满足题意
int left=root->left->val,right=root->right->val;
//若根节点和左节点值相同,则递归找左子树的第二小节点
if(root->val==root->left->val) left=findSecondMinimumValue(root->left);
//若根节点和右节点值相同,则递归找右子树的第二小节点
if(root->val==root->right->val) right=findSecondMinimumValue(root->right);
//若根节点等于左右子树的第二小节点返回-1
if(root->val==left && root->val==right) return -1;
//根据当前的根、左右节点的值继续判断
int min_lr=min(left,right);
if(root->val<min_lr) return min_lr;//根节点小于最小值,返回最小值
else return max(left,right);//根节点等于最小值,返回最大值
}
};
作者:dui-mian-de-er-ha-ni-kan-sha
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
class Solution {
public:
int findSecondMinimumValue(TreeNode* root) {
if(!root||!root->left|!root->right)return -1;
int L=root->left->val,R=root->right->val;
if(root->val==L)L=findSecondMinimumValue(root->left);//找到左子树中第二小的,之后与右节点比较
if(root->val==R)R=findSecondMinimumValue(root->right);//找到右子树中第二小的,之后与左节点比较
int smin=min(L,R);
int smax=max(L,R);
if(root->val<smin)return smin;
else {
if(root->val<smax)return smax;
else return -1;
}
}
};
5)学到的东西
递归思想,先递归到最下面,再从下面开始比较
审题,特殊的二叉树特殊对待。如此题,根据题意可知根节点就是最小的结点,那么只需判断根节点、(与根节点值相同的那一边)子树中第二小节点、另一边的那个子节点这三个节点即可。
比如当根节点与left值相同,那只需判断根节点,左子树中第二小节点,右节点这三个节点即可。
14、打家劫舍 III(337、Medium)
1)题目要求
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1:
输入: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:
输入: [3,4,5,1,3,null,1]
3
/ \
4 5
/ \ \
1 3 1
输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
2)我的解法
c++
(1)暴力:(超时)
class Solution {
public:
int find(TreeNode*root,bool tag)
{
if(!root)return 0;
if(tag)return root->val+find(root->left,false)+find(root->right,false);
//当抢了当前房子,它的两个子结点就不能抢了
int left=max(find(root->left,false),find(root->left,true));
int right=max(find(root->right,false),find(root->right,true));
return left+right;
}
int rob(TreeNode* root) {
return max(find(root,true),find(root,false));
}
};
(2)动态规划:(通过)
class Solution {
public:
unordered_map<TreeNode*,int> dp;
//以下为动态规划的状态转移方程
//int child=dp[i->left]+dp[i->right];
//int grandchild=dp[i->left->left]+dp[i->left->right]
//+dp[i->right->left]+dp[i->right->right]
//dp[i]=max(child,grandchild+i);
void find(TreeNode*root)
{
if(!root){dp[root]=0;return ;}
find(root->left);
find(root->right);//后序遍历
int child=dp[root->left]+dp[root->right];
int lchild=0,rchild=0;
if(root->left)lchild=dp[root->left->left]+dp[root->left->right];
if(root->right)rchild=dp[root->right->left]+dp[root->right->right];
dp[root]=max(lchild+rchild+root->val,child);
return;
}
int rob(TreeNode* root) {
find(root);
return dp[root];
}
};
3)其他解法
public int rob(TreeNode root) {
if (root == null) return 0;
int money = root.val;
if (root.left != null) {
money += (rob(root.left.left) + rob(root.left.right));
}
if (root.right != null) {
money += (rob(root.right.left) + rob(root.right.right));
}
return Math.max(money, rob(root.left) + rob(root.right));
}
解法二、记忆化 - 解决重复子问题
针对解法一种速度太慢的问题,经过分析其实现,我们发现爷爷在计算自己能偷多少钱的时候,同时计算了 4 个孙子能偷多少钱,也计算了 2 个儿子能偷多少钱。这样在儿子当爷爷时,就会产生重复计算一遍孙子节点。
于是乎我们发现了一个动态规划的关键优化点
重复子问题
我们这一步针对重复子问题进行优化,我们在做斐波那契数列时,使用的优化方案是记忆化,但是之前的问题都是使用数组解决的,把每次计算的结果都存起来,下次如果再来计算,就从缓存中取,不再计算了,这样就保证每个数字只计算一次。
由于二叉树不适合拿数组当缓存,我们这次使用哈希表来存储结果,TreeNode 当做 key,能偷的钱当做 value
解法一加上记忆化优化后代码如下:
public int rob(TreeNode root) {
HashMap<TreeNode, Integer> memo = new HashMap<>();
return robInternal(root, memo);
}
public int robInternal(TreeNode root, HashMap<TreeNode, Integer> memo) {
if (root == null) return 0;
if (memo.containsKey(root)) return memo.get(root);
int money = root.val;
if (root.left != null) {
money += (robInternal(root.left.left, memo) + robInternal(root.left.right, memo));
}
if (root.right != null) {
money += (robInternal(root.right.left, memo) + robInternal(root.right.right, memo));
}
int result = Math.max(money, robInternal(root.left, memo) + robInternal(root.right, memo));
memo.put(root, result);
return result;
}
(相当于打家劫舍1里面的滚动数组)
public int rob(TreeNode root) {
int[] result = robInternal(root);
return Math.max(result[0], result[1]);
}
public int[] robInternal(TreeNode root) {
if (root == null) return new int[2];
int[] result = new int[2];
int[] left = robInternal(root.left);
int[] right = robInternal(root.right);
result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
result[1] = left[0] + right[0] + root.val;
return result;
}
作者:reals
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
*/
class Solution {
public:
vector<int> find(TreeNode*root)
{
if(!root)return {0,0};
vector<int> L=find(root->left);
vector<int> R=find(root->right);
return {L[1]+R[1]+root->val,max(L[0],L[1])+max(R[0],R[1])};
}
int rob(TreeNode* root) {
vector<int> result=find(root);
return max(result[0],result[1]);
}
};
5)学到的东西
动态规划算法(想出状态转移方程是关键)、记忆化
后序遍历、深度优先
先递归到最底层,从最底层一步步往上算
在动态规划(记忆化,状态转移方程)的基础上加上分类讨论。当前结点偷的时候、当前结点不偷的时候,这样可以避免去计算孙子结点
层次遍历
1、二叉树的层平均值(637、Easy)
1)题目要求
给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。
示例 1:
输入:
3
/
9 20
/
15 7
输出:[3, 14.5, 11]
解释:
第 0 层的平均值是 3 , 第1层是 14.5 , 第2层是 11 。因此返回 [3, 14.5, 11] 。
提示:
节点值的范围在32位有符号整数范围内。
2)我的解法
c++
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
vector<double> result;
queue<TreeNode*> q;
q.push(root);
while(!q.empty())
{
vector<TreeNode*> temp;
double sum=0;
while(!q.empty()){
if(!q.front()){q.pop();continue;}
temp.push_back(q.front());
sum+=q.front()->val;
q.pop();
}
if(temp.size()==0)break;
result.push_back(sum/temp.size());
for(int i=0;i<temp.size();i++)
{
q.push(temp[i]->left);
q.push(temp[i]->right);
}
}
return result;
}
};
3)其他解法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Solution {
public List < Double > averageOfLevels(TreeNode root) {
List < Integer > count = new ArrayList < > ();
List < Double > res = new ArrayList < > ();
average(root, 0, res, count);
for (int i = 0; i < res.size(); i++)
res.set(i, res.get(i) / count.get(i));
return res;
}
public void average(TreeNode t, int i, List < Double > sum, List < Integer > count) {
if (t == null)
return;
if (i < sum.size()) {
sum.set(i, sum.get(i) + t.val);
count.set(i, count.get(i) + 1);
} else {
sum.add(1.0 * t.val);
count.add(1);
}
average(t.left, i + 1, sum, count);
average(t.right, i + 1, sum, count);
}
}
public List<Double> averageOfLevels(TreeNode root){
List<Double> res = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
//外层while循环用于遍历层,判断的逻辑是:只要queue非空,那就证明至少还有一层没有遍历到
long sum = 0;
long count = 0;
Queue<TreeNode> temp = new LinkedList<>();
while(!queue.isEmpty()){
//内层while循环用于遍历本层的每个结点
TreeNode n = queue.remove();//一个结点出队列
sum += n.val;
count++;
//将当前结点的左右孩子加入队列temp
if(n.left!=null){
temp.add(n.left);
}
if(n.right!=null){
temp.add(n.right);
}
}
/*
* 本层结点遍历结束,queue中为空,temp中保存的是下一层的结点;
* 将temp传给queue,外层while循环遍历下一层结点
*/
queue = temp;
//将本层的平均值加入res数组
res.add(sum*1.0/count);
}
return res;
}
作者:LeetCode
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
vector<double> result;
queue<TreeNode*> q;
q.push(root);
while(!q.empty())
{
vector<TreeNode*> temp;//保存上一层
double sum=0;
while(!q.empty()){
if(!q.front()){q.pop();continue;}
temp.push_back(q.front());
sum+=q.front()->val;
q.pop();
}
if(temp.size()==0)break;
result.push_back(sum/temp.size());
for(int i=0;i<temp.size();i++)
{
q.push(temp[i]->left);
q.push(temp[i]->right);
}
}
return result;
}
};
5)学到的东西
深度优先遍历、广度优先遍历
深度优先遍历也可以解决层次问题
2、找树左下角的值(513、Medium)
1)题目要求
给定一个二叉树,在树的最后一行找到最左边的值。
示例 1:
输入:
2
/
1 3
输出:
1
示例 2:
输入:
1
/ \
2 3
/ / \
4 5 6
/
7
输出:
7
注意: 您可以假设树(即给定的根节点)不为 NULL。
2)我的解法
c++
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
queue<TreeNode*> q;
q.push(root);
int last=0;
while(!q.empty())
{
vector<TreeNode*> temp;
while(!q.empty())
{
if(!q.front()){q.pop();continue;}
temp.push_back(q.front());
q.pop();
}
if(temp.size()==0)break;
last=temp[0]->val;
for(int i=0;i<temp.size();i++)
{
q.push(temp[i]->left);
q.push(temp[i]->right);
}
}
return last;
}
};
3)其他解法
方法1:迭代,层序遍历,保存每层左边第一个元素为结果,遍历完成后直接返回结果
//方法1:迭代
public int findBottomLeftValue1(TreeNode root) {
//层序遍历
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int res = 0;
while (!queue.isEmpty()) {
int count = queue.size();
//将每层左边第一个作为结果
res = queue.peek().val;
while (count-- > 0) {
TreeNode cur = queue.poll();
if (cur.left != null) {
queue.add(cur.left);
}
if (cur.right != null) {
queue.add(cur.right);
}
}
}
return res;
}
方法2:递归:中序遍历,找到最深层,将左边第一个元素保存在结果中
//方法2:递归
class Solution{
int maxDepth = -1, res = -1;
public int findBottomLeftValue2(TreeNode root) {
helper(root, 0);
return res;
}
private void helper(TreeNode root, int depth) {
if (root == null) return;
helper(root.left, depth + 1);
//判断是否是最大深度
if (depth > maxDepth) {
maxDepth = depth;
res = root.val;
}
helper(root.right, depth + 1);
}
}
作者:mmmmmJCY
链接:link
来源:力扣(LeetCode)
方法3:
通常BFS遍历都是从上到下,从左到右。然而根据题目意思,是要取到最下面,最左边的元素。故只需要对BFS遍历稍作改进即可。具体思路为从上到下保持不变, 但水平遍历方向改为从右到左即可。如此一来,先上后下,先右后左,此策略走下去,最后一个元素必然是最下方最左边的元素,最后返回该节点node.val即可
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def findBottomLeftValue(self, root: TreeNode) -> int:
queue = [root]
while queue:
node = queue.pop(0)
if node.right: # 先右后左
queue.append(node.right)
if node.left:
queue.append(node.left)
return node.val
作者:quantbruce
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
queue<TreeNode*> q;
q.push(root);
int last=0;
while(!q.empty())
{
TreeNode*cur=q.front();
q.pop();
if(!cur)continue;
last=cur->val;
q.push(cur->right);//先右后左
q.push(cur->left);
}
return last;
}
};
5)学到的东西
要懂得变通,比如这题仅需把一般的BFS改成先右后左即可
中序遍历(递归)
前中后序遍历
1、二叉树的前序遍历(144、Medium)
1)题目要求
给定一个二叉树,返回它的 前序 遍历。
示例:
输入: [1,null,2,3]
1
2
/
3
输出: [1,2,3]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
2)我的解法
1、递归
class Solution {
private List<Integer> result=new ArrayList<>();
public void getResult(TreeNode root){
if(root==null)return;
result.add(root.val);
getResult(root.left);
getResult(root.right);
}
public List<Integer> preorderTraversal(TreeNode root) {
getResult(root);
return result;
}
}
2、迭代
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result=new ArrayList<>();
Stack<TreeNode> s=new Stack();
if(root==null)return result;
TreeNode t=root;
while(true){
result.add(t.val);
if(t.right!=null)s.push(t.right);
if(t.left!=null)t=t.left;
else {
if(s.empty())break;
t=s.peek();
s.pop();
}
}
return result;
}
}
3)其他解法
1、迭代
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
LinkedList<TreeNode> stack = new LinkedList<>();
LinkedList<Integer> output = new LinkedList<>();
if (root == null) {
return output;
}
stack.add(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pollLast();
output.add(node.val);
if (node.right != null) {
stack.add(node.right);
}
if (node.left != null) {
stack.add(node.left);
}
}
return output;
}
}
4)自己的优化代码
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result=new ArrayList<>();
Stack<TreeNode> s=new Stack();
if(root==null)return result;
s.push(root);
TreeNode t=root;
while(!s.empty()){
t=s.peek();
s.pop();
result.add(t.val);
if(t.right!=null)s.push(t.right);
if(t.left!=null)s.push(t.left);
}
return result;
}
}
5)学到的东西
java 中栈的使用: java stack的详细实现分析
利用栈的迭代思想
2、二叉树的中序遍历(94、Medium)
1)题目要求
给定一个二叉树,返回它的中序 遍历。
示例:
输入: [1,null,2,3]
1
2
/
3
输出: [1,3,2]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
2)我的解法
1、递归
class Solution {
private List<Integer> result=new ArrayList<>();
public void getResult(TreeNode root){
if(root==null)return ;
getResult(root.left);
result.add(root.val);
getResult(root.right);
}
public List<Integer> inorderTraversal(TreeNode root) {
getResult(root);
return result;
}
}
2、迭代
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result=new ArrayList<>();
Map<TreeNode,Boolean> map=new HashMap<>();
Stack<TreeNode> s=new Stack();
if(root==null)return result;
TreeNode t=root;
while(true){
while(true){
if(map.containsKey(t))break;//如果已经入过栈,跳出
if(t.left==null){//如果左节点为空,入栈右结点后跳出
if(t.right!=null)s.push(t.right);
break;
}
else {//入栈自身和右结点,访问左结点
if(t.right!=null)s.push(t.right);
map.put(t,true);s.push(t);t=t.left;
}
}
result.add(t.val);
if(s.empty())break;
t=s.peek();
s.pop();
}
return result;
}
}
3)其他解法
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Deque<TreeNode> stk = new LinkedList<TreeNode>();
while (root != null || !stk.isEmpty()) {
while (root != null) {
stk.push(root);
root = root.left;
}
root = stk.pop();
res.add(root.val);
root = root.right;
}
return res;
}
}
作者:LeetCode-Solution
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result=new ArrayList<>();
Stack<TreeNode> s=new Stack();
if(root==null)return result;
TreeNode t=root;
while(t!=null||!s.empty()){
while(t!=null){
s.push(t);
t=t.left;
}
t=s.pop();
result.add(t.val);
t=t.right;
}
return result;
}
}
5)学到的东西
中序遍历
迭代思想:把整个子树存进去,而不是存结点。按照递归的思想来写迭代:先遍历完左边,然后中间,然后右边,然后下一次循环
3、二叉树的后序遍历(145、Medium)
1)题目要求
给定一个二叉树,返回它的 后序 遍历。
示例:
输入: [1,null,2,3]
1
2
/
3
输出: [3,2,1]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
2)我的解法
1、递归
class Solution {
private List<Integer> result=new ArrayList<>();
public void getResult(TreeNode root){
if(root==null)return ;
getResult(root.left);
getResult(root.right);
result.add(root.val);
}
public List<Integer> postorderTraversal(TreeNode root) {
getResult(root);
return result;
}
}
2、迭代
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result=new ArrayList<>();
Map<TreeNode,Boolean> map=new HashMap<>();
Stack<TreeNode> s=new Stack<>();
TreeNode t=root;
while(t!=null||!s.empty()){
while(t!=null){
s.push(t);
t=t.left;
}
t=s.peek();
if(t.right==null||map.containsKey(t.right)){
s.pop();
result.add(t.val);
map.put(t,true);
t=null;
}
else t=t.right;
}
return result;
}
}
3)其他解法
1、官方的神奇解法。。。
从根节点开始依次迭代,弹出栈顶元素输出到输出列表中,然后依次压入它的所有孩子节点,按照从上到下、从左至右的顺序依次压入栈中。
因为深度优先搜索后序遍历的顺序是从下到上、从左至右,所以需要将输出列表逆序输出。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
LinkedList<TreeNode> stack = new LinkedList<>();
LinkedList<Integer> output = new LinkedList<>();
if (root == null) {
return output;
}
stack.add(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pollLast();
output.addFirst(node.val);
if (node.left != null) {
stack.add(node.left);
}
if (node.right != null) {
stack.add(node.right);
}
}
return output;
}
}
作者:LeetCode
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result=new ArrayList<>();
Map<TreeNode,Boolean> map=new HashMap<>();
Stack<TreeNode> s=new Stack<>();
TreeNode t=root;
while(t!=null||!s.empty()){
while(t!=null){
s.push(t);
t=t.left;
}
t=s.peek();
if(t.right==null||map.containsKey(t.right)){
s.pop();
result.add(t.val);
map.put(t,true);
t=null;
}
else t=t.right;
}
return result;
}
}
5)学到的东西
后序遍历:可以等效于前序遍历调换左右后的逆序(前序为root–left–right,变为root–right–left,再反转后正好为后序:left–right—root)
迭代思想:按照递归思考。
每次循环时,先遍历完左,之后如果右边为空或右边访问过就访问当前结点,如果右边不为空且未访问过,则让t=t.right