剑指Offer66题之每日6题 - 第七天

原题链接:

第一题:数字在排序数组中出现的次数

题目:

统计一个数字在排序数组中出现的次数。

解析:

这个题最简单的做法就是,两次查找,一次从前面往后找,找到第一个数字,一次从后面往前面找,找到最后一个数字,这样把下标相减,就得到答案。

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        int pre = 0, last = data.size();
        for ( ; pre != last && data[pre] != k; pre++);
        for ( ; pre != last && data[last - 1] != k; last--);
        return last - pre;
    }
};

上面这种做法的时间复杂度为 O(n) O ( n ) ,我们没有充分利用有序这个条件,有序序列的查找可以用二分来做,查找第一个数字用二分求下界;查找第二个数字用二分求上界。

这样一做,时间复杂度就是 O(logn) O ( l o g n ) 了。

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        int pre, last, l, r;
        for (l = 0, r = data.size() ;l < r; ) {
            int mid = (l + r) / 2;
            if (data[mid] >= k)
                r = mid;
            else
                l = mid + 1;
        }
        pre = r;
        for (l = -1, r = (int)data.size() - 1; l < r; ) {
            int mid = (l + r + 1) / 2;
            if (data[mid] <= k)
                l = mid;
            else
                r = mid - 1;
        }
        last = l;
        return last - pre + 1;
    }
};

关于各种二分的写法及其细节,请看这篇博客你真的理解二分的写法吗 - 二分写法详解

STL S T L 还是个好东西,里面给我们实现了这些二分查找的两个版本lower_boundupper_bound,直接调用就好了。

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        return upper_bound(data.begin(), data.end(), k) - lower_bound(data.begin(), data.end(), k);
    }
};

STL S T L 中还有equal_range这个函数,用于查找一个有序序列中值相等的一部分的范围。

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        pair<vector<int>::iterator, vector<int>::iterator> ret = 
            equal_range(data.begin(), data.end(), k);
        return ret.second - ret.first;
    }
};

第二题:二叉树的深度

题目:

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

解析:

递归很好做,取左右子树的深度中的较大值,然后加一。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/

class Solution {
public:
    int TreeDepth(TreeNode *pRoot) {
        if (pRoot == nullptr)
            return 0;
        return max(TreeDepth(pRoot->left), TreeDepth(pRoot->right)) + 1;
    }
};

非递归也好做,利用遍历二叉树的后序非递归版(具体请看二叉树前序、中序、后序遍历的非递归写法),维护栈的一个最大长度就是答案。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/

class Solution {
public:
    int TreeDepth(TreeNode* pRoot)
    {
        stack<pair<TreeNode *, int> > st;
        TreeNode *p = pRoot;
        int ret = 0;
        while (p || !st.empty()) {
            if (p) {
                st.push(make_pair(p, 1));
                p = p->left;
            } else {

                auto now = st.top();
                st.pop();
                if (now.second == 1) {
                    st.push(make_pair(now.first, 2));
                    p = now.first->right;
                } else
                    ret = max(ret, (int)st.size());
            }
        }
        return pRoot == nullptr ? 0 : ret + 1;
    }
};

第三题:平衡二叉树

题目:

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

解析:

先利用平衡二叉树的定义和上一题的代码来一发裸的递归。

class Solution {
public:
    int TreeDepth(TreeNode *pRoot) {
        if (pRoot == nullptr)
            return 0;
        return max(TreeDepth(pRoot->left), TreeDepth(pRoot->right)) + 1;
    }

    bool IsBalanced_Solution(TreeNode *pRoot) {
        if (pRoot == nullptr)
            return true;
        return IsBalanced_Solution(pRoot->left) && IsBalanced_Solution(pRoot->right) &&
            abs(TreeDepth(pRoot->left) - TreeDepth(pRoot->right)) <= 1;
    }
};

不知道你有没有发觉,在一次递归中,会计算树高,然后判断子树的时候又计算了一次树高,其实这是重复的操作,知道了父亲的高度,就知道了儿子的高度,换句话说,知道了儿子的高度也就知道了父亲的高度。

于是,在一次递归中计算了树高,然后在回溯到父亲的的时候就把这个高度传递给父亲,这样父亲的高度就可以不用重复计算就得知了。

class Solution {
public:
    bool IsBalanced_Solution(TreeNode *pRoot) {
        int depth;
        return dfs(pRoot, &depth);
    }

    bool dfs(TreeNode *pRoot, int *pd) {
        if (pRoot == nullptr)
            return *pd = 0, true;
        int d_left, d_right;
        if (dfs(pRoot->left, &d_left) && dfs(pRoot->right, &d_right) &&
             abs(d_left - d_right) <= 1)
            return *pd = max(d_left, d_right) + 1, true;
        return false;
    }
};

第四题:数组中只出现一次的数字

题目:

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

解析:

这其实是一个很经典的题了,就是利用两个相同的数异或结果为0这一性质来做的。

这个题简化版是这样的: 一个整型数组里除了一个数字之外,其他的数字都出现了两次。请写程序找出这一个只出现一次的数字。

做法就是把所有的数字异或起来,得到的结果就是答案,为什么呢?有以下几点可以讲:

  • 两个相同的数字异或和为0;
  • 0异或任何一个数字任为原数字,也就是说0是保位的
  • 这个数组中只有一个数字出现了一次,其余的数字都出现了两次,那么那些出了两次的数字异或起来就是0,最后就剩下这一个数字了;
  • 异或运算具有结合律、交换律。

那这个题中有两个数字都只出现了一次,如果按照上面这种做法,异或出来的就是这两个只出现一次的数字的异或和了;

怎么办,要是我们可以把这两个数字”分开“,拆成两个这个问题的简化版就好了!

问题是怎么拆分,这里要保证两点:

  • 这两个数字必须在不同的组中;
  • 出现了两次的数字必须在同一组中。

这里我们就把目光从十进制转到二进制去,我们考虑这两个不同数字的异或和的二进制。这个二进制中肯定有一些位是0,其余的位是1,还记得上面说的吗?0异或任何一个数字任为原数字,也就是说0是保位的。这就意味着二进制中为1的那些位在这两个只出现一次的数字之中是不一样的,因此我们就按这一点进行划分组,这样会保证上面说的第一点;

第二点我这里要解释下,由于某一位二进制来划分组,那么两个相同的数字这一位二进制必然一样,故一定可以分到同一个组;

那么最后一个问题,取哪一个二进制为1的位 ?这里根据我的这篇博客树状数组简单易懂的详解中的lowbit函数,可以得知,取最低一位的1是最好求的(其实你取哪一位1都无所谓),所以我们这里直接用lowbit

最后就是划分组的技巧了,这里我直接利用头尾指针在原数组上进行划分,头尾指针这也是个常见的技巧了。

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        int eo = 0;
        for (auto it = data.begin(); it != data.end(); eo ^= *it++);
        int pos = eo & -eo, cnt = 0;
        for (int head = 0, tail = (int)data.size() - 1; head < tail; ) {
            for (; head < tail && (data[head] & pos) == pos ; ++head);
            for (; head < tail && (data[tail] & pos) == 0 ; --tail);
            head < tail ? (swap(data[head++], data[tail--]), 0) : 0;
        }
        for (auto it = data.begin(); it != data.end(); cnt += ((*it++ & pos) == pos));
        for (*num1 = eo = 0; eo < cnt; *num1 ^= data[eo++]);
        for (*num2 = 0; eo < (int)data.size(); *num2 ^= data[eo++]);
    }
};

第五题:和为S的连续正数序列

题目:

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序。

解析:

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        // 2 * n * a + n * (n - 1) = 2 * s = n * (n + 2a - 1);
        vector<vector<int> > ret;
        int top = sqrt(2 * sum);
        for (int n = top, a; n > 1; n--)
            if ((a = (2 * sum / n - n + 1) / 2) > 0 && 2 * sum == n * (n + 2 * a - 1)) {
                vector<int> arr;
                for (int i = a; i < a + n; arr.push_back(i++));
                ret.push_back(arr);
            }
        return ret;
    }
};
class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int> > ret;
        for (int l = 1, r = 2 ; (l + l + 1) <= sum; ) {
            if ((l + r) * (r - l + 1) / 2 == sum) {
                vector<int> arr;
                for (int i = l; i <= r; arr.push_back(i++));
                ret.push_back(arr);
                ++r;
            } else
                (l + r) * (r - l + 1) / 2 > sum ? ++l : ++r;
        }
        return ret;
    }
};

第六题:和为S的两个数字

题目:

输入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

对应每个测试案例,输出两个数,小的先输出。

解析:

关于这个题的详细解法及扩展问题请看这篇博客

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        vector<int> ret;
        int head, tail;
        for (head = 0, tail = (int)array.size() - 1;
            head < tail && array[head] + array[tail] != sum;
            array[head] + array[tail] < sum ? ++head : --tail) {}
        if (head < tail)
            ret.push_back(array[head]),ret.push_back(array[tail]);
        return ret;
    }
};
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值