剑指Offer66题之1-6
第一题: 二维数组中的查找
题目
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解析
根据有序这个条件,可以逐行二分查找。时间复杂度为
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比当前位置数值大,则往右查找。时间复杂度为
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
// n 为行数, m为列数
int n = array.size(), m = array[0].size();
// 初始位置,左下角(r,c)
int r = n - 1, c = 0;
// 当查询位置仍在数组范围内,即r>=0 && c<m
while (r >= 0 && c < m)
// 找到target
if (target == array[r][c])
return true;
// target小于当前位置数值,往上
else if (target < array[r][c])
--r;
// 往右
else
++c;
return false; //没有查到
}
};
备注
C++ STL 的二分查找函数:
binary_search(arr[],arr[]+size,index)
,arr[]是起始位置,arr[]+size是结束位置,index是查找对象
第二题 : 替换空格
题目
请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
解析
1)求出空格数,2) 把
str
指向的空间容量扩大到能接纳替换后的字符串容量大小, 使用realloc
函数,3) 然后从原始字符串的尾部把字符一个一个复制到扩容字符串的尾部,遇到空格就替换,只有从尾部开始才能保证数据不被覆盖。
class Solution{
public:
void replaceSpace(char* str, int length){
int cnt = 0; // 空格数
int i = lenght -1;
// 计算空格数
for (int i = 0; i < length; cnt += (str[i++] == ' '));
// 扩大str的内存空间,length变成新str的长度
str = (char *)realloc(str, lenght += 2 * cnt);
// 从后往前复制
// j是新str长度-1,i是原str长度-1
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;
}
}
}
备注
void *realloc (void *ptr, size_t new_size )
修改一个原先已经分配的内存块的大小,可以使一块内存的扩大或缩小。当起始空间的地址为空,即ptr = NULL,则同malloc。当ptr非空:若nuw_size < size,即缩小ptr所指向的内存空间,该内存块尾部的部分内存被拿掉,剩余部分内存的原先内容依然保留;若nuw_size > size,即扩大ptr所指向的内存空间,如果原先的内存尾部有足够的扩大空间,则直接在原先的内存块尾部新增内存,如果原先的内存尾部空间不足,或原先的内存块无法改变大小,realloc将重新分配另一块nuw_size大小的内存,并把原先那块内存的内容复制到新的内存块上。因此,使用realloc后就应该改用realloc返回的新指针
第三题 : 从尾到头打印链表
题目:
输入一个链表,从尾到头打印链表每个节点的值。
解析:
两种做法:
- 栈, 将链表存入栈内,再依次从栈内取出,Fist in last out
- 递归
/**
* 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;
// 将stack中元素依次取出,存入vector
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);
}
};
备注
stack , queue : push
list, vector : push_back
第四题 : 重建二叉树
题目
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列
{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){
// 如果为空,返回NULL
if (pre.size() == 0)
return NULL;
// 递归函数dfs
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]);
// pos: 根节点在vin中位置
int pos = -1;
// 计算pos
for (int i = v_l; i < v_r; i++)
if (vin[i] == pre[p_l]) {
pos = i;
break;
}
// 划分左右子树,调用递归函数;其中,pos-v_l 为左子树长度
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(){
// 如果stack2为空,将stack1中元素全部放入stack2
if (stack2.empty()){
while(!stack1.empty()){
stack2.push(stack1.top()), stack1.pop()
}
}
// 输出stack2的top
int ret = stack2.top();
stack2.pop();
return ret;
}
private:
stack<int> stack1;
stack<int> stack2;
}
备注
栈 (stack) : 先进后出
队列 (queue): 先进先出
第六题: 旋转数组的最小数字
题目
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
解析
因为是非递减,所以采用二分法思想
三条性质:
- 如果中间元素比左端点大,则中间元素位于左边的有序序列,左端点可以右移
- 如果中间元素比右断点小,则中间元素位于右边的有序序列,右端点可以左移
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];
}
};