本人大二,最近开始自学算法,在此记录自己学习过程中接触的习题。与君共勉。
水平有限,目前涉及的题目都比较水。
题目分布为5+1. 5为自己学习的5道水题。 1为从网上找到的比较有水平的相关题目。
一步步学算法(算法题解)---1
数值处理相关问题。
问题描述:有一个老人在临死前把三个儿子叫到跟前,告诉他们把19头牛分了,老大分1/2,老二分1/4,老三分1/5,说完就死了.按当地习俗,不能宰牛.问三个儿子各能分多少?
问题分析:由于19与2、4、5都不能整除,所以就不能用平常的方法来解决这个问题。但是,如果仔细一点就可以发觉到:1/2+1/4+1/5=19/20,而牛的数量刚好为19。
由此,不难写出下面的代码。
#include
int main()
{
int i;
for (i=1; i<=10; i++) //遍历查找
{
if (i + i/2 + 2*i/5 == 19) //找到满足条件的个数
{
printf("三个儿子分别分得:%d头,%d头,%d头", i, i/2, 2*i/5);
}
}
return 0;
}
//打印结果
三个儿子分别分得:10头,5头,4头
问题描述:一元钱分成1分、2分、5分的,问有多少种分法?
问题分析:从题目就能看出,分发肯定是有很多种,常规的逐个判断显然不适合。考虑到不是所有的面值都得用到,并且1分得特殊性(最小因子)。我们先从5分开始考虑,遍历5分所有可能个数,然后制约2分的个数,最后1分的无需考虑,缺了全部补一分的就行了。
由此,不难写出下面的代码
#include
int main()
{
int i, j;
int count_ = 0;
for (i=0; i<=20; i++) //遍历5分的所有情况。 0<=i<=20
{
for (j=0; j<=(100-5*i)/2; j++) //制约2分的可能情况 一
{
count_++; //二
} //三
//或者。上述 一二三语句可以替换成如下语句
//for (j=0; j<=50; j++)
//{
// if (5*i + j*2 <= 100)
// {
// count_++;
// }
//}
}
printf("共有%d种分法", count_);
return 0;
}
//打印结果 共有541种分法
问题描述:在爱尔兰守神节那天,举行每年一度的庆祝游戏,指挥者若将乐队排成10人、9人、8人、7人、6人、5人、4人、3人和2人时,最后的一排总是缺少一个人,那些人想这个位置大概是给数月前死去的乐队成员凯西还留着位置。指挥者见到总缺一人恼火了,叫大家排成一列纵队前进。假定人数不超过7000人,那么乐队究竟有多少人?
#include
int main()
{
int i, j;
for (i=9; i<=7000; i+=10) //用最大步长取最外层循环变量值
{
for (j=9; j>=2; j--) //模拟排队过程
{
if ((i+1)%j != 0) //不满足条件
{
break;
}
}
if (j==1) //满足条件
{
printf("乐队共有%d人", i);
return 0; //找到最小的
}
}
return 0;
}
//打印结果 乐队共有2519人
问题描述:甲、乙两个城市有一条999公里长的公路。公路旁每隔一公里竖立着一个里程碑,里程碑的半边写着距甲城的距离,另半边写着距乙城的距离。有位司机注意到有的里程碑上所写的数仅用了两个不同的数字,例如000/999仅用了0和9,118/881仅用了1和8。算一算具有这种特征的里程碑共有多少个,是什么样的?
问题分析:从题意中可知每对数仅用了两个不同的数字,并且两个数字之和衡等于9.并且,每对数之和也应衡等于999. 通过这两个限制条件,我们也不难写出代码。
#include
int main()
{
int i, j, k, sum_, count_ = 0;
for (i=0; i<=9; i++)
{
for (j=0; j<=9; j++)
{
for (k=0; k<=9; k++)
{
if (((i==j)&&(9-i==k))||((i==k)&&(9-i==j))||((j==k)&&(9-k==i))||((i==j)&&(j==k)))
{
sum_ = i*100 + j*10 + k;
printf("%d---%d\n", sum_, 999-sum_);
count_++;
}
}
}
}
printf("满足条件的里程碑个数: %d", count_);
return 0;
}
//打印结果 满足条件的里程碑个数: 40 (具体省略)
问题描述:父亲临终时,让按下列方式分配他的遗产:大儿子分得100克朗和剩下财产的1/10,二儿子分得200克朗和剩下财产的1/10,三儿子分得300克朗和剩下财产的1/10。依此类推,最后发现这种分法好极了,因为所有儿子分得的钱数恰好相等。问他共有几个儿子?每
个儿子分得多少遗产?
问题分析: 还是遍历。不难,直接上代码
#include
int main()
{
int i, j, k, m, n;
for (n=600; ; n=n+10) //由题意得,遗产最少600,并且最小步长10
{
k = 100 + (n-100)/10; //遍历寻找满足条件的
m = n - k; //每个儿子遗产都一样,直接利用大儿子分得的带入计算
for (i=2; m>0; i++)
if ((m%10!=0)||(k!=i*100+(m-i*100)/10))
break;
else m = (m-i*100) - (m-i*100)/10;
if (m==0)
{
printf("他共有%d个儿子,每个儿子分得%d克朗.", i, k);
exit(0); //找出后退出程序,不然无限循环
}
}
return 0;
}
//打印结果 他共有10个儿子,每个儿子分得900克
问题描述:
我们把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第1500个丑数。
分析:这是一道在网络上广为流传的面试题,据说google曾经采用过这道题。
所谓一个数m是另一个数n的因子,是指n能被m整除,也就是n % m == 0。根据丑数的定义,丑数只能被2、3和5整除。也就是说如果一个数如果它能被2整除,我们把它连续除以2;如果能被3整除,就连续除以3;如果能被5整除,就除以连续5。如果最后我们得到的是1,那么这个数就是丑数,否则不是。
基于前面的分析,我们可以写出如下的函数来判断一个数是不是丑数:
bool IsUgly(int number)
{
while(number % 2 == 0)
number /= 2;
while(number % 3 == 0)
number /= 3;
while(number % 5 == 0)
number /= 5;
return (number == 1) ? true : false;
}
int GetUglyNumber_Solution1(int index)
{
if(index <= 0)
return 0;
int number = 0;
int uglyFound = 0;
while(uglyFound < index)
{
++number;
if(IsUgly(number))
{
++uglyFound;
}
}
return number;
}
我们只需要在函数GetUglyNumber_Solution1中传入参数1500,就能得到第1500个丑数。该算法非常直观,代码也非常简洁,但最大的问题我们每个整数都需要计算。即使一个数字不是丑数,我们还是需要对它做求余数和除法操作。因此该算法的时间效率不是很高。
接下来我们换一种思路来分析这个问题,试图只计算丑数,而不在非丑数的整数上花费时间。根据丑数的定义,丑数应该是另一个丑数乘以2、3或者5的结果(1除外)。因此我们可以创建一个数组,里面的数字是排好序的丑数。里面的每一个丑数是前面的丑数乘以2、3或者5得到的。
这种思路的关键在于怎样确保数组里面的丑数是排好序的。我们假设数组中已经有若干个丑数,排好序后存在数组中。我们把现有的最大丑数记做M。现在我们来生成下一个丑数,该丑数肯定是前面某一个丑数乘以2、3或者5的结果。我们首先考虑把已有的每个丑数乘以2。在乘以2的时候,能得到若干个结果小于或等于M的。由于我们是按照顺序生成的,小于或者等于M肯定已经在数组中了,我们不需再次考虑;我们还会得到若干个大于M的结果,但我们只需要第一个大于M的结果,因为我们希望丑数是按从小到大顺序生成的,其他更大的结果我们以后再说。我们把得到的第一个乘以2后大于M的结果,记为M2。同样我们把已有的每一个丑数乘以3和5,能得到第一个大于M的结果M3和M5。那么下一个丑数应该是M2、M3和M5三个数的最小者。
前面我们分析的时候,提到把已有的每个丑数分别都乘以2、3和5,事实上是不需要的,因为已有的丑数是按顺序存在数组中的。对乘以2而言,肯定存在某一个丑数T2,排在它之前的每一个丑数乘以2得到的结果都会小于已有最大的丑数,在它之后的每一个丑数乘以2得到的结果都会太大。我们只需要记下这个丑数的位置,同时每次生成新的丑数的时候,去更新这个T2。对乘以3和5而言,存在着同样的T3和T5。
有了这些分析,我们不难写出如下的代码:
int GetUglyNumber_Solution2(int index)
{
if(index <= 0)
return 0;
int *pUglyNumbers = new int[index];
pUglyNumbers[0] = 1;
int nextUglyIndex = 1;
int *pMultiply2 = pUglyNumbers;
int *pMultiply3 = pUglyNumbers;
int *pMultiply5 = pUglyNumbers;
while(nextUglyIndex < index)
{
int min = Min(*pMultiply2 * 2, *pMultiply3 * 3, *pMultiply5 * 5);
pUglyNumbers[nextUglyIndex] = min;
while(*pMultiply2 * 2 <= pUglyNumbers[nextUglyIndex])
++pMultiply2;
while(*pMultiply3 * 3 <= pUglyNumbers[nextUglyIndex])
++pMultiply3;
while(*pMultiply5 * 5 <= pUglyNumbers[nextUglyIndex])
++pMultiply5;
++nextUglyIndex;
}
int ugly = pUglyNumbers[nextUglyIndex - 1];
delete[] pUglyNumbers;
return ugly;
}
int Min(int number1, int number2, int number3)
{
int min = (number1 < number2) ? number1 : number2;
min = (min < number3) ? min : number3;
return min;
}