Tag Archives:

LeetCode Path Sum II

113. Path Sum II

Given a binary tree and a sum, find all root-to-leaf paths where each path’s sum equals the given sum.

Note: A leaf is a node with no children.

Example:

Given the below binary tree and sum = 22,

      5
     / \
    4   8
   /   / \
  11  13  4
 /  \    / \
7    2  5   1

Return:

[
   [5,4,11,2],
   [5,8,4,5]
]

相比上一题LeetCode Path Sum更进一步,本题要把所有等于sum的路径记录下来,所以递归求解的时候要带着一个数组,把从根节点到当前节点的路径记录下来。常规题,完整代码如下:

class Solution {
public:
    void dfs(vector<vector<int> >& ans, vector<int>& path, int& target, int cur, TreeNode* root)
    {
        if (root->left == NULL && root->right == NULL) {
            if (cur + root->val == target) {
                path.push_back(root->val);
                ans.push_back(path);
                path.pop_back();
            }
            return;
        }
        if (root->left != NULL) {
            path.push_back(root->val);
            dfs(ans, path, target, cur + root->val, root->left);
            path.pop_back();
        }
        if (root->right != NULL) {
            path.push_back(root->val);
            dfs(ans, path, target, cur + root->val, root->right);
            path.pop_back();
        }
    }
    vector<vector<int> > pathSum(TreeNode* root, int sum)
    {
        vector<vector<int> > ans;
        if (root == NULL)
            return ans;
        vector<int> path;
        dfs(ans, path, sum, 0, root);
        return ans;
    }
};

本代码提交AC,用时16MS。
二刷。更简洁的代码,如下:

class Solution {
private:
    void dfs(vector<vector<int> >& ans, vector<int>& candidate, TreeNode* root, int sum)
    {
        candidate.push_back(root->val);
        sum -= root->val;
        if (root->left == NULL && root->right == NULL && sum == 0) {
            ans.push_back(candidate);
        }
        if (root->left != NULL)
            dfs(ans, candidate, root->left, sum);
        if (root->right != NULL)
            dfs(ans, candidate, root->right, sum);
        candidate.pop_back();
    }
public:
    vector<vector<int> > pathSum(TreeNode* root, int sum)
    {
        if (root == NULL)
            return {};
        vector<vector<int> > ans;
        vector<int> candidate;
        dfs(ans, candidate, root, sum);
        return ans;
    }
};

本代码提交AC,用时9MS。

三刷。更易读代码:

class Solution {
public:
	void dfs(TreeNode *root, vector<vector<int>> &ans, vector<int> &cand, int sum) {
		if (root->left == NULL && root->right == NULL && sum == 0) {
			ans.push_back(cand);
			return;
		}
		if (root->left != NULL) {
			cand.push_back(root->left->val);
			sum -= root->left->val;
			dfs(root->left, ans, cand, sum);
			sum += root->left->val;
			cand.pop_back();
		}
		if (root->right != NULL) {
			cand.push_back(root->right->val);
			sum -= root->right->val;
			dfs(root->right, ans, cand, sum);
			sum += root->right->val;
			cand.pop_back();
		}
	}
	vector<vector<int>> pathSum(TreeNode* root, int sum) {
		if (root == NULL)return {};
		vector<vector<int>> ans;
		vector<int> cand = { root->val };
		dfs(root, ans, cand, sum - root->val);
		return ans;
	}
};

本代码提交AC,用时12MS。

LeetCode Path Sum

112. Path Sum

Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given sum.

Note: A leaf is a node with no children.

Example:

Given the below binary tree and sum = 22,

      5
     / \
    4   8
   /   / \
  11  13  4
 /  \      \
7    2      1

return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.


本题要一个数是否等于从树根到某个叶子的和。简单题,直接dfs把所有从根到叶子的路径和算出来,存到set里面,然后用sum去set里查。当然如果还想更高效,可以用hash,比如unordered_map。 完整代码如下:

class Solution {
public:
    void dfs(set<int>& vals, int cur, TreeNode* root)
    {
        if (root->left == NULL && root->right == NULL) {
            vals.insert(cur + root->val);
            return;
        }
        if (root->left != NULL) {
            dfs(vals, cur + root->val, root->left);
        }
        if (root->right != NULL) {
            dfs(vals, cur + root->val, root->right);
        }
    }
    bool hasPathSum(TreeNode* root, int sum)
    {
        if (root == NULL)
            return false;
        set<int> vals;
        dfs(vals, 0, root);
        if (vals.count(sum) > 0)
            return true;
        else
            return false;
    }
};

本代码提交AC,用时13MS。
二刷。直接递归判断某一条路径的和是否等于sum就好了,代码如下:

class Solution {
public:
    bool hasPathSum(TreeNode* root, int sum)
    {
        if (root == NULL)
            return false;
        if (root->left == NULL && root->right == NULL && root->val == sum)
            return true;
        if (root->left == NULL && root->right == NULL && root->val != sum)
            return false;
        if (root->left != NULL)
            if (hasPathSum(root->left, sum – root->val))
                return true;
        if (root->right != NULL)
            if (hasPathSum(root->right, sum – root->val))
                return true;
        return false;
    }
};

本代码提交AC,用时9MS。

LeetCode Binary Tree Postorder Traversal

145. Binary Tree Postorder Traversal

Given a binary tree, return the postorder traversal of its nodes’ values.

Example:

Input: [1,null,2,3]
   1
    \
     2
    /
   3

Output: [3,2,1]

Follow up: Recursive solution is trivial, could you do it iteratively?


实现二叉树的后续遍历算法,需要非递归形式哦~这题好难…是三种非递归遍历算法中最难的一种了。
惯例,先上递归算法:

class Solution {
public:
    void work(vector<int>& ans, TreeNode* root)
    {
        if (root == NULL)
            return;
        work(ans, root->left);
        work(ans, root->right);
        ans.push_back(root->val);
    }
    vector<int> postorderTraversal(TreeNode* root)
    {
        vector<int> ans;
        work(ans, root);
        return ans;
    }
};

本代码提交AC,用时3MS。
下面来想非递归算法。续遍历的顺序是:左右,根节点是最后访问的。还是用堆栈来实现,那么根据堆栈的后进先出性质,压栈的顺序应该是根右左
那么什么时候可以访问根节点呢,只有当这个节点是叶子节点或者左右孩子都访问完之后才可以。判断是否为叶子节点很容易,那么怎么判断左右孩子都访问完了呢,此时我们可以用一个变量pre来记录上一次访问的节点,curr为当前栈顶节点。如果pre是curr的左或右孩子节点,则说明左右孩子已经访问过了,此时我们就可以访问curr了。
本算法的代码如下:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root)
    {
        vector<int> ans;
        if (root == NULL)
            return ans;
        stack<TreeNode*> tree;
        TreeNode *curr, *pre = NULL;
        tree.push(root); // 根
        while (!tree.empty()) {
            curr = tree.top();
            if ((curr->left == NULL && curr->right == NULL) || // 已到叶子节点
                (pre != NULL && (curr->left == pre || curr->right == pre))) { // 回溯的时候
                ans.push_back(curr->val);
                tree.pop();
                pre = curr;
            }
            else {
                if (curr->right != NULL)
                    tree.push(curr->right); // 右
                if (curr->left != NULL)
                    tree.push(curr->left); // 左
            }
        }
        return ans;
    }
};

本代码提交AC,用时0MS。

可能有同学担心curr->left==pre不能一定说明左右孩子都访问完了,只能说明左孩子访问完了。但是根据我们的压栈顺序,当curr同时又左右孩子的时候,不会走这条判断。如下左图所示,首先4会弹栈,然后栈顶变为了5,此时pre=4, curr=5,访问5;然后5弹栈,栈顶为2,此时pre=5,curr=2,所以走的是curr->right==pre,访问2。只有当curr只有左孩子没有右孩子的时候才会判断curr->left==pre,如下右图,此时是可以访问curr的。

还有一种很简洁漂亮的解法,用两个堆栈。再次回忆序遍历顺序:左右,我们怎样用两个堆栈得到这样的顺序呢。如下图所示,第一个堆栈压栈顺序是左右根,然后弹出来压到第二个堆栈里,则从第二个堆栈里弹出来的顺序就是左右根了。注意,虽然第一次压栈的顺序已经是左右根了,我们要得到的也是左右根,为什么还要经过第二个堆栈这样倒腾一下呢?因为堆栈1的左右根和从堆栈2出来的左右根是不一样的,前者是靠近根节点的左右根,但是我们的后序遍历是要从叶子节点开始得到左右根,而从堆栈2出来的就是从叶子节点开始的左右根。

下面结合代码来说:

 class Solution {
   public: vector < int > postorderTraversal(TreeNode * root) {
     vector < int > ans;
     if (root == NULL) return ans;
     stack < TreeNode * > s1, s2;
     TreeNode * top;
     s1.push(root); // 根 
     while (!s1.empty()) {
       top = s1.top(); // (1)
       s1.pop(); // (1)
       s2.push(top); // (1)
       if (top - > left != NULL) {
         // 左
         s1.push(top - > left);
       }
       if (top - > right != NULL) {
         // 右 
         s1.push(top - > right);
       }
     }
     while (!s2.empty()) {
       top = s2.top();
       ans.push_back(top - > val);
       s2.pop();
     }
     return ans;
   }
 };

从第13、16行可知,对于堆栈1,我们先压了左后压了右,但是从第8行来看,根是最先压的,看起来不符合左右根的压栈顺序,但是请注意,代码(1)部分在先于13、16行弹栈了,然后马上压到堆栈2了,这样的效果不就是根节点在堆栈1的栈顶,然后被压到了堆栈2的底部了吗。所以和上图是统一的。

本代码提交AC,用时0MS。
定睛一看,这居然是道hard题。。。

三刷。这一题和中序遍历的迭代解法很类似。后序遍历是左右根,依然是要最先访问左子树的左叶子,所以依然可以使用函数putAllLeft将根沿着左子树的节点都放到栈中,只是在弹栈访问时,需要注意,如果该节点的右孩子已经访问,则无需再调用putAllLeft了,否则会导致死循环。所以在访问某个节点时,记录一下,存到pre中,作为下一个节点访问时上一个访问的节点。如果当前要访问的右孩子为pre,则说明右孩子已经访问过了,无需对右孩子调用putAllLeft。

完整代码如下:

class Solution {
public:
	void PutAllLeft(TreeNode *root, stack<TreeNode*> &stk) {
		while (root != NULL) {
			stk.push(root);
			root = root->left;
		}
	}
	vector<int> postorderTraversal(TreeNode* root) {
		vector<int> ans;
		stack<TreeNode*> stk;
		PutAllLeft(root, stk);
		TreeNode *pre = NULL;
		while (!stk.empty()) {
			TreeNode *cur = stk.top();
			if (cur->right != NULL && cur->right != pre) {
				PutAllLeft(cur->right, stk);
			}
			else {
				ans.push_back(cur->val);
				stk.pop();
				pre = cur;
			}
		}
		return ans;
	}
};

本代码提交AC,用时0MS。

LeetCode Binary Tree Preorder Traversal

144. Binary Tree Preorder Traversal

Given a binary tree, return the preorder traversal of its nodes’ values.

Example:

Input: [1,null,2,3]
   1
    \
     2
    /
   3

Output: [1,2,3]

Follow up: Recursive solution is trivial, could you do it iteratively?


这一题要实现二叉树的前序遍历,序遍历的顺序是:左右。
首先献上妇孺皆知的递归解法:

class Solution {
public:
    void work(vector<int>& ans, TreeNode* root)
    {
        if (root == NULL)
            return;
        ans.push_back(root->val);
        work(ans, root->left);
        work(ans, root->right);
    }
    vector<int> preorderTraversal(TreeNode* root)
    {
        vector<int> ans;
        work(ans, root);
        return ans;
    }
};

本代码提交AC,用时3MS。和LeetCode Binary Tree Inorder Traversal一样,本题也要实现前序遍历的迭代算法。迭代的思路同样是用堆栈来实现:首先把当前根节点压栈,然后注意,因为前序遍历是先访问左子树,后访问右子树,所以我们压栈的时候,要先压右子树,再压左子树,这样弹栈的时候才能符合前序遍历的要求。除此之外,我觉得实现起来比中序遍历要简单。
迭代版本的完整代码如下:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root)
    {
        vector<int> ans;
        if (root == NULL)
            return ans;
        stack<TreeNode*> tree;
        tree.push(root);
        TreeNode* top;
        while (!tree.empty()) {
            top = tree.top();
            ans.push_back(top->val);
            tree.pop();
            if (top->right != NULL) { // 先压栈右子树
                tree.push(top->right);
            }
            if (top->left != NULL) { // 再压栈左子树
                tree.push(top->left);
            }
        }
        return ans;
    }
};

本代码提交AC,用时也是3MS。

LeetCode Binary Tree Inorder Traversal

94. Binary Tree Inorder Traversal

Given a binary tree, return the inorder traversal of its nodes’ values.

Example:

Input: [1,null,2,3]
   1
    \
     2
    /
   3

Output: [1,3,2]

Follow up: Recursive solution is trivial, could you do it iteratively?


本题要实现二叉树的中序遍历。这个题应该是很基本的二叉树的遍历题,本科学数据结构对这个是基本要求。不过本科学的是中文教材,只知道前序、中序、后序,不知道对应的英文是什么,WIKI上的Tree traversal图解还是很通俗易懂的:前序(Pre-order),中序(In-order),后序(Post-order)和层次遍历(Level-order)。
前序、中序和后序的区别很容易记,只要记住他们的前、中、后分别是访问根节点的顺序。比如前序顺序是先访问根节点,然后依次访问左右孩子节点。序:左右;序:左右;序:左右
本题要实现二叉树的中序遍历,如上所述,先访问左子树,然后访问根节点,最后访问右子树。用递归的方法非常简洁直观。递归的完整代码如下:

class Solution {
public:
    void work(vector<int>& ans, TreeNode* root)
    {
        if (root == NULL)
            return;
        work(ans, root->left); // 左
        ans.push_back(root->val); // 根
        work(ans, root->right); // 右
    }
    vector<int> inorderTraversal(TreeNode* root)
    {
        vector<int> ans;
        work(ans, root);
        return ans;
    }
};

本代码提交AC,用时0MS。

但是Note问能不能用迭代实现以下呢?当然可以,所以可以用递归实现的算法肯定都可以用迭代的方法实现,只是有时候迭代写起来更麻烦。
我的思路是肯定要首先找到树的最左下角的叶子节点,这个节点是最先被访问的,然后访问根节点,最后访问右子树。访问右子树的时候,又是重复之前的操作,首先找到右子树中的最左下角的叶子节点,访问它,然后访问根节点,最后又访问其右子树。
上述过程需要在访问完左孩子之后再访问根节点,但是树中节点的指向是单向的,只能由根访问到孩子,不能由孩子回溯到根。我们不可能在找孩子的同时保存该孩子的父亲节点,这样太麻烦了。于是我想到了用堆栈,在找最左下角的叶子节点的时候,顺便把经过的祖先节点压到堆栈中,当访问完孩子节点后,弹栈,此时的栈顶节点就是父亲节点了。如果父节点有右子树,则进入右子树,重复之前的过程(找右子树的最左下角叶子)。
迭代法的完整代码如下:

class Solution {
public:
    void putAllLeft(TreeNode* root, stack<TreeNode*>& tree)
    {
        while (root != NULL) {
            tree.push(root);
            root = root->left;
        }
    }
    vector<int> inorderTraversal(TreeNode* root)
    {
        vector<int> ans;
        stack<TreeNode*> tree;
        putAllLeft(root, tree); // 找最左边的叶子节点
        TreeNode* top;
        while (!tree.empty()) {
            top = tree.top();
            ans.push_back(top->val); // 访问左(根)节点
            tree.pop();
            putAllLeft(top->right, tree); // 访问右节点
        }
        return ans;
    }
};

本代码提交AC,用时0MS。
调试的时候可以使用我之前写过的由数组生成二叉树的函数,挺方便的。

LeetCode Same Tree

100. Same Tree

Given two binary trees, write a function to check if they are the same or not.

Two binary trees are considered the same if they are structurally identical and the nodes have the same value.

Example 1:

Input:     1         1
          / \       / \
         2   3     2   3

        [1,2,3],   [1,2,3]

Output: true

Example 2:

Input:     1         1
          /           \
         2             2

        [1,2],     [1,null,2]

Output: false

Example 3:

Input:     1         1
          / \       / \
         2   1     1   2

        [1,2,1],   [1,1,2]

Output: false

判断两棵二叉树是否相同。我第一想到的是对两棵树都先序遍历一下,然后看看遍历出来的序列是否一致,如果一致则树是一样的。

class Solution {
public:
    void preOrderTraverse(TreeNode* t, vector<int>& res)
    {
        if (t != NULL) {
            res.push_back(t->val);
            preOrderTraverse(t->left, res);
            preOrderTraverse(t->right, res);
        }
    }
    bool isSameTree(TreeNode* p, TreeNode* q)
    {
        vector<int> pRes, qRes;
        preOrderTraverse(p, pRes);
        preOrderTraverse(q, qRes);
        if (pRes.size() != qRes.size())
            return false;
        for (int i = 0; i < pRes.size(); i++) {
            if (pRes[i] != qRes[i])
                return false;
        }
        return true;
    }
};

但是这样做居然是错的!请注意,虽然两棵树的遍历结果是一样的,并不代表这两棵树的结构是一样的。比如[10,5,15]和[10,5,null,null,15],虽然先序遍历的结果都是10,5,15,但是这两棵树的结构是不一样的。所以不能只看遍历的结果,在遍历的时候就要判断是否相同。如果在同一个位置上,一颗节点为空,另一棵上有值,结构就不一样了。要么两个节点都为空或者有值且相等。
完整代码如下:

class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q)
    {
        if (p == NULL && q == NULL)
            return true;
        if (p == NULL || q == NULL)
            return false;
        if (p->val != q->val)
            return false;
        return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
    }
};

本代码提交AC,用时0MS。

LeetCode Minimum Depth of Binary Tree

111. Minimum Depth of Binary Tree

Given a binary tree, find its minimum depth.

The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node.

Note: A leaf is a node with no children.

Example:

Given binary tree [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

return its minimum depth = 2.


求一棵二叉树的最小深度,类似于LeetCode Maximum Depth of Binary Tree求最大深度,但是稍微难一点。 请注意,不能直接把求最大深度的大于号改为小于号:

class Solution {
public:
    int dfs(TreeNode* root, int nDepth)
    {
        if (root == NULL)
            return nDepth;
        int left = dfs(root->left, nDepth + 1);
        int right = dfs(root->right, nDepth + 1);
        return left < right ? left : right;
    }
    int minDepth(TreeNode* root) { return dfs(root, 0); }
};

因为如上图所示,当走到A点时,没有右孩子,导致int right = dfs(root->right, nDepth + 1);会得到A点的高度2,比左孩子的高度低,即最小高度为2了。但是最小高度显然是4。所以需要稍加修改,使得如果某个孩子为空时,不在该孩子深搜了,只有当两个孩子都为空时(即是叶子节点时),才得到一个合法的深度,去和其他深度做比较。

深度搜索的完整代码如下:

class Solution {
public:
    int dfs(TreeNode* root, int nDepth)
    {
        if (root == NULL)
            return nDepth;
        if (root->left == NULL && root->right == NULL)
            return nDepth + 1;
        int left = root->left == NULL ? INT_MAX : dfs(root->left, nDepth + 1);
        int right = root->right == NULL ? INT_MAX : dfs(root->right, nDepth + 1);
        return left < right ? left : right;
    }
    int minDepth(TreeNode* root) { return dfs(root, 0); }
};

本代码提交AC,用时9MS。
但是DFS必须走到所有叶子节点才能得到最小深度,而如果采用广度优先搜索BFS时,遇到的第一个叶子节点的深度就是最小深度了,就可以返回了。所以理论上,BFS的性能会更优。
BFS的完整代码如下:

struct NewTreeNode {
    TreeNode* tn;
    int depth;
};
class Solution {
public:
    int bfs(TreeNode* root)
    {
        queue<NewTreeNode*> qTree;
        NewTreeNode* ntn = new NewTreeNode;
        ntn->tn = root;
        ntn->depth = 0;
        qTree.push(ntn);
        NewTreeNode* top = new NewTreeNode;
        while (!qTree.empty()) {
            top = qTree.front();
            qTree.pop();
            if (top->tn == NULL)
                return top->depth;
            if (top->tn->left == NULL && top->tn->right == NULL)
                return top->depth + 1;
            if (top->tn->left != NULL) {
                NewTreeNode* tmp = new NewTreeNode;
                tmp->tn = top->tn->left;
                tmp->depth = top->depth + 1;
                qTree.push(tmp);
            }
            if (top->tn->right != NULL) {
                NewTreeNode* tmp = new NewTreeNode;
                tmp->tn = top->tn->right;
                tmp->depth = top->depth + 1;
                qTree.push(tmp);
            }
        }
        return -1;
    }
    int minDepth(TreeNode* root) { return bfs(root); }
};

本代码提交AC,用时也是9MS。
我看Leetcode上有很多关于树的题,为了方便测试,自己写了一个由数组构造树结构的函数,如下:

#include <vector>
using namespace std;
// Definition for a binary tree node.
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x)
        : val(x)
        , left(NULL)
        , right(NULL)
    {
    }
};
TreeNode* genTree(vector<int>& vi, int id)
{
    if (vi.size() == 0)
        return NULL;
    TreeNode* root = new TreeNode(vi[id]);
    if (2 * id + 1 < vi.size() && vi[2 * id + 1] != -1) {
        root->left = genTree(vi, 2 * id + 1);
    }
    if (2 * id + 2 < vi.size() && vi[2 * id + 2] != -1) {
        root->right = genTree(vi, 2 * id + 2);
    }
    return root;
}
int main()
{
    vector<int> vTree = { 1, 2, 3, 4, 5 };
    TreeNode* root = genTree(vTree, 0);
    return 0;
}

使用时只要传入数组和0,返回的就是一个建好的树。用数组存树,一般第i号节点的左右孩子下标分别为2i+1和2i+2。存好之后恰好是一棵树的层次遍历结果。
如果要构建的不是一棵完全树,比如下面的左图,则先需要转换为完全图,也就是用-1代替一个不存在的节点,此时传入的数组应该是[1,-1,2,-1,-1,3,-1],genTree函数遇到-1会自动跳过不创建节点。当然如果你的树中本来就有-1这个值,也可以把-1换成其他数。

二刷。BFS的代码还可以写得更简洁漂亮一些,类似于层次遍历,遍历到某一层有叶子节点时,层数就是最小深度,代码如下:

class Solution {
public:
    int minDepth(TreeNode* root)
    {
        if (root == NULL)
            return 0;
        int depth = 0;
        queue<TreeNode*> q;
        q.push(root);
        while (!q.empty()) {
            ++depth;
            int n = q.size();
            while (n--) {
                TreeNode* cur = q.front();
                q.pop();
                if (cur->left == NULL && cur->right == NULL)
                    return depth;
                if (cur->left != NULL)
                    q.push(cur->left);
                if (cur->right != NULL)
                    q.push(cur->right);
            }
        }
        return depth;
    }
};

本代码提交AC,用时9MS。
DFS的代码也可以写得更漂亮一些:

class Solution {
public:
    int minDepth(TreeNode* root)
    {
        if (root == NULL)
            return 0;
        if (root->left == NULL)
            return minDepth(root->right) + 1;
        if (root->right == NULL)
            return minDepth(root->left) + 1;
        return min(minDepth(root->left), minDepth(root->right)) + 1;
    }
};

本代码提交AC,用时13MS,比BFS慢。

LeetCode Maximum Depth of Binary Tree

Given a binary tree, find its maximum depth.

The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.

Note: A leaf is a node with no children.

Example:

Given binary tree [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

return its depth = 3.


计算出二叉树的最大深度,简单题,直接深搜即可。。。 完整代码如下:

class Solution {
public:
    int dfs(TreeNode* root, int nDepth)
    {
        if (root == NULL)
            return nDepth;
        int left = dfs(root->left, nDepth + 1);
        int right = dfs(root->right, nDepth + 1);
        return left > right ? left : right;
    }
    int maxDepth(TreeNode* root) { return dfs(root, 0); }
};


本代码提交AC,用时13MS。
二刷。还可以更简洁一些:

class Solution {
public:
    int maxDepth(TreeNode* root)
    {
        if (root == NULL)
            return 0;
        if (root->left == NULL && root->right == NULL)
            return 1;
        int left_depth = maxDepth(root->left), right_depth = maxDepth(root->right);
        return max(left_depth, right_depth) + 1;
    }
};

本代码提交AC,用时6MS。

hihoCoder 1077-RMQ问题再临-线段树

hihoCoder 1077-RMQ问题再临-线段树 #1077 : RMQ问题再临-线段树 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 上回说到:小Hi给小Ho出了这样一道问题:假设整个货架上从左到右摆放了N种商品,并且依次标号为1到N,每次小Hi都给出一段区间[L, R],小Ho要做的是选出标号在这个区间内的所有商品重量最轻的一种,并且告诉小Hi这个商品的重量。但是在这个过程中,可能会因为其他人的各种行为,对某些位置上的商品的重量产生改变(如更换了其他种类的商品)。 小Ho提出了两种非常简单的方法,但是都不能完美的解决。那么这一次,面对更大的数据规模,小Ho将如何是好呢? 提示:其实只是比ST少计算了一些区间而已 输入 每个测试点(输入文件)有且仅有一组测试数据。 每组测试数据的第1行为一个整数N,意义如前文所述。 每组测试数据的第2行为N个整数,分别描述每种商品的重量,其中第i个整数表示标号为i的商品的重量weight_i。 每组测试数据的第3行为一个整数Q,表示小Hi总共询问的次数与商品的重量被更改的次数之和。 每组测试数据的第N+4~N+Q+3行,每行分别描述一次操作,每行的开头均为一个属于0或1的数字,分别表示该行描述一个询问和描述一次商品的重量的更改两种情况。对于第N+i+3行,如果该行描述一个询问,则接下来为两个整数Li, Ri,表示小Hi询问的一个区间[Li, Ri];如果该行描述一次商品的重量的更改,则接下来为两个整数Pi,Wi,表示位置编号为Pi的商品的重量变更为Wi 对于100%的数据,满足N<=10^6,Q<=10^6, 1<=Li<=Ri<=N,1<=Pi<=N, 0<weight_i, Wi<=10^4。 输出 对于每组测试数据,对于每个小Hi的询问,按照在输入中出现的顺序,各输出一行,表示查询的结果:标号在区间[Li, Ri]中的所有商品中重量最轻的商品的重量。 样例输入 10 3655 5246 8991 5933 7474 7603 6098 6654 2414 884 6 0 4 9 0 2 10 1 4 7009 0 5 6 1 3 7949 1 3 1227 样例输出 2414 884 7474


这一题是hihoCoder Problem 1070:RMQ问题再临的升级版,数据量提升到10^6,但提示还是用线段树来解决。我原本以为只要把之前的数组大小改为10^6就行了,没想到这次居然给我报CE错。 第一次遇到这种错误,看了半天没明白什么意思,后来多方查找才得知可能是数组太大了,直接编译就不通过,想想看10^6的二维数组:10^6*10^6=10^12,装的是int,则总大小为4*10^12B=3725G,这明显大大超出了内存范围,而之前的10^4二维数组只有4*10^8B=381M,按理说也超出了题目的256MB,不过还是险些AC了,但是这一次就没这么好运了,所以必须优化算法! 怎样优化内存空间呢?先把我们的线段树请出来看看: 上图是线段树的一个例子,每个节点保存了区间范围以及该区间的最小值。总的区间大小是[1,10],仔细看看这个区间树的节点个数只有19个;另外再画一个[1,6]区间上的区间树,节点个数只有11个。可以不加证明的得出一个n的区间长度的线段树的节点个数为2*n-1,这远远小于n*n,所以我们只需要O(n)的空间来存储,而不是O(n^2)。 反观之前hihoCoder Problem 1070:RMQ问题再临的解法,其实没有构造一个真正的线段树,所以浪费了很多空间,那么怎样来构造一个真正的线段树呢? 我们知道常规的树形结构是用链表来实现的,每一个节点都有指向其左右孩子节点的指针,这样就可以很容易的访问孩子节点,如果用数组的结构来表示链表的结果,是不是会简单很多呢?于是我们定义如下树的节点结构: [cpp] typedef struct node//线段树节点 { int l;//区间左端点 int r;//区间右端点 int minv;//区间最小值 }; [/cpp] 再定义一个表示树的数组node tree[2*MAX_N];很自然的tree[i]的左右孩子节点分别存储在tree[2i]和tree[2i+1],这样是不是也很容易访问孩子节点了呢。 不论是创建、查询、更新树,都是从树根开始递归往下,这个过程和之前的那个题目类似,这里就不再赘述了。完整的代码如下: [cpp] #include<iostream> #include<cstdio> using namespace std; const int MAX_N=1e6+2; int w[MAX_N]; int n,q; typedef struct node//线段树节点 { int l;//区间左端点 int r;//区间右端点 int minv;//区间最小值 }; node tree[2*MAX_N]; inline int get_min(int a,int b) { return a<b?a:b; } //创建树 void build(int l,int r,int pos) { tree[pos].l=l; tree[pos].r=r; if(l==r) tree[pos].minv=w[l]; else { int mid=(l+r)/2; build(l,mid,pos*2); build(mid+1,r,pos*2+1); tree[pos].minv=get_min(tree[pos*2].minv,tree[pos*2+1].minv); } } //查询树 int query(int l,int r,int pos) { if(l==tree[pos].l&&r==tree[pos].r) return tree[pos].minv; int mid=(tree[pos].l+tree[pos].r)/2; if(r<=mid) return query(l,r,pos*2); else if(l>mid) return query(l,r,pos*2+1); else { int left=query(l,mid,pos*2); int right=query(mid+1,r,pos*2+1); return get_min(left,right); } } //更新树 void update(int pi,int wi,int pos) { if(tree[pos].l==tree[pos].r&&tree[pos].l==pi) tree[pos].minv=wi; else { int mid=(tree[pos].l+tree[pos].r)/2; if(pi<=mid) update(pi,wi,pos*2); else update(pi,wi,pos*2+1); tree[pos].minv=get_min(tree[pos*2].minv,tree[pos*2+1].minv); } } int main() { //freopen("input.txt","r",stdin); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&w[i]); build(1,n,1); scanf("%d",&q); int p,l,r; for(int i=0;i<q;i++) { scanf("%d%d%d",&p,&l,&r); if(p==0) printf("%d\n",query(l,r,1)); else update(l,r,1); } return 0; } [/cpp] 本代码提交AC,用时697MS,内存45MB。 P.S.之前用cin、cout超时,改成scanf、printf就好了,懒得取消同步什么的,以后就打算一直用scanf、printf了。唉,从上一题到这一题,优化是无止境的啊~~]]>

hihoCoder 1070-RMQ问题再临

hihoCoder 1070-RMQ问题再临 #1070 : RMQ问题再临 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 终于,小Hi和小Ho踏上了回国的旅程。在飞机上,望着采购来的特产——小Hi陷入了沉思:还记得在上上周他们去超市的时候,前前后后挑了那么多的东西,都幸运的没有任何其他人(售货员/其他顾客)来打搅他们的采购过程。但是如果发生了这样的事情,他们的采购又会变得如何呢? 于是小Hi便向小Ho提出了这个问题:假设整个货架上从左到右摆放了N种商品,并且依次标号为1到N,每次小Hi都给出一段区间[L, R],小Ho要做的是选出标号在这个区间内的所有商品重量最轻的一种,并且告诉小Hi这个商品的重量。但是在这个过程中,可能会因为其他人的各种行为,对某些位置上的商品的重量产生改变(如更换了其他种类的商品),面对这样一个问题,小Ho又该如何解决呢? 提示:平衡乃和谐之理 输入 每个测试点(输入文件)有且仅有一组测试数据。 每组测试数据的第1行为一个整数N,意义如前文所述。 每组测试数据的第2行为N个整数,分别描述每种商品的重量,其中第i个整数表示标号为i的商品的重量weight_i。 每组测试数据的第3行为一个整数Q,表示小Hi总共询问的次数与商品的重量被更改的次数之和。 每组测试数据的第N+4~N+Q+3行,每行分别描述一次操作,每行的开头均为一个属于0或1的数字,分别表示该行描述一个询问和描述一次商品的重量的更改两种情况。对于第N+i+3行,如果该行描述一个询问,则接下来为两个整数Li, Ri,表示小Hi询问的一个区间[Li, Ri];如果该行描述一次商品的重量的更改,则接下来为两个整数Pi,Wi,表示位置编号为Pi的商品的重量变更为Wi 对于100%的数据,满足N<=10^4,Q<=10^4, 1<=Li<=Ri<=N,1<=Pi<=N, 0<weight_i, Wi<=10^4。 输出 对于每组测试数据,对于每个小Hi的询问,按照在输入中出现的顺序,各输出一行,表示查询的结果:标号在区间[Li, Ri]中的所有商品中重量最轻的商品的重量。 样例输入 10 618 5122 1923 8934 2518 6024 5406 1020 8291 2647 6 0 3 6 1 2 2009 0 2 2 0 2 10 1 1 5284 0 2 5 样例输出 1923 2009 1020 1923


本题的数据量比hihoCoder Problem 1068: RMQ-ST 算法这题要小,虽然
用O(N)的时间进行计算——扫描一遍整个区间找到最小值,然后对于每一个更改操作,使用O(1)的时间直接进行修改,这样也就是O(NQ)的总时间复杂度!在这种数据量下完全是可以通过的!
但是本题希望我们用线段树来解决。 我曾在《数据结构》的改造红黑树中看到过区间树,但是本题的线段树和书中的区间树有所区别,区间树由红黑树改造而来,结构更复杂些,这里只需要使用线段树即可。 常规的线段树如下图所示: 从图中可以看到构造线段树的过程就是一个二分的过程,不断将区间分成两半,直到只有一个元素。图中的线段树每一个节点是一个区间[l,r],本题我稍微改造了一下,改成了数组int seg_tree[left][length],比如seg_tree[i][j]表示从下标i开始,长度为j的这样一个区间上的最小值,这样就可以利用线段树来解决RMQ问题了。比如改造后的线段树就成了下面的样子: 因为树形这种特殊的结构,我们可以用一个DFS来对树实现二分构造,当DFS到某个节点长度为1时,其最小值就是w[i]本身,在回溯到父节点时,父节那个区间的最小值又是所有子节点最小值中的最小值。因为树的总节点数大约为2*n,所以复杂度O(n)。 当需要查询区间[l,r]的最小值时,只需对数组seg_tree二分搜索。具体来说,假设我们搜索到了节点[s_l,s_len],如果r<(s_l+s_len/2),说明区间[l,r]全在[s_l,s_len]的左边,我们递归在[s_l,s_len/2]区间找;如果l>=(s_l+s_len/2),说明区间[l,r]全在[s_l,s_len]的右边,我们递归在[s_l+s_len/2,s_len-s_len/2]区间找;如果以上两者都不是,说明[l,r]跨界了,而且中点下标一定是s_l+s_len/2,所以我们分别在二两半区间找,然后求这两者的最小值。复杂度O(lgn)。 当需要更新某个下标为pos的值为value时,也是DFS查找线段树,直到找到叶子seg_tree[pos][1],更新它的值,以及所有我们在查找过程经过的父节点的值。复杂度O(lgn)。 所以线段是的性质使得无论是构造、查询、更新操作,复杂度都只要O(lgn),这就是题目中所说的把总的复杂度平均分配到不同操作:平衡乃和谐之理。 完整代码如下: [cpp] #include<iostream> using namespace std; const int MAX_N=1e4+2; int w[MAX_N];//每个商品重量 int n,m; int seg_tree[MAX_N][MAX_N];//seg_tree[i][j]:起点为i,长度为j的区间的最小值 inline int get_min(int a,int b) { return a<b?a:b; } //深度优先遍历以构造线段树 void dfs(int left,int length) { if(length==1) { seg_tree[left][1]=w[left]; return; } dfs(left,length/2); dfs(left+length/2,length-length/2); seg_tree[left][length]=get_min(seg_tree[left][length/2],seg_tree[left+length/2][length-length/2]);//取最小值 } //在区间[s_left,s_len]搜索区间[left,length]的最小值 int search_min(int s_left,int s_len,int left,int length) { if((s_left==left)&&(s_len==length)) return seg_tree[s_left][s_len]; if((left+length-1)<(s_left+s_len/2))//全在左半部分 { return search_min(s_left,s_len/2,left,length); } else if(left>=(s_left+s_len/2))//全在右半部分 { return search_min(s_left+s_len/2,s_len-s_len/2,left,length); } else//左右分开搜索 { int left_len=s_left+s_len/2-left; int right_len=length-left_len; int min_left=search_min(s_left,s_len/2,left,left_len); int min_right=search_min(s_left+s_len/2,s_len-s_len/2,s_left+s_len/2,right_len); return get_min(min_left,min_right); } } //从区间[s_left,s_len]开始更新下标pos的值为value void update(int s_left,int s_len,int pos,int value) { if((s_left==pos)&&(s_len==1)) { seg_tree[s_left][1]=value; return ; } int mid=s_left+s_len/2; if(pos<mid) update(s_left,s_len/2,pos,value); else update(mid,s_len-s_len/2,pos,value); seg_tree[s_left][s_len]=get_min(seg_tree[s_left][s_len/2],seg_tree[mid][s_len-s_len/2]);//更新父节点 } int main() { //freopen("input.txt","r",stdin); cin>>n; for(int i=1;i<=n;i++) cin>>w[i]; dfs(1,n); cin>>m; int p,l,r; for(int i=0;i<m;i++) { cin>>p>>l>>r; if(p==0)//查询 { cout<<search_min(1,n,l,r-l+1)<<endl; } else//修改 { update(1,n,l,r); } } return 0; } [/cpp] 本代码提交AC,用时151MS,内存42MB。 ]]>