起因

海滩上有一堆桃子,五只猴子来分。第一只猴子把这堆桃子分为五份,多了一个,这只猴子把多的一个仍入海中,拿走了一份。第二只猴子把剩下的桃子平均分成五份,又多了一个,它同样把多的一个扔入海中,拿走了一份,第三、四、五只猴子都是这样做的,问海滩上原来最少有多少个桃子?

起初我看到这道题是崩溃的,咋又碰到数学题啦!我数学向来差的一批,所以这道题我是真真的没想出啥解决的方法,于是乎我便到网上查找解题方案,好家伙,不查不要紧,一查我更崩溃了,因为我发现那些解释我一个都没懂!尽管N多个人从N多个方向提出了解题思路,但我是真弄没弄清楚他们列出来的数学公式。连解题思路我都没懂,就更别谈网上的代码了,代码复制下来是能跑出正确结果,但是从开头的数值初始化,到最后的打印语句,我没有一行能理解的o(╥﹏╥)o

就在我万念俱灰、瞎搞乱搞之际,突然一条思路在我脑袋里冒出,顺着这条思路我瞬间理清了解决方法,并相当轻松的完成了代码!!(不要问我思路咋突然就理清了,我也母鸡啊╮(╯▽╰)╭)这个思路不需要你有啥数学知识,也不用列什么数学公式,只有非常简单的一点逻辑分析,我相信在这个思路下,大家就算数学糊烂到爆,也能理解这道题,毕竟数学再烂能有我烂!?

问题分析

分析这个猴子偷桃的问题之前,可以参考一下我之前写的这篇,暴力解三元一次方程的思路:用for循环解三元一次方程的思路

拥有好的数学功底,固然能更好的写出程序,但是程序不仅仅只有数学,还有逻辑分析。数学解不出来,我就用程序暴力解决呗,程序不就是代替我们人类做起来重复又繁琐的事嘛。回到猴子分桃的问题上来, 目前咱们从题目中得到的信息说多不多,说少不少,咱们一步一步得来提取:

首先,有一个固定的数值是显然易见的,那么就是猴子有5只,他们一共分了5次桃。那咱们就将猴子的变量先提取出来,monkey = 5呗。

然后,每次分桃时候的桃子数量有5份+1,至于每份是多少,不知道。最难的地方就在这,每份的桃子数不光不确定,而且每次分完桃子后,桃子的数量还会变化!!不过别急着用数学的公式来解决他,这里虽然是最难搞定的,但也是突破口。

咱继续进一步思考,猴子每次会分5份桃子+1个,然后丢掉一个,拿走一其中份,这不就代表着 将 桃子数先 - 1,然后再拿走五分之一数量的桃子,只剩下五分之四的桃子,并且下一次分桃又重复着这种操作,总共进行了5次。 (不要嫌我啰嗦反复讲这些已知的,代码马上就可以写出了)。同样的操作,进行了5次,嗯哼,大家有联想到程序里的什么吗!? 没错,这不就是循环嘛!! 一共循环了5次呗。OK,咱们现在知道要弄一个循环出来了,循环的执行代码块又是啥咧?

int i = 1;// 循环的条件变量
while(i <= 5) {
    //这里要做点啥咧......
    i++;
}

有一个数,要 - 1后 再 剩下 五分之四,并且要不断的进行五次,也就是说,咱们要求的这个数要达到这些条件才符合我们的要求。符合要求,嗯哼?大家又联想到程序里的什么吗?是滴,就是判断嘛,一个数要符合这个条件并持续五次,才算达到要求。OK,那不就代表咱们要把判断条件放到那个五次循环里进行嘛,循环执行完毕后都通过了判断,就代表找到那个数了!

咱们要最终找到的那个数是桃子,所以变量为了见名知意,就定为peach哈

int i = 1;
while(i <= 5) {
    // -1后才能分平均五份,不就代表着模5要等于1嘛
    if (peach % 5 == 1) {
        // 每次弄完后,就剩下原数量的 - 1后的五分之四了,所以每次要将这个数更新一下
        // peach / 5就代表现在的每份桃子的数量
        // 拿掉一份后,就是只剩下四份,所以才乘以4
        // 有人问不是多了一个桃子嘛,怎么不用-1呢,因为程序/5会直接减掉余数,所以不用-1后再除。
        // 如果想-1后再除以5,也是一样的,只是没必要
        peach = (peach / 5) * 4;
    }
    i++;
}

非常好,感觉有点意思了。现在下一个问题自然就摆在我们眼前,peach我该定义成啥数啊?呃...要定义啥数我也不知道,要是我知道的话那就不用求这个数了,答案不就出来了嘛。 别急,不知道啥数,咱们就找嘛,我们一个数字一个数字的去试,看看到底哪个数符合这个条件,答案不就出来了? 一个一个数字的去试,咱们人做起来肯定要疯,不过聪明的你肯定想到了,用程序去做呗!注意哦,这里已经将问题的解决思路全部说出来了,就是 一个数一个数的去尝试,如果符合题意条件的就代表那个数找到了! 咱们现在要做的就是,怎样去一个数一个数的去找:

int i = 1; 
int peach = 1; // 要寻找的数,咱们从最小的整数开始往下一个一个的找
while(i <= 5) {
    if (peach % 5 == 1) {
        peach = (peach / 5) * 4;
    }
    i++;
    peach++; // 每循环一次peach的数量就加一次,然后再进行尝试判断
}

写到这里有一个程序的问题,就是循环只有五次,peach的数量只能加五次,这五次要是没符合,程序还是会结束,所以这里就要进行逻辑上的优化,以达到我能一个数一个数进行尝试的要求(这一步和题目其实没啥关系,就是程序逻辑的优化):

int i = 1; 
int peach = 1; // 要寻找的数,咱们从最小的整数开始往下一个一个的找
while(i <= 5) {
    if (peach % 5 == 1) {
        peach = (peach / 5) * 4;
        i++;// 只有数值判断成功了,才会将循环条件更新
    } else { // 如果数值没有判断成功,就代表数值不符合要求,就要继续找下一个数
        peach++; // 每循环一次peach的数量就加一次,然后再进行尝试判断
    }
}

为了讲解的更清楚,这一步我没有完全优化好,就是说这块代码还是有很多问题。比如,要是一个数值只要符合 % 5 == 1了,就会进行代码块里的数值更新(peach呀,循环条件i呀都会变化掉),但是我们要求的是得连续符合5次才行,现在一次符合就将我们数值给变化了,那可不行,所以咱进行下一步优化,一定要保证,如果不是连续符合五次的话,我的其他数值就不能变化,然后重新再来,进行下一个数值的判断

int i = 1; 
int peach = 1;
int count = 1; // 因为每次分桃的数量会更新,所以必须额外声明一个变量来不断递增做实验
while(i <= 5) {
    if (peach % 5 == 1) {
        peach = (peach / 5) * 4;
        i++;
    } else { 
         // 每循环一次peach的数量就加一次,然后再进行尝试判断,为了避免peach在if代码块里变化过,所以额外的一个变量来做更新
        count++;
        peach = count;
        i = 1; // 为了避免循环条件变量i也被更新过,所以只要有一次判断不成功就重新来过,重新设为初始值
    }
}

至此代码就已经完全实现了,为了更加的见名知意,我们将循环条件变量i改一下名,既然是五只猴子来分,就将i改成monkey,以下是完整代码,我会将所有注释都加上,好方便理解(如果看完整代码对其中某一步不明白,就看一下之前我是怎样一步一步增加代码的):

/**
 * 
 * @function 猴子分桃
 * @author RudeCrab
 * @date 2019年4月1日上午8:30:21
 * @version 1.0.0
 * @copyright RudeCrab
 */
public class Peach {
    public static void main(String[] args) {
        int monkey = 1;// 要分桃(判断)的次数,就是到第几个猴子分桃了
        int peach = 1;// 每次分桃(判断)的总数,用来判断是否符合条件
        int count = 1;// 因为每次分桃(判断)后数量会更新,所以必须额外声明一个变量来递增做实验
        // 循环进行分桃判断
        while (monkey <= 5) {
            // 怎样才算分桃(判断)成功呢,就是要%5后还剩一个,然后来五次
            // -1后才能分平均五份,不就代表着模5要等于1嘛
            if (peach % 5 == 1) {
                // 每次弄完后,就剩下原数量的 - 1后的五分之四了,所以每次要将这个数更新一下
                // peach / 5 就代表现在的每份桃子的数量
                // 拿掉一份后,就是只剩下四份,所以才乘以4
                // 有人问不是多了一个桃子嘛,怎么不用-1呢,因为程序/5会直接减掉余数,所以不用-1后再除。
                // 如果想-1后再除以5,也是一样的,只是没必要
                peach = (peach / 5) * 4;
                // 分桃成功的话,就将循环条件变量monkey进行更新
                monkey++;
            } else { // 如果数值没有判断成功,就代表数值不符合要求,就要继续找下一个数
                // 代表刚才那个数字不能达到五次的要求,所以重新再来
                
                count++; // 为了避免peach在if代码块里变化过,所以额外的一个变量来做更新
                peach = count; // 然后peach重新被赋值,这样peach就算判断失败了,也不会受影响
                monkey = 1; // 为了避免循环条件变量也被更新过,所以只要有一次判断不成功就重新来过,重新设为初始值
            }
        }
        // 最后整个循环执行完毕了,就代表数值找到了,打印数值即可
        // 这里为啥不打印peach呢,因为peach在代码块里每次分桃(判断)后都被更新了,所以count才是真实数值
        System.out.println("桃的最小总数为:" + count);
    }
}

最后输出结果为3121,完美求出结果。这个解题思路说白了就是暴力求解,一个数一个数的去尝试判断,哪个数能符合题意的就代表数值找到了。 是不是用不到难以理解的数学公式就搞定了,嘿嘿。其实代码量非常少,根本没多少行,但是为了方便理解,所以我的注释尽量加的足够多,大家要是看着一堆注释要点晕,可以先将代码复制下来,然后将注释单独保存,将代码和注释对照查看肯定能够更好的理解!

到这里大家以为就结束了嘛,nonono,要是这个思路只能解决这一个问题,这顶多叫这一题的特定方法,既然我说了是一种解题的思路,就代表这一种类型的问题都可以搞定,那么接下来让我们看一下另一个 猴子分桃问题。

扩展练习

有一只猴子,摘了很多桃子,他每天吃掉一些,剩下的桃子比之前的全部的一半少一个。他吃了十天才吃完,请问他一共摘了多少桃子?

看到这个问题是不是觉得不难,按照之前的方法去解就OK了咯,这和上一题不同点就只是在于数值判断的条件循环的次数。 我就不一步一步分解这一题了,直接贴上所有代码和注释,如果期间有不明白的,回到文章前面看一下思路就能搞定:

public static void showPeach2() {
    int day = 1; // 天数,即循环的判断条件
    int peach = 1; // 桃子数,即要求出的值
    int count = 1; // 额外的变量,用来保持桃子数量的更新
    while (day <= 10) { // 这里改成10次循环
        if (peach % 2 == 0 && peach > 0) { // 因为0%2也会等于0,所以得加一个限定条件数量要大于0才行
            peach = peach / 2 - 1; // 更新变量
            day++; // 更新循环条件
        } else { // 如果没有判定成功,代表重新来过
            count++;
            peach = count;
            day = 1;
        }
    }

    System.out.println("总桃子为:" + count);
    /*输出结果为2046*/
}

总结和思考

和解三元一次方程一样,数学咱不会解,就用程序来暴力解,毕竟程序可以替人类做那些重复繁琐的事嘛,这也是程序的用途所在!

如果对于这题还有地方不明白,请留言,咱们一起交流


RudeCrab
22 声望2 粉丝