【剑指Offer】搜索与回溯算法(5)

剑指 Offer 64. 求1+2+…+n

1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

示例 1:

输入: n = 3
输出: 6

示例 2:

输入: n = 9
输出: 45
class Solution {
public:
    int sumNums(int n) {
        int ans = 0;
        n != 0 && (ans = sumNums(n - 1) + n);
        return ans;
    }
};

算法思路

这题求连加,可以用递归,但考虑到递归出口要判断但又不能用if,这里的解决方法是用到了逻辑与的短路运算,就是前半部分语句为假时不执行后半段语句,这就形成了一个递归出口的判断。

题目一结果

限制:

  • 1 <= n <= 10000
剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]

img

示例 1:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6。

示例 2:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉搜索树中。
/**
 * 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:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == nullptr || root == p || root == q){
            return root;
        }
        TreeNode* l = lowestCommonAncestor(root->left, p, q);
        TreeNode* r = lowestCommonAncestor(root->right, p, q);
        return l == nullptr? r : (r == nullptr ? l : root);
    }
};

算法思路

判断一个点是不是两个节点的第一个共同先,有一个特点就是两个节点分别在两棵子树中,而且非第一个祖先结点的其它祖先结点,两个目标结点其实只分布在它的一边子树,因此可以从上往下遍历,回溯的过程中判断是不是第一个祖先结点就可以得到答案。

题目二结果

剑指 Offer 68 - II. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]

img

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉树中。
/**
 * 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:
    unordered_map<TreeNode*, int> lev;
    unordered_map<TreeNode*, map<int, TreeNode*>> st;
    void dfs(TreeNode *root, TreeNode *pre){
        if(root == nullptr)return;
        lev[root] = lev[pre] + 1;
        st[root][0] = pre;
        for(int i = 1; i < 50; ++ i){
            st[root][i] = st[st[root][i - 1]][i - 1];
        }
        dfs(root->left, root);
        dfs(root->right, root);
    }
    TreeNode* lca(TreeNode *p, TreeNode *q){
        if(lev[p] < lev[q]){
            swap(p, q);
        }
        while(lev[p] > lev[q]){
            p = st[p][(int)log2(lev[p] - lev[q])];
        }
        if(p == q){
            return p;
        }
        for(int i = 49; i >= 0; -- i){
            if(st[p][i] != st[q][i]){
                p = st[p][i];
                q = st[q][i];
            }
        }
        return st[p][0];
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        dfs(root, nullptr);
        return lca(p, q);
    }
};

算法思路

这一题貌似和第二题一样,但我测了一下好像是数据规模不同,这一题规模比较大,上面的那种解法算是解决这个问题的最优方法了,但我在第三种用了一种比较特殊的算法"倍增"。很经典的一个例子比如你要到28米的地方,你可以走1米、2米、3米…28米直到28米,但实际上你只需要走1米、2米、4米、8米、16米就可以到达28,比如27=16+8+2+1,21=16+4+1…28以内的数都可以由面前五个数(1,2,4,8,16)组成。在这题中两个结点,我们可以先让低层结点往上走直到两个结点同一层,如果次数两个结点相同,则该结点就是结果;否则两结点同时向上走,直到第一次相遇,就是结果。如何向上走,走的步数多少就用到了倍增。代码还是比较麻烦的,也不是最优的方法(甚至可以算差),主要还是了解一下倍增这个算法,这个算法对其它问题还是很高效的。

题目三结果

【剑指Offer】系列:
【剑指Offer】栈
【剑指Offer】链表
【剑指Offer】字符串
【剑指Offer】查找算法
【剑指Offer】查找算法(1)
【剑指Offer】搜索与回溯算法
【剑指Offer】搜索与回溯算法(1)
【剑指Offer】动态规划
【剑指Offer】动态规划(1)
【剑指Offer】动态规划(2)
【剑指Offer】双指针
【剑指Offer】双指针(1)
【剑指Offer】双指针(2)
【剑指Offer】搜索与回溯算法(2)
【剑指Offer】搜素与回溯算法(3)
【剑指Offer】排序
【剑指Offer】排序(1)
【剑指Offer】搜索与回溯算法(4)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值