原题链接:
- 第一题:数字在排序数组中出现的次数;
- 第二题:二叉树的深度;
- 第三题:平衡二叉树;
- 第四题:数组中只出现一次的数字;
- 第五题:和为S的连续正数序列;
- 第六题:和为S的两个数字;
第一题:数字在排序数组中出现的次数
题目:
统计一个数字在排序数组中出现的次数。
解析:
这个题最简单的做法就是,两次查找,一次从前面往后找,找到第一个数字,一次从后面往前面找,找到最后一个数字,这样把下标相减,就得到答案。
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_bound
,upper_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;
}
};