leetcode

Posted 旧时星空

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode相关的知识,希望对你有一定的参考价值。

leetcode

根据前序和后续遍历构造二叉树–中等

递归

前序遍历为:

(根结点) (前序遍历左分支) (前序遍历右分支)
而后序遍历为:

(后序遍历左分支) (后序遍历右分支) (根结点)
例如,如果最终的二叉树可以被序列化的表述为 [1, 2, 3, 4, 5, 6, 7],那么其前序遍历为 [1] + [2, 4, 5] + [3, 6, 7],而后序遍历为 [4, 5, 2] + [6, 7, 3] + [1].

如果我们知道左分支有多少个结点,我们就可以对这些数组进行分组,并用递归生成树的每个分支。

算法

我们令左分支有 LL 个节点。我们知道左分支的头节点为 pre[1],但它也出现在左分支的后序表示的最后。所以 pre[1] = post[L-1](因为结点的值具有唯一性),因此 L = post.indexOf(pre[1]) + 1。

现在在我们的递归步骤中,左分支由 pre[1 : L+1] 和 post[0 : L] 重新分支,而右分支将由 pre[L+1 : N] 和 post[L : N-1] 重新分支。

class Solution 
    public TreeNode constructFromPrePost(int[] pre, int[] post) 
        int N = pre.length;
        if (N == 0) return null;
        TreeNode root = new TreeNode(pre[0]);
        if (N == 1) return root;

        int L = 0;
        for (int i = 0; i < N; ++i)
            if (post[i] == pre[1])
                L = i+1;

        root.left = constructFromPrePost(Arrays.copyOfRange(pre, 1, L+1),
                                         Arrays.copyOfRange(post, 0, L));
        root.right = constructFromPrePost(Arrays.copyOfRange(pre, L+1, N),
                                          Arrays.copyOfRange(post, L, N-1));
        return root;
    

礼物的最大价值–中等

动态规划

题目说明:从棋盘的左上角开始拿格子里的礼物,并每次 向右 或者 向下 移动一格、直到到达棋盘的右下角。
根据题目说明,易得某单元格只可能从上边单元格或左边单元格到达。

设 f(i, j)f(i,j) 为从棋盘左上角走至单元格 (i ,j)(i,j) 的礼物最大累计价值,易得到以下递推关系:f(i,j)f(i,j) 等于 f(i,j-1)f(i,j−1) 和 f(i-1,j)f(i−1,j) 中的较大值加上当前单元格礼物价值 grid(i,j)grid(i,j) 。

f(i,j) = \\max[f(i,j-1), f(i-1,j)] + grid(i,j)
f(i,j)=max[f(i,j−1),f(i−1,j)]+grid(i,j)

因此,可用动态规划解决此问题,以上公式便为转移方程。

由于 dp[i][j]dp[i][j] 只与 dp[i-1][j]dp[i−1][j] , dp[i][j-1]dp[i][j−1] , grid[i][j]grid[i][j] 有关系,因此可以将原矩阵 gridgrid 用作 dpdp 矩阵,即直接在 gridgrid 上修改即可。
应用此方法可省去 dpdp 矩阵使用的额外空间,因此空间复杂度从 O(MN)O(MN) 降至 O(1)O(1)

class Solution 
    public int maxValue(int[][] grid) 
        int m = grid.length, n = grid[0].length;
        for(int i = 0; i < m; i++) 
            for(int j = 0; j < n; j++) 
                if(i == 0 && j == 0) continue;
                if(i == 0) grid[i][j] += grid[i][j - 1] ;
                else if(j == 0) grid[i][j] += grid[i - 1][j];
                else grid[i][j] += Math.max(grid[i][j - 1], grid[i - 1][j]);
            
        
        return grid[m - 1][n - 1];
    

N叉树的后序遍历–简单

递归

class Solution 
public:
    void helper(const Node* root, vector<int> & res) 
        if (root == nullptr) 
            return;
        
        for (auto & ch : root->children) 
            helper(ch, res);
        
        res.emplace_back(root->val);
    

    vector<int> postorder(Node* root) 
        vector<int> res;
        helper(root, res);
        return res;
    
;

验证回文串–简单

使用双指针

我们直接在原字符串 ss 上使用双指针。在移动任意一个指针时,需要不断地向另一指针的方向移动,直到遇到一个字母或数字字符,或者两指针重合为止。也就是说,我们每次将指针移到下一个字母字符或数字字符,再判断这两个指针指向的字符是否相同。

class Solution 
public:
    bool isPalindrome(string s) 
        int n = s.size();
        int left = 0, right = n - 1;
        while (left < right) 
            while (left < right && !isalnum(s[left])) 
                ++left;
            
            while (left < right && !isalnum(s[right])) 
                --right;
            
            if (left < right) 
                if (tolower(s[left]) != tolower(s[right])) 
                    return false;
                
                ++left;
                --right;
            
        
        return true;
    
;

二叉树的后序遍历–简单

Morris遍历

有一种巧妙的方法可以在线性时间内,只占用常数空间来实现后序遍历。这种方法由 J. H. Morris 在 1979 年的论文「Traversing Binary Trees Simply and Cheaply」中首次提出,因此被称为 Morris 遍历。

Morris 遍历的核心思想是利用树的大量空闲指针,实现空间开销的极限缩减。其后序遍历规则总结如下:

新建临时节点,令该节点为 root;

如果当前节点的左子节点为空,则遍历当前节点的右子节点;

如果当前节点的左子节点不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点;

如果前驱节点的右子节点为空,将前驱节点的右子节点设置为当前节点,当前节点更新为当前节点的左子节点。

如果前驱节点的右子节点为当前节点,将它的右子节点重新设为空。倒序输出从当前节点的左子节点到该前驱节点这条路径上的所有节点。当前节点更新为当前节点的右子节点。

重复步骤 2 和步骤 3,直到遍历结束。

这样我们利用 Morris 遍历的方法,后序遍历该二叉搜索树,即可实现线性时间与常数空间的遍历。

class Solution 
public:
    void addPath(vector<int> &vec, TreeNode *node) 
        int count = 0;
        while (node != nullptr) 
            ++count;
            vec.emplace_back(node->val);
            node = node->right;
        
        reverse(vec.end() - count, vec.end());
    

    vector<int> postorderTraversal(TreeNode *root) 
        vector<int> res;
        if (root == nullptr) 
            return res;
        

        TreeNode *p1 = root, *p2 = nullptr;

        while (p1 != nullptr) 
            p2 = p1->left;
            if (p2 != nullptr) 
                while (p2->right != nullptr && p2->right != p1) 
                    p2 = p2->right;
                
                if (p2->right == nullptr) 
                    p2->right = p1;
                    p1 = p1->left;
                    continue;
                 else 
                    p2->right = nullptr;
                    addPath(res, p1->left);
                
            
            p1 = p1->right;
        
        addPath(res, root);
        return res;
    
;

以上是关于leetcode的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode-删除中间节点

[leetcode] 75. 分类颜色(常数空间且只扫描一次算法)

Leetcode 380. 常数时间插入删除和获取随机元素

LeetCode 0123.买卖股票的最佳时机III:常数空间下的动态规划+模拟

[leetcode]380. Insert Delete GetRandom O常数时间插入删除取随机值

LeetCode 380. Insert Delete GetRandom O (插入删除和获得随机数 常数时间)