剑指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;
    }
};

仔细思考会发现,这里面有很多重复计算的,可以使用前缀和优化成 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);
    }
};

剑指 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);
    }
};

代码简化版,左右为负值时不如不选,就为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;
    }
};

剑指 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;
    }
};

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

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;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值