• 剑指offer专项突击版第17天


    剑指 Offer II 050. 向下的路径节点之和

    容易想到 O ( N 2 ) O(N^2) O(N2)的暴力遍历算法,本质上跟for双重循环暴力遍历很类似,就是实现代码需要的技巧较多也比较新颖。

    class Solution {
    public:
    	//保证root下每种路径中的每个结点都会选取,不会有路径中间存在没选取的结点
        int dfs(TreeNode *root, int targetSum) { 
            if(!root) return 0;
            int cnt = 0;
            if(root->val == targetSum) cnt++;
            cnt += dfs(root->left, targetSum - root->val);
            cnt += dfs(root->right, targetSum - root->val);
            return cnt;
        }
    
        int pathSum(TreeNode* root, int targetSum) {
            if(!root) return 0;
            //选取root点
            int ans = dfs(root,targetSum);
            //不选root点  
            ans += pathSum(root->left, targetSum);
            ans += pathSum(root->right, targetSum);
            return ans;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    仔细思考会发现,这里面有很多重复计算的,可以使用前缀和优化成 O ( N ) O(N) O(N) 时间复杂度,其中前缀和值放在map的键上,值则为个数,这样就不用重新遍历一遍之前的前缀和,严谨的来讲因为用到map就导致算法复杂度为 O ( N ∗ l o g N ) O(N*logN) O(NlogN)

    class Solution {
    private:
        unordered_map<long long, int> prefix;
        // int ans = 0;    
    public:
        int dfs(TreeNode *root, long long sum, int targetSum) {
            if(!root) return 0;
            sum += root->val;
            int ans = 0;
            if(prefix.count(sum-targetSum)) { //sum - x = targetSum,其中x就是前面某个前缀和
                ans += prefix[sum-targetSum];
            }
            prefix[sum]++;
            ans += dfs(root->left, sum, targetSum);
            ans += dfs(root->right, sum, targetSum);
            prefix[sum]--; //回溯,不然会用到其他分支的结点之和
            return ans;
        }
    
        int pathSum(TreeNode* root, int targetSum) {
            prefix[0] = 1; //什么都没选,当某条从root开始的路径结点之和等于targetSum时会用到
            return dfs(root, 0, targetSum);
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    剑指 Offer II 051. 节点之和最大的路径

    使用到后序遍历+DP思想

    • 后序遍历是先求左右子路径的最大值
    • DP思想是当前栈帧会计算当前结点+左子路径+右子路径(这个不能回溯给父节点的,因为会使得路径不是线性的),不管你要回溯给父节点当前结点、当前+左子路径、当前+右子路径,最终得到的是全局最大的路径和。
    class Solution {
    private:
        int res = INT_MIN;    
    public:
        int maxPathSum(TreeNode* root) {
            maxGain(root);
            return res;
        }
    
        int maxGain(TreeNode *root) {
            if(root == nullptr) return 0;
            int left = maxGain(root->left);
            int right = maxGain(root->right);
            res = max(res, root->val); //考虑左右路径都为负值
            res = max(res, root->val + left);
            res = max(res, root->val + right);
            res = max(res, root->val + left + right);
            //回溯只能返回当结点值 或 当前+左 或 当前+右
            return max(max(root->val,root->val+left), root->val+right);
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    代码简化版,左右为负值时不如不选,就为0

    class Solution {
    private:
        int res = INT_MIN;    
    public:
        int maxPathSum(TreeNode* root) {
            maxGain(root);
            return res;
        }
    
        int maxGain(TreeNode *root) {
            if(root == nullptr) return 0;
            //左右为负值时不如不选,就为0
            int left = max(0,maxGain(root->left)); 
            int right = max(0,maxGain(root->right));
            res = max(res, root->val + left + right);
            //回溯只能返回当结点值 或 当前+左 或 当前+右
            return max(left,right) + root->val;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    剑指 Offer II 052. 展平二叉搜索树

    最容易想到的方法,先存下中序遍历序列,然后按照序列顺序指定每个结点的右结点,这样就等同于创建一个链表

    class Solution {
    public:
        void inorder(vector<TreeNode*> &ls, TreeNode *root) {
            if(!root) return;
            inorder(ls,root->left);
            ls.emplace_back(root);
            inorder(ls,root->right);
        }   
        TreeNode* increasingBST(TreeNode* root) {
            vector<TreeNode*> ls;
            inorder(ls,root);
            auto head = new TreeNode(-1);
            auto tmp = head;
            for(auto cur: ls) {
                cur->left = cur->right = nullptr;
                tmp->right = cur;
                tmp = cur;
            }
            return head->right;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    来一个难一些的方法,一边遍历一边改变结点指针方向。抓住中序遍历算法->遍历顺序不变的特性。然后用一个移动指针跟着遍历顺序移动。(有个坑,移动指针不能放函数参数里,必须全局变量,因为递归回溯过程中,移动指针还是原来的并没有改变)

    class Solution {
    private:
        TreeNode *cur = nullptr;    
    public:
        void inorder(TreeNode *root) {
            if(!root) return;
            inorder(root->left);
            root->left = nullptr;
            cur->right = root;
            cur = cur->right;
            inorder(root->right);
        }   
        TreeNode* increasingBST(TreeNode* root) {
            auto head = new TreeNode(-1);
            cur = head;
            inorder(root);
            return head->right;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 相关阅读:
    如何在springboot项目中删除引用jar包中的无用bean
    使用U3D、pico开发VR(一)——将unity的场景在设备中呈现
    学习gerrit笔记
    Day59——Ajax,前后端数据编码格式
    java从入门到进阶
    06-图2 Saving James Bond - Easy Version
    静态分析 Qt Ceator 组织的工程代码
    【MySQL从入门到精通】【高级篇】(六)MySQL表的存储引擎,InnoDB与MyISAM的对比
    Vue和SpringBoot打造中学生家校互联系统
    php伪协议 [ACTF2020 新生赛]Include1
  • 原文地址:https://blog.csdn.net/hys__handsome/article/details/126112978