原题链接:
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
利用有序这个条件,可以逐行二分查找。
时间复杂度: O(nlogn) 。
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
for (int i = 0; i < (int)array.size(); i++)
if (binary_search(array[i].begin(), array[i].end(), target))
return true;
return false;
}
};
暴力时,从第一行第一列开始查找,查找时按行优先,由于数组向下和向右时递增的,故如果从第一行第一列查找如果
target
比当前元素大,那么向下走和向右走都是可以的,这样很麻烦,但是如果从左下角或右上角开始查找,问题就变得简单了,以从左下角开始查找为例,如果当前元素比target
大,那么就往上走;如果target
比当前元素小,就往右走。如果走出了这个矩阵还没有找到target
那么返回false
。这样做为什么是可行的呢?把数组想象成一个棋盘,起点是左下角,终点是
target
(棋盘中的某个位置)。 其实就是找一条最短路从起点到终点,那么判断的条件是如果当前元素比target
大,那么就往上走;如果target
比当前元素小,就往右走,可能你会想,当前元素比target
,往左走也是可以的啊,确实可以,但是你这样不是多走了一些路吗,如果最短路走不到target
,那么你往左走也是行不通的。时间复杂度: O(2n) 。
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
int n = array.size(), m = array[0].size();
int r = n - 1, c = 0;
while (r >= 0 && c < m)
if (target == array[r][c])
return true;
else if (target < array[r][c])
--r;
else
++c;
return false;
}
};
请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
求出空格数,把
str
指向的空间容量扩大到能接纳替换后的字符串容量大小,使用realloc
函数,然后从原始字符串的尾部把字符一个一个复制到扩容字符串的尾部,遇到空格就替换,只有从尾部开始才能保证数据不被覆盖。
class Solution {
public:
void replaceSpace(char *str,int length) {
int cnt = 0;
int i = length - 1;
for (int i = 0; i < length; cnt += (str[i++] == ' '));
str = (char *)realloc(str, length += 2 * cnt);
for (int j = length - 1; i >= 0; i--) {
if (str[i] != ' ')
str[j--] = str[i];
else
str[j] = '0', str[j - 1] = '2', str[j - 2] = '%', j -= 3;
}
}
};
输入一个链表,从尾到头打印链表每个节点的值。
两种做法:
- 栈;
- 递归。
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
stack<int> st;
while (head != nullptr)
st.push(head->val), head = head->next;
vector<int> ret;
while (!st.empty())
ret.push_back(st.top()), st.pop();
return ret;
}
};
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> ret;
dfs(head, ret);
return ret;
}
void dfs(ListNode *head, vector<int> &ret) {
if (head == nullptr)
return ;
dfs(head->next, ret);
ret.push_back(head->val);
}
};
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列
{1,2,4,7,3,5,6,8}
和中序遍历序列{4,7,2,1,5,3,8,6}
,则重建二叉树并返回。
知道前序和中序遍历的结果是可以唯一确定一颗二叉树的,但是知道前序和后序遍历是不可以唯一确定一颗二叉树的。
利用前序遍历第一个数字为根,用这个根在中序遍历中查找,左边的就是左子树,右边的就是右子树,算出左右子树的长度,用其长度在前序遍历中划分出左右子树,重复上述过程,就可以重建这颗二叉树了。
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
if (pre.size() == 0)
return NULL;
TreeNode *root = dfs(pre, vin, 0, pre.size(), 0, vin.size());
return root;
}
TreeNode *dfs(vector<int> &pre,vector<int> &vin, int p_l, int p_r, int v_l, int v_r) {
if (p_l == p_r)
return NULL;
TreeNode *root = new TreeNode(pre[p_l]);
int pos = -1;
for (int i = v_l; i < v_r; i++)
if (vin[i] == pre[p_l]) {
pos = i;
break;
}
root->left = dfs(pre, vin, p_l + 1, p_l + 1 + pos - v_l, v_l, pos);
root->right = dfs(pre, vin, p_l + 1 + pos - v_l, p_r, pos + 1, v_r);
return root;
}
};
用两个栈来实现一个队列,完成队列的
Push
和Pop
操作。 队列中的元素为int
类型。
用一个栈专门来完成
push
操作;用另一个栈来完成
pop
操作,如果这个栈为空,那么就把第一个栈的元素依次出栈然后入栈到该栈,由于元素在第一个栈中是先入后出,经过加粗的步骤,元素就变成了先入先出了,满足队列的性质。
class Solution
{
public:
void push(int node) {
stack1.push(node);
}
int pop() {
if (stack2.empty()) {
while (!stack1.empty())
stack2.push(stack1.top()), stack1.pop();
}
int ret = stack2.top();
stack2.pop();
return ret;
}
private:
stack<int> stack1;
stack<int> stack2;
};
那么,如何用两个队列实现一个栈呢?
- 两个队列中保持一个队列永远为空;
push
操作:把元素入不为空的那个队列;pop
操作:由于一个队列为空,于是把不为空的那个队列的元素出队到为空的这个队列,直到不为空的队列只剩一个元素为止,这个时候把这个元素弹出队列返回,这样就又保证了两个队列中保持一个队列永远为空。
class Solution
{
public:
void push(int node) {
que1.empty() ? que2.push(node) : que1.push(node);
}
int pop() {
auto &que_out = que1.empty() ? que2 : que1;
auto &que_in = que1.empty() ? que1 : que2;
while (que_out.size() > 1)
que_in.push(que_out.front()), que_out.pop();
int ret = que_out.front();
que_out.pop();
return ret;
}
private:
queue<int> que1;
queue<int> que2;
};
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组
{3,4,5,1,2}
为{1,2,3,4,5}
的一个旋转,该数组的最小值为1
。NOTE:给出的所有元素都大于
0
,若数组大小为0
,请返回0
。
假设没有非递减这个东西,很好考虑,其实有了也无所谓。
首先考虑一个这样的性质:
- 如果发生了旋转,那么第一个元素一定大于最后一个元素;
- 如果第一个元素小于最后一个元素,那么第一个元素一定就是答案;
- 旋转后,数组的前半部分是非递减的,后半部分也是递减的,故这个题要求的就是两个有序序列的分界点。
我们先考虑严格递增,这样根据上面的三条性质很好写出二分的代码:
- 如果中间元素比左端点大,那么中间元素一定在左边这个有序序列,故可以缩小左端点的范围;
- 如果中间元素比右端点小,那么中间元素一定在右边这个有序序列,故可以缩小右端点的范围;
- 直到左右端点重合,答案就是左端点的这个数。
现在我们再来考虑非递减这个条件,在二分的代码中增加一个这样判断条件
rotateArray[l] == rotateArray[r] && rotateArray[l] == rotateArray[mid]
,如果左端点等于右端点,那么只能在左端点和右端点之间顺序查找了(反正范围已经缩减到了左右端点之间),因为如果满足了上述判断,mid
这个点无法确定在左区间还是在右区间,只能顺序查找了。时间复杂度 O(logn) ,最坏情况为 O(n) 。
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
if (rotateArray.size() == 0)
return 0;
int l = 0, r = rotateArray.size() - 1;
while (l < r && rotateArray[l] >= rotateArray[r]) {
int mid = (l + r) / 2;
if (rotateArray[l] == rotateArray[r] && rotateArray[l] == rotateArray[mid]) {
int ret = rotateArray[l];
for (int i = l + 1; i <= r; ret = min(ret, rotateArray[i++]));
return ret;
}
int mid = (l + r) / 2;
if (rotateArray[mid] >= rotateArray[l])
l = mid + 1;
else
r = mid;
}
return rotateArray[l];
}
};