一步步学算法(算法题解)---1

本人大二,最近开始自学算法,在此记录自己学习过程中接触的习题。与君共勉。

水平有限,目前涉及的题目都比较水。

题目分布为5+1.  5为自己学习的5道水题。 1为从网上找到的比较有水平的相关题目。



一步步学算法(算法题解)---1

数值处理相关问题。


1。19头牛

问题描述:有一个老人在临死前把三个儿子叫到跟前,告诉他们把19头牛分了,老大分1/2,老二分1/4,老三分1/5,说完就死了.按当地习俗,不能宰牛.问三个儿子各能分多少?

问题分析:由于19与2、4、5都不能整除,所以就不能用平常的方法来解决这个问题。但是,如果仔细一点就可以发觉到:1/2+1/4+1/5=19/20,而牛的数量刚好为19。 

由此,不难写出下面的代码。

#include <stdio.h>

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头


2.分钱

问题描述:一元钱分成1分、2分、5分的,问有多少种分法? 

问题分析:从题目就能看出,分发肯定是有很多种,常规的逐个判断显然不适合。考虑到不是所有的面值都得用到,并且1分得特殊性(最小因子)。我们先从5分开始考虑,遍历5分所有可能个数,然后制约2分的个数,最后1分的无需考虑,缺了全部补一分的就行了。

由此,不难写出下面的代码

#include <stdio.h>

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种分法

3.队列人数

问题描述:在爱尔兰守神节那天,举行每年一度的庆祝游戏,指挥者若将乐队排成10人、9人、8人、7人、6人、5人、4人、3人和2人时,最后的一排总是缺少一个人,那些人想这个位置大概是给数月前死去的乐队成员凯西还留着位置。指挥者见到总缺一人恼火了,叫大家排成一列纵队前进。假定人数不超过7000人,那么乐队究竟有多少人? 

问题分析:问题不难,无非遍历,找到满足所有分布情况的那个值就好了。

#include <stdio.h>

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人

4.里程碑种数

问题描述:甲、乙两个城市有一条999公里长的公路。公路旁每隔一公里竖立着一个里程碑,里程碑的半边写着距甲城的距离,另半边写着距乙城的距离。有位司机注意到有的里程碑上所写的数仅用了两个不同的数字,例如000/999仅用了0和9,118/881仅用了1和8。算一算具有这种特征的里程碑共有多少个,是什么样的? 

问题分析:从题意中可知每对数仅用了两个不同的数字,并且两个数字之和衡等于9.并且,每对数之和也应衡等于999. 通过这两个限制条件,我们也不难写出代码。

#include <stdio.h>

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 (具体省略)


5.同等遗产

问题描述:父亲临终时,让按下列方式分配他的遗产:大儿子分得100克朗和剩下财产的1/10,二儿子分得200克朗和剩下财产的1/10,三儿子分得300克朗和剩下财产的1/10。依此类推,最后发现这种分法好极了,因为所有儿子分得的钱数恰好相等。问他共有几个儿子?每

个儿子分得多少遗产? 


问题分析:  还是遍历。不难,直接上代码

#include <stdio.h>

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克


6** 寻找丑数 (来源:博客园-笨鸟先飞)

问题描述:

我们把只包含因子235的数称作丑数(Ugly Number)。例如68都是丑数,但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;
}

和第一种思路相比,这种算法不需要在非丑数的整数上做任何计算,因此时间复杂度要低很多。感兴趣的读者可以分别统计两个函数 GetUglyNumber_Solution1(1500) GetUglyNumber_Solution2(1500) 的运行时间。当然我们也要指出,第二种算法由于要保存已经生成的丑数,因此需要一个数组,从而需要额外的内存。第一种算法是没有这样的内存开销的。


  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Colin丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值