剑指Offer——数学问题
1、第7题 斐波那契数列
题目描述:
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
n<=39
解题思路:
这道题的思路就不多介绍了,我们这里给出递归和非递归的实现方式。
代码实现:
递归方式(这里在牛客网上会超时)
#include<iostream>
using namespace std;
int Fibonacci(int n)
{
if (n == 0)
return 0;
if(n == 1)
return 1;
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
int main()
{
int res = Fibonacci(10);
cout << res << endl;
return 0;
}
非递归方式
class Solution {
public:
int Fibonacci(int n) {
int *number = new int[n+1];
number[0] = 0;
number[1] = 1;
int index = 2;
while(index <= n)
{
number[index] = number[index -1]+number[index -2];
index ++;
}
return number[n];
}
};
2、第8题 跳台阶问题
题目描述:
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
解题思路:
如果当前处于的是第k个台阶:
如果 k<= 1,则说明跳跃到当前台阶的方式只能是从前一个台阶跳上来。
如果 k>1 则跳跃到当前台阶的方式有两种,一种是从前一个台阶跳跃一个到达,第二种方式从前两个台阶跳跃两个方式到达。
显然,上面描述的是一个递归的过程。也就是说jump(k) = jump(k-1)+jump(k-2)。
代码实现:
class Solution {
public:
int jumpFloor(int number) {
if(number == 0)
return 0;
return jump(number);
}
int jump(int n)
{
if(n <= 1)
return 1;
return jump(n-1)+jump(n-2);
}
};
3、第9题、变态跳台阶问题
题目描述:
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
解题思路:
这道题需要一点点的数学推理:对于一个k阶的台阶有:
如果k≤1的时候,只有一种方式来到达。在返回1。
如果k>1的时候,跳跃到k阶的方式包括从第1阶开始,第二阶开始,…,第k-1阶开始,也就是说jump(k)= jump(1)+jump(2) +,…,+jump(k-2)+jump(k-1),而jump(1)+jump(2) +,…,+jump(k-2)=jump(k-1)。则jump(k) = 2 * jump(k-1)。至此,递推公式完成。
代码实现:
class Solution {
public:
int jumpFloorII(int number) {
if(number <= 1)
return number;
return jumpFloorII(number-1) *2 ;
}
};
4、第10题 矩形覆盖
题目描述:
我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
比如n=3时,2*3的矩形块有3种覆盖方法:
解题思路:
我们开始逐步的推导一下:
当n等于1的时候,此时只用一种方式进行覆盖。
当n等于2的时候,可以竖着覆盖,同时也可以横着覆盖,此时有两种方式。
当n=3的时候,此时,我们可以倒推,暂时屏蔽掉一个位置,那么此时n=2的时候方式的数量相同,同理屏蔽掉两个位置,则此时和n=1的时候方式相同,也就是说f(3)=f(2)+f(1)
当n=4的时候,是在2个的基础上在增加两个,每两个的覆盖方式有f(2)种,则一共的覆盖方式有f(2)。或者在3的基础上增加一个,此时方式为f(3),则有f(4)=f(3)+f(2)。
所以,我们可以推导出递推公式为f(n) = f(n-1)+f(n-2),也就是斐波那契数列。
代码实现:
class Solution {
public:
int rectCover(int number) {
if(number<=2)
return number;
return rectCover(number-1)+rectCover(number-2);
}
};
5、第11题 二进制中1的个数
题目描述:
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
思路分析:
这道题的思路还是很简单的,只需要采用一个1,不断的去和数字中的各个位置进行与运算,并记录不为0的情况。
代码实现:
class Solution {
public:
int NumberOf1(int n) {
int flag = 1;
int count = 0;
while(flag != 0)
{
if((flag & n) != 0)
count ++;
flag = flag<<1;
}
return count;
}
};
6、第12题 数值的整数次方
题目描述:
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
保证base和exponent不同时为0
解题思路:
直接计算
代码实现:
class Solution {
public:
double Power(double base, int exponent) {
if(exponent == 0)
return 1;
if(base == 0)
return 0;
int exp = abs(exponent);
double res = 0.0;
res = quickPower(base,exp);
if(exponent < 0)
return 1/res;
else
return res;
}
double quickPower(double base,int exponent)
{
if(exponent == 1)
return base;
return base * quickPower(base,exponent-1);
}
};
7、第29题 最小的K个数
题目描述:
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
解题思路:
排序,获得结果并输出
代码实现:
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
vector<int> res;
if(input.size() ==0 || k>input.size()) return res;
sort(input.begin(),input.end());
for(int i=0;i<k;i++)
res.push_back(input[i]);
return res;
}
};
8、第31题 整数中1出现的次数
题目描述:
求出1-13的整数中1出现的次数,并算出100-1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
解题思路:
我们来分析一下一出现的位置:
- 当1作为个位数出现的时候,有1,11,21,31,41,51,…,91
- 第1作为十位数出现的时候,有10,11,12,13,…,
- 当1作为百位出现的时候,有100,101,102,。。。,199
所以,下一步我们是判断给定n的大小,当n为个位数的时候,返回1,当n为十位数的时候,返回的是1出现在十位上和出现在个位数上的和。其他同理。
代码实现:
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n)
{
if(n<=0)
return 0;
int count = 0;
for(int i=1;i<=n;i*=10)
{
int divier = i * 10;
int k = n %(i * 10);
count += (n/divier) * i;
if(k>2*i-1)
count += i;
else if(k <i)
count += 0;
else
count += (k-i+1);
}
return count ;
}
};
9、第33题 丑数
题目描述:
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
解题思路:
对于这道题,我们首先设置三个下标参数t2,t3,t5,然后开始循环生成,根据丑数的概念,我们只需要将对这三个质因子做累乘,其结果就是一个丑数,然后比较t2指向的丑数2,t3指向的丑数3,t5指向的丑数乘以5,在比较三个结果的大小,选择最小的结果作为当前位置的丑数即可。
代码实现:
class Solution {
public:
int GetUglyNumber_Solution(int index) {
if(index<7)
return index;
int t2=0,t3=0,t5=0;
vector<int> res(index);
res[0] = 1;
for(int i=1;i<index;i++)
{
res[i] = min(res[t2]*2,min(res[t3]*3,res[t5]*5));
if(res[i] == res[t2] *2) t2++;
if(res[i] == res[t3] *3) t3++;
if(res[i] == res[t5] *5) t5++;
}
return res[index -1];
}
};
10、第41题 和为S的连续整数序列
题目描述:
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
解题思路:
本题考查的就是一个连续序列相加的数学公式,根据等差数列的求和公式有: n ( n − 1 ) / 2 n(n-1)/2 n(n−1)/2,则我们可以定义两个下标参数,将两个下标之间的数字相加和目标去比较,如果小于目标参数,则前面的参数指针继续向下走,如果大于目标参数,则后面的下标指针向前一个位置。如果相等,则输出结果。
代码实现:
class Solution {
public:
vector<vector<int> > FindContinuousSequence(int sum) {
vector<vector<int>> res;
if(sum<=0)
return res;
int pslow = 1,pfast = 2;
while(pfast>pslow)
{
int temp = (pfast+pslow)*(pfast - pslow +1) / 2;
if(temp == sum)
{
vector<int> a;
for(int i=pslow;i<=pfast;i++)
a.push_back(i);
res.push_back(a);
pslow++;
}
else if(temp < sum)
pfast ++;
else
pslow ++;
}
return res;
}
};
11、第41题 和为S的两个数字
题目描述:
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
解题思路:
两个位置指针,第一个从前向后low,第二个从后向前high,,当low+high小于sum的时候,low向前移动,当low+high大于sum的时候,high前后移动。当相等的时候与最下的乘积值进行比较。
代码实现:
class Solution {
public:
vector<int> FindNumbersWithSum(vector<int> array,int sum) {
vector<int> res;
if(array.size()==0)
return res;
int low=0,high = array.size()-1;
int minv = INT_MAX;
while(high > low)
{
if((array[high] + array[low]) > sum)
high --;
else if((array[low] + array[high]) < sum)
low ++;
else
{
if((array[low] * array[high]) < minv)
{
minv = array[low] * array[high];
res.clear();
res.push_back(array[low]);
res.push_back(array[high]);
}
low++;
high --;
}
}
return res;
}
};
12、第45题 扑克牌的顺子
题目描述:
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
解题思路:
我们需要考虑以下几种情况,
- 如果当前数字大于13或者小于0,则返回false;
- 如果当前数字为0的时候,则跳过不考虑。
- 如果当前数字中出现了重复的情况,则返回false,这里我们采用flag作为标记,如果两次flag相与不为0,则说明有数字重复。
- 在遍历的过程中,记录最大值和最小值,如果最大值和最小值之间的差值≥5,则返回false。
代码实现:
class Solution {
public:
bool IsContinuous( vector<int> numbers ) {
if(numbers.size() == 0)
return false;
int maxv = -1;
int minv = 13;
int flag = 1;
for(int i=0;i<numbers.size();i++)
{
if(numbers[i] >13|| numbers[i] < 0) return -1;
if(numbers[i] == 0) continue;
if(flag & (1<<numbers[i])) return false;
flag |= (1<<numbers[i]);
if(numbers[i] > maxv)
maxv = numbers[i];
if(numbers[i] < minv)
minv = numbers[i];
}
if(maxv-minv >= 5)
return false;
return true;
}
};
13、第46题 孩子们的游戏
题目描述:
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
如果没有小朋友,请返回-1
解题思路:
用链表来模拟环,当到对应的数字的时候,删除该节点,最终只剩一个节点的时候就是我们希望的结果。
代码实现:
struct Person
{
int val;
struct Person * next;
Person(int v):val(v),next(NULL){}
};
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
if(n<=0||m<=0)
return -1;
Person * head = new Person(0);
Person * cur = head;
for(int i=1;i<n;i++)
{
Person * p = new Person(i);
cur->next = p;
cur = cur->next;
}
cur->next = head;
int k =0;
while(cur != cur->next)
{
if(++k == m)
{
cur->next = cur->next->next;
k = 0;
}
else
cur = cur->next;
}
return cur->val;
}
};
14、第47题、求1+2+3+…+n
题目描述:
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
解题思路:
本题的思路比较巧妙,需要提前理解到C++中的“短路操作”,在表达式1&&表达式2,如果表达式1不满足条件,则表达式2不会被计算。我们采用递归的方式,当n的0的时候递归结束。
代码实现:
class Solution {
public:
int Sum_Solution(int n) {
int sum = n;
n && (sum += Sum_Solution(n-1));
return sum;
}
};
15、第48题、不用加减乘除做加法
题目描述:
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
解题思路:
采用异或操作和进位标志来表示进位来完成加法。
代码实现:
class Solution {
public:
int Add(int num1, int num2)
{
int carry = (num1 & num2)<<1;
int tempr = num1 ^ num2;
while(carry & tempr)
{
num1 = carry;
num2 = tempr;
carry = (num1 & num2) << 1;
tempr = num1 ^ num2;
}
return carry^tempr;
}
};