惭愧自己断断续续用了一个月才刷完了剑指offer的66题,发现很多题的思路自己已经遗忘,这里就再整理一次。
主要还是以理清算法思路为主,部分题进行适当的补充。感谢牛客网提供了良好的刷题环境和讨论氛围。
第一题 二维数组中查找
题目描述:
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解题思路:
我习惯从右上角开始,当待查找的数target等于右上角的数 array[i][j]时,那么存在返回true,
当右上角的数 array[i][j]>target
时,那么至少可以肯定这一列是没有的,因为它下方的数都比它大,所以列数少1,
当右上角的数 array[i][j]<target
时,那么可以肯定这一行是没有的,因为它左边的数都比它小,所以行数加1。
右上角的数移动出了数组的范围就返回false。
class Solution {
public:
bool Find(vector<vector<int> > array,int target) {
int row = array.size();
if(row==0)
return false;
int col = array[0].size();
if(col==0)
return false;
int i = 0;
int j = col-1;
while(i<row&&j>=0){
if(array[i][j] == target)
return true;
if(target<array[i][j])
j--;
else
i++;
}
return false;
}
};
第二题 替换空格
题目描述:
请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
解体思路:
如果我重新分配空间的话,那么显然是一次遍历就能解决问题。
这里就是假设不能重新分配空间,那么我先用一次遍历统计出有多少个空格。
然后计算新的字符串的长度应该是多少。如果这个长度大于字符串原始分配的空间了,那么显然是不能进行替换的,这里是直接返回,实际上最好能直接抛出异常。
复制从前往后是不行的,因为我要同时保留原来的字符串,非常聪明的做法就是从后往前复制。我不想每一次我进行了替换还要把原来的序列向后移动。
这里还要计算加上'\0'
的长度,并且在复制的时候从字符串结束标记开始复制(我尝试过如果不加上字符串结束标志,在vs2010下仍然可以编译通过,有点诡异)
class Solution {
public:
void replaceSpace(char *str,int length) {
if(!str)
return;
int count =0;
char *p = str;
int oriLength=0;
while(*p){
if(*p==' ')
count++;
p++;
oriLength++;
}
int newLength = oriLength+2*count+1;
if(newLength> length)
return ;
int p1 = oriLength+1;
int p2 = newLength;
while(p1<p2){
if(str[p1]!=' ')
str[p2--]=str[p1--];
else{
str[p2--]='0';
str[p2--]='2';
str[p2--]='%';
p1--;
}
}
return ;
}
};
第三题:从尾到头打印链表
题目描述:
输入一个链表,从尾到头打印链表每个节点的值。
解题思路:
从尾到头容易让人联想到后进先出,自然就是栈的结构,所以很自然的第一种思路会想到用栈。
当下一个节点不为NULL时,我都把它放到栈里去,一直到栈顶了,我开始依次pop输出栈顶元素。
另一种会让人想到递归本身就是一种栈的结构,所以可以依次递归调用函数本身直到链表末尾,这时递归返回输出的时候正好可以形成逆序。
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(struct ListNode* head) {
/*
vector<int> result;
if(!head){
if(NULL!=head->next)
result = printListFromTailToHead(head->next);
result.push_back(head->val);
}
return result;*/
std::stack<ListNode*> nodes;
struct ListNode* p = head;
while(p!=NULL){
nodes.push(p);
p = p->next;
}
vector<int> result;
while(!nodes.empty()){
p = nodes.top();
result.push_back(p->val);
nodes.pop();
}
return result;
}
};
第四题:重建二叉树
题目描述:
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{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:
struct TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> in) {
if(pre.empty())
return NULL;
int val = pre[0];
TreeNode * root = new TreeNode(val);
//递归退出的条件
if(pre.size()==1){
root->val = val;
root->left = NULL;
root->right = NULL;
return root;
}
//在中序遍历中找到根节点的值
vector<int>::iterator itr = in.begin();
while( itr!=in.end()){
if( (*itr) == val)
break;
itr++;
}
//得到左子树的长度
//这种写法是比较简单,但是多了很多内存复制,其实我完全可以再写一个coreFunction,只传下标就可以了
int len = itr - in.begin();
vector<int> pre1(pre.begin()+1,pre.begin()+1+len);
vector<int> in1(in.begin(),in.begin()+len);
vector<int> pre2(pre.begin()+len+1,pre.end());
vector<int> in2(in.begin()+len+1,in.end());
root->left = reConstructBinaryTree(pre1,in1);
root->right = reConstructBinaryTree(pre2,in2);
return root;
}
};
第五题:用两个栈实现
题目描述:
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
解题思路:
栈是先进后出,把栈的元素依次放到另一个栈里,就可以实现先进先出。
push操作
压栈压到stack1中。
pop操作
由于我们用stack2作为中转,依次首先返回stack2的栈顶,如果stack2已经空了,那么再把stack1中的元素依次弹出放到stack2中,原stack1栈底的元素肯定是最先进去的,放到stack2中就成为栈顶了,此时返回栈顶元素就可以了。
class Solution
{
public:
void push(int node) {
stack1.push(node);
}
int pop() {
if(!stack2.empty()){
int val =stack2.top();
stack2.pop();
return val;
}
while(!stack1.empty()){
int val = stack1.top();
stack1.pop();
stack2.push(val);
}
int val = stack2.top();
stack2.pop();
return val;
}
private:
stack<int> stack1;
stack<int> stack2;
};
第六题:旋转数组的最小数字
题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减序列的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
解题思路:
直接遍历肯定是最简单的,但是达不到要求。
所以可以考虑利用旋转数组的部分有序的特点,可以考虑用两个指针分别指向数组的第一个元素和最后一个元素,然后可以找到数组中间的元素。
- 当第一个元素比最后一个元素小的时候,由于非递减的特性,显然只需要返回第一个元素就可以了。
- 如果该中间元素位于前面递增子数组,它将大于等于第一个元素,那么最小元素在中间元素的右侧,移动首元素到中间元素的位置。
比如 2 3 4 5 1 - 如果中间元素小于等于最后一个元素,也就是说,最小的元素其实位于中间元素的左侧。移动尾元素到中间元素的位置。
比如 5 1 2 3 4 - 存在循环移位时,当首元素和尾元素相差1时,返回尾元素的值就是最小值,因为数据首元素的移动方向是从小到大的,尾元素的移动的方向是从大到小的。
- 如果第一个元素和最后一个元素相同,也和中间的元素相同,此时是无法判断的。
比如 1 0 1 1 1 和 1 1 1 0 1,此时必须采用顺序查找的方法
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
vector<int>& a = rotateArray;
if(rotateArray.empty())
return 0;
int len = rotateArray.size();
if(len == 1)
return rotateArray[0];
int minIndex = 0;
int bigIndex = len - 1;
while(a[minIndex] >= a[bigIndex]){
//首尾相差1的时候返回尾元素所指的元素值
if(bigIndex-minIndex==1)
return a[bigIndex];
int mid = minIndex + ((bigIndex-minIndex)>>1);
// 顺序查找
if(a[minIndex]==a[bigIndex]&&a[minIndex]==a[mid])
{
int mi = a[0];
for(int j=1;j<len;j++)
if(a[j]<mi)
mi =a[j];
return mi;
}
if(a[minIndex]>=a[mid])
bigIndex = mid;
if(a[bigIndex]<=a[mid])
minIndex = mid;
}
//如果首元素的值小于尾元素,那么可以直接返回首元素的值
return a[minIndex];
}
};
第七题:斐波那契数列
题目描述
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。
解题思路
如果使用递归,会存在严重的效率问题,因为会有很多重复计算的值,重复的结点会随着n的增加而急剧增加,所以最简单的做法是从下往上计算。还有一种时间复杂度为
O(logn)
的做法,就是快速幂算法。首先存在一个数学公式:
所以我们只要求得矩阵 [1110]n−1 就能得到 f(n) ,如果只是单纯从0开始循环,那么n次方需要n次运算,其时间复杂度仍然为 O(n) ,并不比以前的快,所以可以考虑乘方的性质,求n次方,先求n/2次方,再对它进行平方即可。
同时,一个数n=5可以写成2进制(101),我们最后要计算的时候,就是1*4+0*2+1*1。
按照这种思路,底数是aj,计算它的 an=a(n)2 将系数展开成二进制形式,问题就变成了:
ab1∗k1+b2∗k12+b3∗(k12)2+...=ab1∗k1⋅ab2∗k12⋅ab3∗k14+...
由于 bi 仅仅可以是1或0,相当于乘以一个矩阵 aki 或者乘以一个单位矩阵。
另外 ak1=I 也就是当 k1=0 (对应着二进制最低位的权重)是一个单位矩阵,于是可以得到下面的 快速幂算法。
class Solution {
public:
int Fibonacci(int n) {
int num = n;
//int len = floor(log2(n))+1; // 转化为2进制是多少位数
int aj[4]={1,1,1,0};
int sum[4]={1,0,1,0};
int a1,b1,c1,d1;
while(num){
int k = num&1;
if(k){
a1 = sum[0]*aj[0]+sum[1]*aj[2];
b1 = sum[0]*aj[1]+sum[1]*aj[3];
c1 = sum[0]*aj[2]+sum[3]*aj[2];
d1 = sum[1]*aj[2]+sum[3]*aj[3];
sum[0] = a1;sum[1]=b1;sum[2]=c1;sum[3]=d1;
}
// a^{2^j} = a^{2^{j-1}}* a^{2^{j-1}}
a1 = aj[0]*aj[0]+aj[1]*aj[2];
b1 = aj[0]*aj[1]+aj[1]*aj[3];
c1 = aj[0]*aj[2]+aj[3]*aj[2];
d1 = aj[1]*aj[2]+aj[3]*aj[3];
aj[0] = a1; aj[1]=b1;aj[2]=c1;aj[3]=d1;
num=num>>1;
}
return sum[1];
}
};
备注:这里实际求取的是n次方,所以左上角对应着
f(n+1)
, 右上角对应着
f(n)
如果前面写成:
if(n<1)
return 0;
int num = n-1;
那么后面可以返回 sum[0]
第八题: 跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
解题思路
这是斐波拉契数列的应用。讨论当n>2 时,就看最后一步是1级还是两级,如果是1级,前面有f(n-1)种做法,如果是2级,前面有f(n-2)级做法。
class Solution {
public:
int jumpFloor(int number) {
if(1==number)
return 1;
if(2==number)
return 2;
int temp;
int last1 = 1;
int last2 = 2;
for(int i=3;i<=number;i++){
temp = last2;
last2 = last1+last2;
last1 = temp;
}
return last2;
}
};
第九题:变态跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
解题思路
这只青蛙真变态,但是我们可以用数学归纳法证明
f(n)=2n−1
。这个结论的得来我是这么思考的,最后一步可以跳1级,2级,甚至n级,对应着就是
f(n)=f(n−1)+f(n−2)+f(n−3)+...+f(0)
问题是
f(0)
不是一个有意义的值,但是我用知道
f(1)=1
,并且知道
f(1)=f(0)
,所以我可以反推回去得到
f(0)=1
,这样1+1+2+4+8+ 就很容易看出通项公式了。
class Solution {
public:
int jumpFloorII(int number) {
return pow(2.0,number-1);
}
};
第十题:矩形覆盖
题目描述
我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
解题思路
关键问题是覆盖的模板和被覆盖的矩形都有一条边为2,将模板横着放,那么2*2的空间可以横着放2个,右边的区域的为(n-2)*2,如果将模板竖着放,那么右边的区域就是(n-1)*2。所以这个问题又可以看出其实就是斐波拉契数列的另一个应用。
class Solution {
public:
int jumpFloor(int number) {
if(1==number)
return 1;
if(2==number)
return 2;
int temp;
int last1 = 1;
int last2 = 2;
for(int i=3;i<=number;i++){
temp = last2;
last2 = last1+last2;
last1 = temp;
}
return last2;
}
};
第十一题:二进制中1的个数
题目描述
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
解题思路
如果选择将数字右移,然后将最低位与1相与是否为1来计算1的数目,输入的数为负数时,可能会使程序陷入死循环。因此常规解法为了避免死循环,可以将flag左移,也就是首先判定最低位是不是1,然后判定次低位是不是1:
unsigned int flag = 1;
while(flag){
if(n&flag)
count++;
flag = flag<<1;
}
然而这种做法,整数有多少位就要循环几次。
还有一种更简单的做法,整数中有几个1就循环几次。这是通过首先观察发现了一个规律,就是把一个整数减去1,就是把最右边的1变为0,如果它右边有0的话,就把这些0变为1,而最右边的1的左边的部分都是不变的,因此如果我们把1个数和它减去1滞后求与,它右边的部分肯定为0(因为如果有位数的话,肯定是0,否则与最右边这个限制矛盾),实际变化的这一位,就是最右边的这个1变成了0,总共1的位数就少了一位。
所以循环的次数等于整数二进制中1的个数。
class Solution {
public:
int NumberOf1(int n) {
int c = 0 ;
while(n){
c++;
n = (n-1)& n;
}
return c;
}
};
第十二题:数值的整数次方
题目描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
解题思路
首先要考虑指数为负数时,我们先求绝对值,最后对结果求倒数,但是由于存在分母为0的风险,所以,当指数为负数时,我们的分母绝对不可以是0,在浮点数中,不能直接用等号来进行判断,而应该是限定一个范围。
其它的就是我们经典的快速幂算法啦!
class Solution {
public:
double Power(double base, int exponent) {
if(base<0.000001&&base>= -0.000001 && exponent<0)
return 0.0;
if(exponent<0)
return 1.0/Power(base,-exponent);
double result = 1.0;
while(exponent){
result *= base;
base *= base;
exponent >>= 1;
}
return result;
}
};
第十三题:调整数组顺序使奇数位于偶数前面
题目描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
解题思路
这题和剑指offer上原题略有不同,原题并不要求保留相对位置不变。
可以用插入排序的思想来做,我们的目标是将所有的偶数顺序的放到奇数的末尾,因此我们从前面开始依次找到每一个奇数,把它们前面的偶数放到奇数的后面去,为了保证相对顺序,只进行相邻两个数的交换,也就是把偶数和它后面的数交换(从右往左进行),这模拟的是把偶数放到奇数后面的过程。
比如241
发现1之后,它前面1个数是4是偶数,把4放到1后面得到 214。
再前面1个数是2也是偶数,再和1交换,得到124。保证了相对顺序
如果奇数前面的数已经是奇数,那么说明再之前的数都是奇数,因为我们是从头(左边)开始遍历的。
class Solution {
public:
void reOrderArray(vector<int> &array) {
for(int i = 1;i<array.size();i++){
if((array[i]&1)!=0){ //odd
for(int j=i-1;j>=0;j--){
if((array[j]&1)==0){ //even
// int temp = array[j+1];
// array[j+1] = array[j];
// array[j] = temp;
std::swap(array[j],array[j+1]);
}
else // odd before odd ,it is in order
break;
}
}
}
}
};
第十四题:链表中倒数第k个结点
题目描述
输入一个链表,输出该链表中倒数第k个结点。
解题思路
常规思路是我们遍历两次,一次找出链表中结点的个数,第二次找到倒数第k个结点。为了实现只遍历一次就找到,我们可以定义两个指针,一个从头指针开始遍历向前走k-1步,第二个指针不动,从第k步开始两个指针同时走,这样当前面那个到达队列尾部时,后出发的那个刚好在倒数第k个的位置。
需要注意的是,倒数第k个比最后一个,中间相差的步数为k-1步。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if(!pListHead || k==0)
return NULL;
ListNode* p = pListHead;
for(int i=0;i<k-1 ;i++){
if(p->next)
p = p->next;
else
return NULL;
}
ListNode* b = pListHead;
while(p->next){
p = p->next;
b = b->next;
}
return b;
}
};
第十五题:反转链表
题目描述
输入一个链表,反转链表后,输出链表的所有元素。
解题思路
这题我先说我第一感觉想到的思路,就是如果空间允许的话,利用栈先进后出的思想,重新分配一块内存,将结点逆序的一个个弹出构造成新的链表,再返回指向首结点的指针。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if(!pHead || !pHead->next)
return pHead;
stack<int> values;
struct ListNode * p = pHead;
while(p->next){
values.push(p->val);
p = p->next;
}
struct ListNode * reverse = p;
do{
p->next = new ListNode(values.top());
values.pop();
p = p->next;
}while(!values.empty());
p->next = NULL;
return reverse;
}
};
然而,后来我发现其实这道题考察的就是指针的操作。
假设原来的情况为a->b->c->d
如果我遍历到b时,直接将b的下一个结点指向a,就会变成a<-b c->d
此时b和c断开,我们无法在链表中遍历到c,这就是说,在断开之前,必须先保存c。
反转后的头结点,就是原来的尾结点,也就是next为NULL的结点。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if(NULL== pHead)
return pHead;
ListNode* rHead = NULL;
ListNode* p = pHead;
ListNode* pre = NULL;
while(p!=NULL){
//保存后继结点
ListNode* ne = p->next;
if(ne==NULL) //如果后继结点为空,当前结点为末尾结点,也就是反转链表的头结点
rHead = p;
p->next = pre;
pre = p;// 存储上一个结点
p = ne; //赋值为下一个结点
}
return rHead;
}
};
第十六题:合并两个排序的链表
题目描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
解题思路
我们得到两个链表中值较小的头结点并把它连接到已经合并的链表之后,两个链表剩余的结点依然是排序的,因此合并的步骤和之前的步骤是一样的。所以可以利用递归的形式来完成。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(NULL==pHead1)
return pHead2;
if(NULL==pHead2)
return pHead1;
ListNode * merge = NULL;
if(pHead1->val < pHead2->val){
merge = pHead1;
pHead1->next = Merge(pHead1->next,pHead2);
}else{
merge = pHead2;
pHead2->next = Merge(pHead1,pHead2->next);
}
return merge;
}
};
第十七题:树的子结构
题目描述
输入两颗二叉树A,B,判断B是不是A的子结构。
解题思路
要查找树A中是否存在和树B一样的子树,可以分为两步:第一步在树A中找到和B根节点的值相同的结点R,第二步判断A中以R为根结点的子树是不是包含和B相同的结构。
第一步就是一个树的遍历,我们可以用递归的方式来写,也可以用循环的方法来遍历,由于递归的代码实现比较简洁,所以通常我们可以采用递归的方式。
首先判断A的根结点和B的根结点是否相同,如果相同那么我们就进行第二步的判断,否则就分别对A的左子树和A的右子树调用递归。
第二步的判断可以单独用一个函数实现,首先判断根结点是否相同(理论上应该是相同的,因为只有相同我们才会进入这个步骤),随后递归的判断左右结点是否相同,要注意的情况是对空结点的处理,如果B中遍历出现空结点,那么可以直接返回true,因为不管A中对应的位置是不是空结点,它都是A的子集,但是如果B中不为空结点,而A中为空结点,那么肯定B就不是A的子结构了,也不需要继续遍历下去。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
if(!pRoot1 || !pRoot2)
return false;
bool result;
if(pRoot1->val = pRoot2->val)
result = ifSubtree(pRoot1,pRoot2);
if(!result)
result = HasSubtree(pRoot1->left,pRoot2);
if(!result)
result = HasSubtree(pRoot1->right,pRoot2);
return result;
}
bool ifSubtree(TreeNode* p1,TreeNode* p2){
if(!p2)
return true;
if(!p1)
return false;
if(p1->val != p2->val)
return false;
return ifSubtree(p1->left,p2->left)&&ifSubtree(p1->right,p2->right);
}
};
第十八题:二叉树的镜像
题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
二叉树的镜像定义:源二叉树
8
/ \
6 10
/ \ / \
5 7 9 11
镜像二叉树
8
/ \
10 6
/ \ / \
11 9 7 5
解题思路
先前序遍历这棵树的每个结点,如果遍历得到的结点有子结点,就交换它的两个子结点,当交换完所有非叶子结点的左右子结点之后就得到了树的镜像。
如果从深度来看,这种思路是由浅到深的交换,实际上我们也可以由深到浅的交换。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
void Mirror(TreeNode *pRoot) {
if(!pRoot)
return;
if(!pRoot->left && !pRoot->right)//叶子结点
return;
if(pRoot->left)
Mirror(pRoot->left);
if(pRoot->right)
Mirror(pRoot->right);
exchange(pRoot);// 交换左右
}
void exchange(TreeNode* p){
TreeNode* temp = p->left;
p->left = p->right;
p->right = temp;
}
};
第十九题:顺时针打印矩阵
题目描述
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
解题思路
这道题看起来没有涉及复杂的数据结构和算法,但是代码中会包含多个循环,并且还需要判定边界条件,着实花了不少时间。
主要是要考虑最里面的一圈,打印矩阵最里面的一圈可能需要3步、2步或1步。
比如
最里面一圈有2行,需要3步:
从左往右6 7 8
从上往下12
从右往左11 10
而
最里面一圈是一列,需要2步
从左往右6
从上往下10 14 0 0
而
最里面一圈是一行,仅仅需要一步
从左往右:6 7
class Solution {
public:
vector<int> printMatrix(vector<vector<int> > matrix) {
vector<int> result;
if(matrix.empty()||matrix[0].empty())
return result;
int rows = matrix.size();
int cols = matrix[0].size();
int startX = 0;
int startY = 0;
while(rows>0 && cols>0){
printCircle(startX,startY,rows,cols,result,matrix);
rows-=2;
cols-=2;
startX++;
startY++;
}
return result;
}
void printCircle(int startX,int startY, int rows,int cols,vector<int>& res,vector<vector<int> >& matrix)
{
if(rows<=0 || cols<=0)
return ;
for(int i = startX;i<startX+cols;i++)
res.push_back(matrix[startY][i]);
if(rows==1)//只有一行,所以内部只有一步
return;
int c = startX+cols-1;
for(int i = startY+1;i<startY+rows;i++)
res.push_back(matrix[i][c]);
if(cols==1)// 只有一列,所以内部只有两步
return;
int r = startY+rows-1;
for(int i = startX+cols-2;i>=startX;i--)
res.push_back(matrix[r][i]);
//事实上当row==2时下面的循环不会进行,也就是内部只有3步
for(int i = startY+rows-2;i>=startY+1;i--)
res.push_back(matrix[i][startX]);
return;
}
};
第二十题:包含min函数的栈
题目描述
定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。
解题思路
如果仅仅用一个变量来存放最小元素是不够的,因为当我们当最小的元素被弹出后,我们再想获得下一个最小的元素就困难了。
我们可以考虑把每次的最小元素(也就是当前的最小元素和新压入栈的值的较小值)保存在一个辅助栈中,这样每次压入,我们都同时往辅助栈中压入一个最小值,每次弹出,我们都对应和和辅助栈的栈顶一起弹出。
class Solution {
public:
void push(int value) {
stack.push(value);
if(min_stack.empty()){
min_stack.push(value);
}
else{
if(value < min_stack.top()){
min_stack.push(value);
}else
min_stack.push(min_stack.top());
}
}
void pop() {
min_stack.pop();
stack.pop();
}
int top() {
return stack.top();
}
int min() {
return min_stack.top();
}
private:
stack<int> min_stack;
stack<int> stack;
};
第二十一题:栈的压入、弹出序列
题目描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
解题思路
解决这个问题很直接的想法是模拟压栈的过程,建立一个辅助栈,对于弹出序列的每个数字,按照压栈序列的顺序依次放入辅助栈中,在弹出时先从辅助栈去找,如果栈顶元素无法对应,那么就再通过压栈序列往辅助栈中添加。如果弹出序列需要弹出一个数而压栈序列已经为空,那么弹出序列就不可能和压栈序列对应起来。
比如第一个需要弹出的数字为4,在压栈序列中它之前为1 2 3 ,这说明它们都必须已经进栈了,所以先将它们都依次压入辅助栈,此时辅助栈中为1 2 3 4(栈顶),随后弹出4,下一个希望弹出的为5,而辅助栈现在栈顶为3,5和3无法对应,4以后的数字就是5,因此可以直接压入和弹出5,剩下的3 2 1 依次都位于辅助栈的栈顶,所以可以依次弹出。
class Solution {
public:
bool IsPopOrder(vector<int> pushV,vector<int> popV) {
if(pushV.empty()||popV.empty()||pushV.size()!=popV.size())
return false;
stack<int> s;
vector<int>::iterator itr = popV.begin();
vector<int>::iterator itr2 = pushV.begin();
while(itr!= popV.end()){
int val = *itr;
if(!s.empty()&& s.top()==val){//和当前栈顶元素对应,可以弹出
s.pop();
itr++;continue;
}
bool ok = false;
while(itr2 != pushV.end() ){
if( *itr2!= *itr){ // 否则压栈,直到等于弹出的数字
s.push(*itr2);itr2++;
continue;
}else{//弹出的数字仍在压栈序列中
ok = true;itr2++;break;
}
}
if(!ok) return false; //如果需要弹出的数字不在压栈序列中,当然也不等于辅助栈顶
itr++;
}
return true;
}
};
第二十二题:从上往下打印二叉树
题目描述
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
解题思路
其实就是考察二叉树的层序遍历,用队列可以实现。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
vector<int> PrintFromTopToBottom(TreeNode *root) {
vector<int> result;
if(!root)
return result;
std::deque<TreeNode*> q;
q.push_back(root);
while(!q.empty()){
TreeNode* tmp = q.front();
result.push_back(tmp->val);
if(tmp->left)
q.push_back(tmp->left);
if(tmp->right)
q.push_back(tmp->right);
q.pop_front();
}
return result;
}
};