offer23
一个台阶总共有n级,如果一次可以跳1级,也可以跳2级。求总共有多少总跳法,并分析算法的时间复杂度。
遇到不会的题目可以想想递归,假设 f(n),表示n级台阶的跳法,那么最后一跳只有两种可能,跳了1级或者跳了2级,即f(n)=f(n-1)+f(n-2),递归公式和非博纳契数列是一样的
/ 1 n=1
f(n)= 2 n=2
\ f(n-1)+(f-2) n>2
代码就不写了。
offer31
输入一个链表的头结点,从尾到头反过来输出每个结点的值
题目可以先链表反转,然后在输出链表,也可以采用递归,但是递归是有深度要求的。
void PrintReversely(ListNode* pHead)
{
if (pHead)
{
PrintReversely(pHead->m_pNext);
printf("%3d ",pHead->m_nKey);
}
}
offer33
给定链表的头指针和一个结点指针,在O(1)时间删除该结点。
要求O(1)时间删除,那么就不能遍历再删除了,可以考虑将节点指针的下一个节点数据复制到当前节点,然后把下一个节点删除就行了。
offer40
从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2-10为数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字。
太考验思维了
//首先要判断除了0以外其他的数字都没有重复的,
//然后只要保证 (除0外)max-min<=4 就可以了(5个数是否组成顺子)
//(如果没有0,差值为4必为顺子(差值不可能小于4))
//(有1个0,那么差值最小为3(本身已经是顺子),差值为4(顺子中空缺1位))
//(有2个0,那么差值最小为2(本身已经是顺子),差值为3(顺子中空缺1位),差值为4(顺子中空缺1位))
#include <stdio.h>
#include <stdlib.h>
bool IsContinuous(int *a, int num)
{
if (!a || num<=0)
return false;
//首先判断除0外是否有重复的
int max,min,i;
int sign[14]={0};
max=min=1; //最小的值只能是1
for (i=0; i<num; i++)
{
if (a[i]<0 || a[i]>13)
return false;
//检查是否有重复数字(除0外)
if (a[i]!=0 && sign[a[i]]!=0)
return false;
//更新最大最小值(除0外)
if (a[i]!=0)
{
max = max>a[i]? max: a[i];
min = min<a[i]? min: a[i];
}
sign[a[i]]++;
}
//检查0个数是否大于2
if (sign[0]>2)
return false;
if (max-min<num)
return true;
return false;
}
offer41
输入一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的一个。例如输入数组{32, 321},则输出这两个能排成的最小数字32132。请给出解决问题的算法,并证明该算法。
offer42
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个排好序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1。
可以采用分治法,将数组从中间分开,看最小值在哪边,然后继续按判断,直到带判断长度是2。
还要注意集中情况,博客上没有提,但是回复里有。
//默认被旋转的数组升序排列的
bool FindMin(int *a, int low, int high, int &min)
{
if (!a || low>high)
return false;
//数组首尾相等时,我们找到第一个不等的情况,找到后
//如果a[low]>a[high],那么数组还是可以看成升序后排列
//否则,有两种情况,low在high之前找到某个数小于末尾数,那么这个数就是最小值,例如1,1,0,1,low在0处停止
//或者,low直到high都没找到不等的,那么这个数组全是相等的值,
//还有一种特例,数组还是升序的,没有旋转,那么a[low] 肯定小于a[high],最小值就是a[low],也包含在下面的情况
while((low<high) && (a[low]==a[high]))
low++;
if (a[low] <= a[high])
{
min = a[low];
return true;
}
//先找中间值,判断是在大端部分还是小端部分
//循环查找,直到low 紧邻 high,那么a[high]就是最小值
int mid = (low+high)/2;
while (low < high)
{
if (high-1==low)
{
min=a[high];
return true;
}
mid = (low+high)/2;
//mid在大端
if (a[mid] >= a[low])
low=mid;
//mid在小端
if (a[mid] <= a[high])
high=mid;
}
}
offer47
数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字。
可以用用快速排序找到排在中间的数,肯定是这个数字,也可以用下面的方法。
这是原文的话,本来想用动态规划理解下,发现不是,不知道蕴含着什么思想,编程之美的解释是,每次去掉两个不同的数,那么当最后不能去掉的时候,就是出现次数多于一半的数了。
前面两种思路都没有考虑到题目中数组的特性:数组中有个数字出现的次数超过了数组长度的一半。也就是说,有个数字出现的次数比其他所有数字出现次数的和还要多。因此我们可以考虑在遍历数组的时候保存两个值:一个是数组中的一个数字,一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1。如果下一个数字和我们之前保存的数字不同,则次数减1。如果次数为零,我们需要保存下一个数字,并把次数设为1。由于我们要找的数字出现的次数比其他所有数字出现的次数之和还要多,那么要找的数字肯定是最后一次把次数设为1时对应的数字。
bool FindNum(int *a, int low, int high, int &num)
{
if (!a || low>high)
return false;
int i, sign;
for (i=low+1, sign=1, num=a[low]; i<=high; i++)
{
//sign为0,表示前面的数字不同的全部都抵销了,即前面没有大于一半的数字了,从新开始寻找
if (0==sign)
{
num=a[i];
sign++;
}
//与前面数字相同,num保持原值,sign+1
else if (a[i]==a[i-1])
sign++;
else
sign--;
}
//用来检查数组中是否存在某个数多余一半
for (i=low, sign=0; i<=high; i++)
{
if (a[i]==num)
sign++;
}
if (sign<=(high-low+1)/2)
return false;
return true;
}
offer49
有一个复杂链表,其结点除了有一个m_pNext指针指向下一个结点外,还有一个m_pSibling指向链表中的任一结点或者NULL。
这个太赞了,直接看贴博客吧, http://zhedahht.blog.163.com/blog/static/254111742010819104710337/
offer55
写一个函数,求两个整数的之和,要求在函数体内不得使用+、-、×、÷。
回复里有一个很好的算法,直接利用地址的偏移做
int add(int a,int b){
char * c;
c = (char *) a;
return &c[b];
}
首先将整数a的值转换成地址,然后把这个地址作为数组的首地址,然后找到这个数组的第b个元素的地址,那句是地址a再向右偏移b个。要在小端模式的机子上才有用,不过现在的机子都是小端模式了。