12_經(jīng)典趣題算法(Java、Kotlin描述)

(1)百錢(qián)買百雞問(wèn)題

????來(lái)自《算經(jīng)》,大意:公雞5文錢(qián)一只,母雞3文錢(qián)一只,小雞1文錢(qián)3只,如果用100文錢(qián),買一百只雞,那么公雞、母雞和小雞各買多少只?
????分析:這是一個(gè)非常經(jīng)典的不定方程問(wèn)題,可能有多個(gè)解。假設(shè)x、y和z,分別代表公雞、母雞和小雞的數(shù)量,那么應(yīng)該滿足:1)x + y + z = 100; 2)5x + 3y + z/3 = 100;
????Java版本算法如下:

    /**
     * 計(jì)算m錢(qián)買n雞問(wèn)題
     * @param m
     * @param n
     */
    void hundredBuyChicken(int m ,int n){
        int x,y,z;
        for (x = 0;x<=n;x++){
            for (y = 0;y<=n;y++){
                z = n - x -y;
                if (z>0 && z %3 == 0 && x*5 + y*3+z/3 == m){
                    System.out.printf("公雞:%d只,母雞:d%只,小雞:d%只",x,y,z);
                }
            }
        }
    }

????Kotlin版本:

    /**
     * 計(jì)算m錢(qián)買n雞問(wèn)題
     * @param m
     * @param n
     */
    fun hundredBuyChicken(m: Int, n: Int) {
        var x: Int
        var y: Int
        var z: Int
        x = 0
        while (x <= n) {
            y = 0
            while (y <= n) {
                z = n - x - y
                if (z > 0 && z % 3 == 0 && x * 5 + y * 3 + z / 3 == m) {
                    System.out.printf("公雞:%d只,母雞:d%只,小雞:d%只", x, y, z)
                }
                y++
            }
            x++
        }
    }

(2)猴子吃桃問(wèn)題

????大意是:某天一只猴子摘了一堆桃子,具體數(shù)量未知。猴子每天吃掉其中的一半然后再多吃一個(gè),第二天吃剩余的一半然后再多吃一個(gè),······,直到第10天,猴子發(fā)現(xiàn)只有一個(gè)桃子。問(wèn)猴子在第一天摘了多少桃子?
????思路:假設(shè)a_n表示第n天剩余的桃子數(shù)量,那么有關(guān)系:
????????a_1 = (a_2+1) * 2;
????????a_2 = (a_3+1) * 2;
????????······
????????a_9 = (a_{10}+1) * 2;
????????a_{10} = 1;
????Java版本:

    int peach(int n){
        int num;
        if (n == 1){
            return 1;
        }else {
            num = (peach(n-1)+1)*2;
        }
        return num;
    }

????Kotlin版本:

    fun peach(n: Int): Int {
        val num: Int
        num = if (n == 1) {
            return 1
        } else {
            (peach(n - 1) + 1) * 2
        }
        return num
    }

(3)舍罕王賞麥問(wèn)題

????一個(gè)著名的級(jí)數(shù)求和問(wèn)題,大意是:在國(guó)際象棋的8 x 8 = 64格子里,第一個(gè)格子放1粒麥子,第二個(gè)格子放2粒麥子,第三個(gè)格子放4粒麥子,······,以此類推,直到64個(gè)格子。每個(gè)格子里的麥子數(shù)是它前一個(gè)格子的2倍。
????Java版本的算法如下:

    long computeWheat(int n){
        long tmp, sum;
        tmp =1;
        sum = 0;
        
        for (int i = 1;i<=n;i++){
            sum = sum + tmp;
            tmp = tmp *2;
        }
        return sum;
    }

????Kotlin版:

    fun computeWheat(n: Int): Long {
        var tmp: Long
        var sum: Long
        tmp = 1
        sum = 0
        for (i in 1..n) {
            sum = sum + tmp
            tmp = tmp * 2
        }
        return sum
    }

(4)漢諾塔問(wèn)題

????漢諾塔問(wèn)題來(lái)自于古印度,又稱河內(nèi)塔問(wèn)題。大意是:有3根柱子,分別為A、B、C。柱子A上有64個(gè)大小不一的圓形金片,其中最大的金片在最底下,其余的依次疊上去,且一個(gè)比一個(gè)小。柱子B和柱子C上開(kāi)始沒(méi)有金片。要求將柱子A上的金片,移動(dòng)到柱子B或者柱子C上。規(guī)定一次只能移動(dòng)一個(gè)金片,且金片放到柱子上時(shí),大的只能放到小的下面。在此過(guò)程中,可以使用另外一個(gè)柱子作為輔助。
????基本思想:1)將A柱上的n-1個(gè)圓盤(pán)移到B柱上;2)將A柱上的1個(gè)圓盤(pán)移到C柱上;3)將B柱上的n-1個(gè)圓盤(pán)移到C柱上。整個(gè)過(guò)程使用了遞歸算法。
????算法的Java版本:

    int count = 0;

    /**
     * 漢諾塔問(wèn)題
     * @param n 圓盤(pán)數(shù)量
     * @param a 初始柱子名稱,例如A
     * @param b 移動(dòng)的輔助柱子名稱,例如B
     * @param c 移動(dòng)的目標(biāo)柱子名稱,例如C
     */
    void hanoiTower(int n, char a, char b, char c) {
        if (n == 1) {
            System.out.printf("第%d次移動(dòng):\t圓盤(pán)從%c柱移動(dòng)到%c柱\n", ++count, a, c);
        } else {
            hanoiTower(n - 1, a, c, b);
            System.out.printf("第%d次移動(dòng):\t圓盤(pán)從%c柱移動(dòng)到%c柱\n", ++count, a, c);
            hanoiTower(n - 1, b, a, c);
        }
    }

????Kotlin版本:

    var count = 0

    fun hanoiTower(n: Int, a: Char, b: Char, c: Char) {
        if (n == 1) {
            System.out.printf("第%d次移動(dòng):\t圓盤(pán)從%c柱移動(dòng)到%c柱\n", ++count, a, c)
        } else {
            hanoiTower(n - 1, a, c, b)
            System.out.printf("第%d次移動(dòng):\t圓盤(pán)從%c柱移動(dòng)到%c柱\n", ++count, a, c)
            hanoiTower(n - 1, b, a, c)
        }
    }

(5)竊賊問(wèn)題

????大意:有一個(gè)竊賊帶著一個(gè)背包去偷東西,房屋中共有5件物品,其重量和價(jià)值如下:

物品1:6公斤,48元;
物品2:5公斤,40元;
物品3:2公斤,12元;
物品4:1公斤,8元;
物品5:1公斤,7元;

????竊賊希望拿最大價(jià)值的東西,而竊賊的背包最多可裝重量8公斤的物品,那么竊賊應(yīng)該裝上面哪些物品才能達(dá)到要求呢?
????主要思路:

  1. 首先,將物品 i 試著添加到方案中;
  2. 然后判斷是否超重,如果未超,則繼續(xù)添加下一個(gè)物品,重復(fù)第一步;
  3. 如果超重,則將該物品排除在方案之外;并判斷此時(shí)所有未排除物品的價(jià)值是否小于已有最大值;如果滿足,就不必再嘗試后續(xù)物品了。

????Java版本算法:

    class ThingType { //物品結(jié)果
        double value;//價(jià)值
        double weight;//重量
        boolean isSelect;//是否選中到方案
    }

    static double maxValue; //方案最大價(jià)值 
    static double totalValue; //物品總價(jià)值
    static double maxWeight;//竊賊能拿的最大重量
    static int num;//物品數(shù)量
    static boolean[] selectedArray; //臨時(shí)選中數(shù)組

    void thiefSolve(ThingType[] goods, int i, double weight, double value) {
        if (weight + goods[i].weight <= maxWeight) { //判斷當(dāng)前方案i重量是否超出限制
            selectedArray[i] = true;
            if (i < num - 1) { //不是最后一個(gè)物品
                thiefSolve(goods, i + 1, weight + goods[i].weight, value);
            } else {
                for (int k = 0; k < num; k++) {
                    goods[k].isSelect = selectedArray[k];
                }
                maxValue = value;//保存當(dāng)前方案的最大價(jià)值
            }
        }
        selectedArray[i] = false;//取消物品i的選擇狀態(tài)
        if (value - goods[i].value > maxValue) { //還可以繼續(xù)添加物品
            if (i < num - 1) {
                thiefSolve(goods, i + 1, weight, value - goods[i].value);
            } else {
                for (int k = 0; k < num; k++) {
                    goods[k].isSelect = selectedArray[k];
                }
                maxValue = value - goods[i].value;//保存當(dāng)前方案的最大價(jià)值
            }
        }
    }

????Kotlin版本:

    internal inner class ThingType {
        //物品結(jié)果
        var value //價(jià)值
                = 0.0
        var weight //重量
                = 0.0
        var isSelect //是否選中到方案
                = false
    }

    fun thiefSolve(goods: Array<ThingType>, i: Int, weight: Double, value: Double) {
        if (weight + goods[i].weight <= maxWeight) { //判斷當(dāng)前方案i重量是否超出限制
            selectedArray[i] = true
            if (i < num - 1) { //不是最后一個(gè)物品
                thiefSolve(goods, i + 1, weight + goods[i].weight, value)
            } else {
                for (k in 0 until num) {
                    goods[k].isSelect = selectedArray[k]
                }
                maxValue = value //保存當(dāng)前方案的最大價(jià)值
            }
        }
        selectedArray[i] = false //取消物品i的選擇狀態(tài)
        if (value - goods[i].value > maxValue) { //還可以繼續(xù)添加物品
            if (i < num - 1) {
                thiefSolve(goods, i + 1, weight, value - goods[i].value)
            } else {
                for (k in 0 until num) {
                    goods[k].isSelect = selectedArray[k]
                }
                maxValue = value - goods[i].value //保存當(dāng)前方案的最大價(jià)值
            }
        }
    }

    companion object {
        var maxValue //方案最大價(jià)值
                = 0.0
        var totalValue //物品總價(jià)值
                = 0.0
        var maxWeight //竊賊能拿的最大重量
                = 0.0
        var num //物品數(shù)量
                = 0
        var selectedArray //臨時(shí)選中數(shù)組
                : BooleanArray
    }

????只看上述的算法,并不容易理解。還需要看初始化及傳入的數(shù)據(jù),這里用竊賊問(wèn)題的數(shù)據(jù)做幾點(diǎn)說(shuō)明:

  1. 初始化數(shù)據(jù),物品數(shù)量num = 5,goods是一個(gè)size=5的數(shù)組;物品總價(jià)值totalValue = 48 + 40 +12 + 8+ 7 = 115 ,maxValue初始值為0 ,maxWeight = 8;
  2. 開(kāi)始調(diào)用thiefSolve()方法時(shí),傳入的參數(shù)是:thiefSolve(goods, 0, 0.0, totalValue); weight = 0.0,value = totalValue;
  3. 可以看出,weight表示當(dāng)前方案的總重量;value表示當(dāng)前方案的總價(jià)值,不過(guò)它并不是從0開(kāi)始,而是反過(guò)來(lái)計(jì)算,初始值為最大值totalValue,然后隨著 i 的增加減去不在方案中的物品價(jià)值。

(6)雞兔同籠問(wèn)題

????雞兔同籠問(wèn)題于1500年前,由《孫子算經(jīng)》所記載。大致是:在一個(gè)籠子里關(guān)著若干雞和兔,從上面數(shù)有35個(gè)頭,從下數(shù)共有94只腳。問(wèn)雞、兔各有多少只?
????窮舉法的Java版本:

    int chicken;
    int rabbit;
    
    boolean computeChickenRabbit(int head,int foot){
        int i,j;
        for (i =0;i<=head;i++){
            j = head - i;
            
            if (i*2 + 4*j == foot){
                chicken = i;
                rabbit = j;
                return true;
            }
        }
        return false;
    }

????Kotlin版本:

    var chicken = 0
    var rabbit = 0
    fun computeChickenRabbit(head: Int, foot: Int): Boolean {
        var i: Int
        var j: Int
        i = 0
        while (i <= head) {
            j = head - i
            if (i * 2 + 4 * j == foot) {
                chicken = i
                rabbit = j
                return true
            }
            i++
        }
        return false
    }

????還有一種不使用窮舉法的求解算法,它更為簡(jiǎn)單,主要思想是:一只雞有一個(gè)頭和兩只腳,一只兔子有一個(gè)頭和四只腳,那么所有的腳減去2倍的頭,就是多余的兔子的另外兩只腳。Java版本如下:

    void computeChickenRabbit2(int head,int foot,int rabbitNum,int chickenNum){
        rabbitNum = (foot - head*2)/2;
        chickenNum = head - rabbitNum;
    }

(7)八皇后問(wèn)題

????八皇后問(wèn)題是1850年高斯提出的,是一個(gè)經(jīng)典的回溯算法問(wèn)題。大意是:在國(guó)際象棋的64個(gè)單元格中,擺放8個(gè)皇后,使其不能互相攻擊,也就是說(shuō)任意兩個(gè)皇后不能處于同一列、同一行或者同一斜線上。問(wèn)共有多少種擺法?每種是怎么擺的?
????簡(jiǎn)略介紹一下國(guó)際象棋:它起源于亞洲,具體來(lái)自古印度,還是中國(guó),尚有爭(zhēng)論;同中國(guó)象棋一樣,國(guó)際象棋共有32個(gè)棋子,每方16個(gè),沒(méi)有中國(guó)象棋的士、炮,增加了王后。王、王后各1個(gè),車、馬、象各2個(gè),再加8個(gè)兵。
????主要思路:采用遞歸的方式來(lái)求解,思路如下:

  1. 首先在棋盤(pán)的某個(gè)位置放置一個(gè)皇后;
  2. 放置下一個(gè)皇后,判斷該皇后是否與前面的皇后形成互相攻擊,若不互相攻擊,則繼續(xù)放下一個(gè)皇后;若相互攻擊,則調(diào)整位置,再繼續(xù)判斷;
  3. 當(dāng)放置完8個(gè)不形成攻擊的皇后,就形成一個(gè)解,將其輸出。

????八皇后問(wèn)題共有92個(gè)解,下面的算法會(huì)輸出所有的解。為了便于理解,對(duì)輸出結(jié)果做一點(diǎn)說(shuō)明,如輸出:1 6 8 3 7 4 2 5 ,表示第一個(gè)皇后放在第1行,第1列;第二個(gè)皇后放在第2行,第6列,第三個(gè)皇后放在第3行,第8列,······,依此類推。
????Java版本:

/**
 * 八皇后問(wèn)題
 */
public class EightQueen {

    final static int MAX = 8;
    static int[] posArray = new int[MAX];
    static int count;

    public static void main(String[] args) {
        eightQueen(0);
        System.out.println("Solution count : " + count);
    }

    static void eightQueen(int n) {
        if (n == MAX) {
            printSolution();
            return;
        }
        for (int i = 0; i < MAX; i++) {
            posArray[n] = i; //在第n行的i列上放置
            if (!willAttack(n)) {//不會(huì)引起沖突,就繼續(xù)下一行
                eightQueen(n + 1);
            }
        }
    }

    /**
     * 和已有皇后是否相互攻擊
     *
     * @param n
     * @return
     */
    static boolean willAttack(int n) {
        for (int i = 0; i < n; i++) {
            //如果同一列,或者列、行差值相等即斜率相等(對(duì)角線),會(huì)互相攻擊
            if (posArray[i] == posArray[n] || Math.abs(n - i) == Math.abs(posArray[i] - posArray[n])) {
                return true;
            }
        }
        return false;
    }

    /**
     * 打印輸出結(jié)果,下標(biāo)加1進(jìn)行調(diào)整
     */
    static void printSolution() {
        count++;
        for (int i = 0; i < posArray.length; i++) {
            System.out.print((posArray[i] + 1) + " ");
        }
        System.out.println();
    }

}

????Kotlin版本:

object EightQueen {
    const val MAX = 8
    var posArray = IntArray(MAX)
    var count = 0
    @JvmStatic
    fun main(args: Array<String>) {
        eightQueen(0)
        println("Solution count : " + count)
    }

    fun eightQueen(n: Int) {
        if (n == MAX) {
            printSolution()
            return
        }
        for (i in 0 until MAX) {
            posArray[n] = i //在第n行的i列上放置
            if (!willAttack(n)) { //不會(huì)引起沖突,就繼續(xù)下一行
                eightQueen(n + 1)
            }
        }
    }

    /**
     * 和已有皇后是否相互攻擊
     *
     * @param n
     * @return
     */
    fun willAttack(n: Int): Boolean {
        for (i in 0 until n) {
            //如果同一列,或者列、行差值相等即斜率相等(對(duì)角線),會(huì)互相攻擊
            if (posArray[i] == posArray[n] || Math.abs(n - i) == Math.abs(
                    posArray[i] - posArray[n]
                )
            ) {
                return true
            }
        }
        return false
    }

    /**
     * 打印輸出結(jié)果,下標(biāo)加1進(jìn)行調(diào)整
     */
    fun printSolution() {
        count++
        for (i in posArray.indices) {
            print((posArray[i] + 1).toString() + " ")
        }
        println()
    }
}

????初看這個(gè)算法時(shí),覺(jué)得這不是只輸出一個(gè)解嗎?printSolution()也才打印一次???怎么會(huì)輸出92個(gè)解呢?注意這是一個(gè)遞歸調(diào)用,對(duì)于皇后每一行、每一列的可能位置都做了遍歷。就拿放置第一個(gè)皇后為例,它可能出現(xiàn)在任意一列,針對(duì)每一個(gè)可能位置,對(duì)第二個(gè)皇后的放置有不同影響,依此類推。每一種可能,都處理到第8個(gè)皇后為止。所以,這個(gè)算法會(huì)輸出所有的解。

(8)假銀幣問(wèn)題

????大意是這樣的:有8枚銀幣,其中一枚是假幣。但是,從外觀和做工上無(wú)法分辨真假,只知道假幣的重量比真幣稍輕。要求使用一個(gè)天平,以最少的步驟尋找假銀幣?
????主要思路:采用遞歸分治的思想來(lái)求解這個(gè)問(wèn)題,步驟如下:

  1. 將所有的銀幣等分成2份,放在天平的兩邊;
  2. 因?yàn)榧賻诺姆至枯^輕,因此天平較輕的一側(cè)中一定包含假幣;
  3. 再將較輕的一側(cè)中的銀幣等分為兩份,重復(fù)上述的做法;
  4. 直到剩下最后兩枚銀幣,可以直接找出。

????關(guān)于等分,這里有一個(gè)問(wèn)題,如果銀幣總數(shù)是奇數(shù),是沒(méi)法等分的。有一些書(shū)籍上,對(duì)奇偶情況分別判斷,分別處理,使得算法既長(zhǎng),又重復(fù),一點(diǎn)也不簡(jiǎn)練。在此,稍微改動(dòng)一下,并不進(jìn)行等分,而是進(jìn)行3分,分3組。前2組銀幣個(gè)數(shù)相等,第三組個(gè)數(shù)不定。如果前2組重量一樣,說(shuō)明假幣在第三組。
????Java版本:

    /**
     * 假銀幣問(wèn)題
     *
     * @param coinArray 包含假銀幣的數(shù)組
     * @param low       起始位置
     * @param high      結(jié)束位置
     * @param n         個(gè)數(shù)
     * @return 假銀幣的在數(shù)組中的下標(biāo),從0開(kāi)始
     */
    int falseCoin(int[] coinArray, int low, int high, int n) {
        int num1, num2, num3;
        int sum1 = 0, sum2 = 0;
        if (n == 1) {
            return low;
        }
        if (n % 3 == 0) {
            num1 = num2 = n / 3;
        } else {
            num1 = num2 = n / 3 + 1;
        }
        num3 = n - num1 - num2;

        for (int i = 0; i < num1; i++) {
            sum1 += coinArray[i];
        }

        for (int i = 0; i < num2; i++) {
            sum2 += coinArray[i];
        }

        if (sum1 < sum2) { //假幣在第一組
            return falseCoin(coinArray, low, low + sum1 - 1, sum1);
        } else if (sum1 > sum2) { //假幣在第二組
            return falseCoin(coinArray, low + num1, low + sum1 + num2 - 1, sum2);
        } else {//假幣在第三組
            return falseCoin(coinArray, low + num1 + num2, high, num3);
        }
    }

????kotlin版本:

    fun falseCoin(coinArray: IntArray, low: Int, high: Int, n: Int): Int {
        val num1: Int
        val num2: Int
        val num3: Int
        var sum1 = 0
        var sum2 = 0
        if (n == 1) {
            return low
        }
        if (n % 3 == 0) {
            num2 = n / 3
            num1 = num2
        } else {
            num2 = n / 3 + 1
            num1 = num2
        }
        num3 = n - num1 - num2
        for (i in 0 until num1) {
            sum1 += coinArray[i]
        }
        for (i in 0 until num2) {
            sum2 += coinArray[i]
        }
        return if (sum1 < sum2) { //假幣在第一組
            falseCoin(coinArray, low, low + sum1 - 1, sum1)
        } else if (sum1 > sum2) { //假幣在第二組
            falseCoin(coinArray, low + num1, low + sum1 + num2 - 1, sum2)
        } else { //假幣在第三組
            falseCoin(coinArray, low + num1 + num2, high, num3)
        }
    }

(9)三色旗問(wèn)題

????大意是這樣的:在一條繩子上掛有白、紅、藍(lán)三種顏色的多面旗子,這些旗子的排列是無(wú)序的。現(xiàn)要將繩子上的旗子按藍(lán)、白、紅三種顏色進(jìn)行歸類排列,每次可以調(diào)換兩個(gè)旗子,問(wèn)如何采用最少的步驟來(lái)完成?
????主要思路:使用三個(gè)變量blue、white、red來(lái)表示旗子的順序下標(biāo),初始時(shí),blue、white均為0,red為旗子總數(shù)減1;在[ 0, blue-1]放置藍(lán)色旗子,在[ blue, white-1 ]放置白色旗子,在[ red+1, length-1]放置紅色旗子;[ white, red ]這個(gè)區(qū)間放置還未處理的旗子。每一次都處理變量write指向位置的元素,分3種情況:

  1. 如果white所在的位置是白旗,直接white++ ;
  2. 如果white所在的位置是藍(lán)旗,將white處的元素與blue對(duì)調(diào),然后blue++、white++;
  3. 如果white所在的位置是紅旗,將white處的元素與red對(duì)調(diào),然后red-- ;

????Java版本:

    /**
     * 三色旗問(wèn)題
     *
     * @param color 由'b'、'w'、'r'組成的無(wú)序字符數(shù)組
     */
    void threeFlags(char[] color) {
        int blue, white, red;
        blue = white = 0;
        red = color.length - 1;

        while (color[white] == 'b') { //藍(lán)旗
            blue++;
            white++;
        }
        while (color[red] == 'r') { //紅旗
            red--;
        }
        while (white <= red) {
            if (color[white] == 'r') {
                swap(color,white,red);
                red--;

                while (color[red] == 'r'){
                    red--;
                }
            }

            if (color[white] == 'w'){
                white++;
            }

            if (color[blue] == 'b'){
                swap(color,white,blue);
                white++;
                blue++;
            }
        }
    }

    void swap(char[] charArray, int x, int y) {
        char tmp = charArray[x];
        charArray[x] = charArray[y];
        charArray[y] = tmp;
    }

????Kotlin版本:

    /**
     * 三色旗問(wèn)題
     *
     * @param color 由'b'、'w'、'r'組成的無(wú)序字符數(shù)組
     */
    fun threeFlags(color: CharArray) {
        var blue: Int
        var white: Int
        var red: Int
        white = 0
        blue = white
        red = color.size - 1
        while (color[white] == 'b') { //藍(lán)旗
            blue++
            white++
        }
        while (color[red] == 'r') { //紅旗
            red--
        }
        while (white <= red) {
            if (color[white] == 'r') {
                swap(color, white, red)
                red--
                while (color[red] == 'r') {
                    red--
                }
            }
            if (color[white] == 'w') {
                white++
            }
            if (color[blue] == 'b') {
                swap(color, white, blue)
                white++
                blue++
            }
        }
    }

    fun swap(charArray: CharArray, x: Int, y: Int) {
        val tmp = charArray[x]
        charArray[x] = charArray[y]
        charArray[y] = tmp
    }

(10)漁夫捕魚(yú)問(wèn)題

????大意是這樣的:某天,A、B、C、D、E 五個(gè)漁夫合伙捕了很多魚(yú),天黑后到岸邊各自休息。第二天早晨,漁夫A第一個(gè)醒來(lái),他將魚(yú)分作五份,把多余的一條扔回河中,拿自己的一份回家去了。漁夫B第二個(gè)醒來(lái),也將魚(yú)分作五份,扔掉多余的一條,拿走自己的一份······以此類推,直到最后一個(gè)漁夫E醒來(lái),也將魚(yú)分作五份,扔掉多余的一條,拿走自己的一份。問(wèn)五漁夫至少捕到多少條魚(yú)呢?
????先來(lái)看看一個(gè)錯(cuò)誤的分析,這種分析非常地誤導(dǎo)人。網(wǎng)絡(luò)和書(shū)籍上的某些作者,都陷入了這個(gè)陷阱中,包括開(kāi)始的自己。

  1. 每個(gè)漁夫醒來(lái)的時(shí)候,魚(yú)的數(shù)量都應(yīng)該是5的倍數(shù)再加1。先來(lái)看看最后一個(gè)漁夫E,魚(yú)的數(shù)量最少應(yīng)該是6。在他扔掉一條魚(yú)之后,仍然可以平均分為5份;
  2. 漁夫D醒來(lái)的時(shí)候,魚(yú)的數(shù)量應(yīng)該是:6*5 + 1 = 31;
  3. 漁夫C醒來(lái)的時(shí)候,魚(yú)的數(shù)量應(yīng)該是:31*5+1 = 156;
  4. 漁夫B醒來(lái)的時(shí)候,魚(yú)的數(shù)量應(yīng)該是:156*5+1 = 781;
  5. 漁夫A醒來(lái)的時(shí)候,魚(yú)的數(shù)量應(yīng)該是:781*5 + 1 = 3906;那么,漁夫至少合伙捕到3906條魚(yú)。

????由上得到,這是一個(gè)遞推的式子,公式:S_{n-1} = 5S_n+1;

????這個(gè)分析乍一看,好像沒(méi)什么問(wèn)題。但拿筆在草稿本上算一算、畫(huà)一畫(huà),就感到哪里不對(duì)勁了。讓我們先來(lái)跟著它的分析,找到矛盾之處。

????先假設(shè)上述分析沒(méi)有問(wèn)題,漁夫E醒來(lái)后的魚(yú)數(shù)是6,漁夫D醒來(lái)后的魚(yú)數(shù)是31,只看這兩個(gè)數(shù)據(jù)就夠了。從問(wèn)題中,漁夫D比漁夫E先醒來(lái),他將魚(yú)數(shù)分作5份,扔掉多余的一條,拿走自己的一份。31條魚(yú),扔掉一條,還有30條,分作5份,每份6條,他拿走自己的一份,剩下應(yīng)該有24條。隨后,漁夫E醒來(lái),此時(shí)魚(yú)的數(shù)量應(yīng)該是24,并不是6。這就是矛盾所在,所以上述分析是錯(cuò)誤的,得出的公式也是錯(cuò)誤的。

????由上可知,如果漁夫E醒來(lái)后魚(yú)的數(shù)量是6,漁夫D醒來(lái)后的魚(yú)數(shù)肯定不是31,那么就想問(wèn)一問(wèn),漁夫D醒來(lái)后的正確魚(yú)數(shù)是多少?

????假設(shè)漁夫D醒來(lái)后的正確魚(yú)數(shù)是N,那么從問(wèn)題描述中可以得到:N - 1 - \frac{N-1}{5} = 6 ,由它得出N = \frac{15}{2} + 1;這顯然不對(duì),因?yàn)轸~(yú)的條數(shù)不可能是小數(shù)。所以,很顯然,漁夫E醒來(lái)后的魚(yú)數(shù)不可能為6。初始感覺(jué)是錯(cuò)誤的。

????我們先來(lái)推導(dǎo)一下正確的公式,將問(wèn)題倒過(guò)來(lái)遞推,以漁夫E醒來(lái)后的魚(yú)數(shù)作為第一項(xiàng)。

????假設(shè)S_n表示第n個(gè)漁夫醒來(lái)后的魚(yú)數(shù),那么:
???????? S_{n+1} - 1 - \frac{S_{n+1}-1}{5} = S_{n}
????由此可得:
???????? S_{n+1} = \frac{5*S_{n}}{4} + 1 , n\epsilon{1,2,3,4,5}

????在本問(wèn)題中,因?yàn)橹挥?個(gè)漁夫,第一項(xiàng)S_1,第二項(xiàng)S_2,第三項(xiàng)S_3,第四項(xiàng)S_4,都必須能被4整除,而第五項(xiàng)S_5無(wú)此要求。
????那么,現(xiàn)在第一項(xiàng)S_1的值是多少呢?顯然不能再靠感覺(jué)了。由上可知:

  1. S_1的值必須被4整除,因此它的最小值為4;
  2. 因?yàn)镾_2、S_3、S_4也必須被4整除;所以如果發(fā)現(xiàn)有不能被整除的情況,那么可以推斷,S_1的值不對(duì);
  3. 重新設(shè)置S_1的值,直到滿足整除條件為止;
  4. S_5可以不被4整除。

????Java版本算法:

/**
 * 5漁夫捕魚(yú)問(wèn)題
 */
public class FishProblem {
    static int initValue = 4;

    /**
     * 5漁夫捕魚(yú)問(wèn)題求解
     * @return 漁夫捕魚(yú)問(wèn)題的最少魚(yú)數(shù)
     */
    static int fiveFishmanProblem(){
        int sum = initValue;

        while (true){
            for (int i=0;i<4;i++){ //只需要處理前4個(gè)值,保證被4整除
                if (sum % 4 != 0){
                    break;
                }
                sum = sum *5/4+1;
            }

            if (sum%4 == 0){
                break;
            } else {
                initValue += 4; //重置s1的值
                sum = initValue;
            }
        }
        sum = sum *5/4+1; //第5個(gè)值單獨(dú)計(jì)算,不需要滿足被4整除
        return sum;
    }

    public static void main(String[] args) {
        int result = fiveFishmanProblem();
        System.out.println("result = "+result +" , S1 = "+ (initValue *5/4 +1));
    }

????運(yùn)行這個(gè)程序,可以得出:

result = 3121 , S1 = 1276

????從結(jié)果得知,漁夫E醒來(lái)時(shí),魚(yú)的數(shù)量是1276,他扔了一條,帶走了255條;五漁夫總共捕獲了至少3121條。
????Kotlin版本:

/**
 * 5漁夫捕魚(yú)問(wèn)題
 */
object FishProblem {
    var initValue = 4

    /**
     * 5漁夫捕魚(yú)問(wèn)題求解
     * @return 漁夫捕魚(yú)問(wèn)題的最少魚(yú)數(shù)
     */
    fun fiveFishmanProblem(): Int {
        var sum = initValue
        while (true) {
            for (i in 0..3) { //只需要處理前4個(gè)值,保證被4整除
                if (sum % 4 != 0) {
                    break
                }
                sum = sum * 5 / 4 + 1
            }
            if (sum % 4 == 0) {
                break
            } else {
                initValue += 4 //重置s1的值
                sum = initValue
            }
        }
        sum = sum * 5 / 4 + 1 //第5個(gè)值單獨(dú)計(jì)算,不需要滿足被4整除
        return sum
    }

    @JvmStatic
    fun main(args: Array<String>) {
        val result = fiveFishmanProblem()
        println("result = " + result + " S1 = " + (initValue * 5 / 4 + 1))
    }
}

????讓我們繼續(xù)深入一下,上述算法是否可以擴(kuò)充到任意個(gè)漁夫?很顯然,簡(jiǎn)單的傳遞一個(gè)漁夫數(shù)量參數(shù)是不行的,因?yàn)椴煌臐O夫數(shù)量,得出的推導(dǎo)公式是不同的。讓我們來(lái)分別考慮。
????二漁夫情況:這個(gè)很簡(jiǎn)單,S_1=3,S_2=7,兩個(gè)人最少捕獲7條魚(yú)。
????三漁夫情況:可以推導(dǎo)獲得以下公式:

????假設(shè)S_n表示第n個(gè)漁夫醒來(lái)后的魚(yú)數(shù)(n\epsilon{1,2,3}),那么:
???????? S_{n+1} - 1 - \frac{S_{n+1}-1}{3} = S_{n}
????由此可得:
???????? S_{n+1} = \frac{3*S_{n}}{2} + 1 , n\epsilon{1,2,3}

????可以得出這樣的結(jié)論:S_1、S_2必須被2整除,S_3無(wú)此限制。
????Java版本:

/**
 * 3漁夫捕魚(yú)問(wèn)題
 */
public class FishProblem {
    static int initValue = 2;

    static int threeFishmanProblem(){
        int sum = initValue;

        while (true){
            for (int i=0;i<2;i++){ //只需要處理前2個(gè)值,保證被2整除
                if (sum % 2 != 0){
                    break;
                }
                sum = sum *3/2+1;
            }

            if (sum%2 == 0){
                break;
            } else {
                initValue += 2; //重置s1的值
                sum = initValue;
            }
        }
        sum = sum *3/2+1; //第3個(gè)值單獨(dú)計(jì)算,不需要滿足被2整除
        return sum;
    }

    public static void main(String[] args) {
        int result = threeFishmanProblem();
        System.out.println("result = "+result +" , S1 = "+ (initValue *3/2 +1));
    }
}

????運(yùn)行此程序,得到的結(jié)果是:

result = 25 , S1 = 10

????可以得知,三漁夫最少捕獲25條魚(yú)。

????類似的,四漁夫推導(dǎo)公式:
???????? S_{n+1} - 1 - \frac{S_{n+1}-1}{4} = S_{n}
????由此可得:
???????? S_{n+1} = \frac{4*S_{n}}{3} + 1 , n\epsilon{1,2,3,4}
????它的Java實(shí)現(xiàn)就省略了,和上面的大同小異。

????綜上所述,可以得到m個(gè)漁夫推導(dǎo)公式:
???????? S_{n+1} - 1 - \frac{S_{n+1}-1}{m} = S_{n}
????由此可得:
???????? S_{n+1} = \frac{m*S_{n}}{m-1} + 1 , n\epsilon{1,2,......,m}

????它的Java代碼實(shí)現(xiàn):

/**
 * m漁夫捕魚(yú)問(wèn)題
 */
public class FishProblem {
    static int initValue = 0;

    /**
     * m漁夫捕魚(yú)問(wèn)題
     *
     * @return
     */
    static int mFishman(int m) {
        if (m == 1) {
            return 3;
        }
        initValue = m - 1;
        int sum = initValue;

        while (true) {
            for (int i = 0; i < m - 1; i++) { //只需要處理前m-1個(gè)值,保證被m-1整除
                if (sum % (m - 1) != 0) {
                    break;
                }
                sum = sum * m / (m - 1) + 1;
            }

            if (sum % (m - 1) == 0) {
                break;
            } else {
                initValue += (m - 1); //重置s1的值
                sum = initValue;
            }
        }
        sum = sum * m / (m - 1) + 1; //第m個(gè)值單獨(dú)計(jì)算,不需要滿足被m-1整除
        return sum;
    }

    public static void main(String[] args) {
        int m = 6; //隨便設(shè)置
        int result = mFishman(m);
        System.out.println("result = "+result +" S1 = "+ (initValue *m/(m-1) +1));
    }
}

????Kotlin版本就省略了,畢竟這一小節(jié)已經(jīng)很長(zhǎng)了。

(11)愛(ài)因斯坦的階梯

????大意是這樣的:有一個(gè)樓,其兩層之間有一個(gè)很長(zhǎng)的階梯。如果一個(gè)人每步上2階,最后剩1階;如果每步上3階,最后剩2階;如果每步上5階,最后剩4階;如果每步上6階,最后剩5階;如果每步上7階,最后剛好一階不剩。問(wèn)這個(gè)階梯至少有多少階?
????主要思路:階數(shù)與上面這些數(shù)字,存在特定的關(guān)系,比如是7的倍數(shù)、奇數(shù)等,通過(guò)遍歷,找到滿足這些關(guān)系的數(shù)。
????Java版本:

    int computeEinsteinStairs() {
        int count = 7;
        for (int i = 0; i < 100; i++) {
            if (count % 2 == 1 && count % 3 == 2 && count % 5 == 4 && count % 6 == 5) {
                break;
            }
            count = 7 * (i + 1);
        }
        return count;
    }

????Kotlin版本:

    fun computeEinsteinStairs(): Int {
        var count = 7
        for (i in 0..99) {
            if (count % 2 == 1 && count % 3 == 2 && count % 5 == 4 && count % 6 == 5) {
                break
            }
            count = 7 * (i + 1)
        }
        return count
    }

(12)常勝將軍問(wèn)題

????大意是這樣的:甲乙兩人玩輪流抽火柴的游戲,共有21根火柴。每個(gè)人每次最多取4根火柴,最少取1根,取到最后一根火柴的判輸。甲讓乙先抽,結(jié)果每次都是甲贏。這是為什么?
????分析:把問(wèn)題倒過(guò)來(lái)看,不從21開(kāi)始分析,以乙拿火柴時(shí)的可能數(shù)量倒序分析:

  1. 如果乙拿火柴時(shí)火柴數(shù)為1,那么按照規(guī)則,乙必輸,甲要爭(zhēng)取這種情況;
  2. 如果乙拿火柴時(shí)火柴數(shù)是2-5,那么他總可以留下一根,則乙必勝,甲要避免這種情況;

????分析火柴數(shù)大于5的情況。如果乙取火柴時(shí),火柴數(shù)是6,那么:

  1. 乙取1根,剩5根,甲取4根,留1根,甲勝;
  2. 乙取2根,剩4根,甲取3根,留1根,甲勝;
  3. 乙取3根,剩3根,甲取2根,留1根,甲勝;
  4. 乙取4根,剩2根,甲取1根,留1根,甲勝;

????可以看到:只要甲取的火柴數(shù),等于5減去乙取的火柴數(shù),那么甲必勝。也就是說(shuō),如果火柴數(shù)是6,遵照這個(gè)規(guī)則,那么誰(shuí)先拿,誰(shuí)必輸。
????由此可以推斷,如果火柴數(shù)是7、8、9、10,那么先拿的一方可以讓剩下的火柴數(shù)等于6,從而獲勝。即誰(shuí)先拿,誰(shuí)必勝。
????再考慮火柴數(shù)是11,此時(shí)不論拿幾根火柴,都會(huì)讓剩下的數(shù)量在區(qū)間[ 7, 10 ],必輸。即誰(shuí)先拿,誰(shuí)必輸。
????同樣的道理,如果火柴數(shù)在區(qū)間[ 12, 16 ],那么先拿者總可以讓剩下的火柴數(shù)等于11,必勝。即誰(shuí)先拿,誰(shuí)必勝。

????綜上所述,可以得到更一般的規(guī)律:如果火柴初始數(shù)量等于(5n+1),n\epsilon {1,2,3,......},那么誰(shuí)先拿,誰(shuí)必輸;如果不等,那么誰(shuí)先拿,誰(shuí)必勝。
????實(shí)現(xiàn)方案:如果火柴初始數(shù)量S=(5n+1),先拿者拿走m個(gè),其中m\epsilon {1,2,3,4},不論m是多少,后拿者只要拿走(5 - m)個(gè),就可以讓剩余火柴數(shù)朝著1遞進(jìn),從而實(shí)現(xiàn)必勝的目標(biāo),即先拿必輸。如果火柴初始數(shù)量S\neq(5n+1),那么先找到一個(gè)值K,K滿足\begin{cases}K=(5n+1) \\K<S \\K+5>S \end{cases},然后先拿者只要拿走( S - K )根火柴,就可以保證必勝,即先拿必勝。

????回歸到本小節(jié)最開(kāi)始的問(wèn)題,火柴的初始值是21,滿足(5n+1),其中n=4,所以根據(jù)上面的規(guī)律:先拿必輸。乙先拿,乙必輸,這也是甲每次都贏的原因。
????Java版本:

import java.util.Random;

/**
 * 常勝將軍問(wèn)題
 */
public class AlwaysWinQ {
    static String personA = "甲";
    static String personB = "乙";

    static void alwaysWinQ(int matchNum) {

        int n = (matchNum - 1) / 5;
        int r = (matchNum - 1) % 5;
        int leftNum = matchNum;
        int bGetNumPerTime, aGetNumPerTime;
        int count = 0;
        Random random = new Random();
        if (r == 0) {
            System.out.println("滿足(5n+1),結(jié)論:先拿者必輸。實(shí)際過(guò)程如下:");
            while (leftNum > 1) {
                //乙先拿,隨機(jī)拿幾個(gè)
                bGetNumPerTime = 1 + random.nextInt(4);
                aGetNumPerTime = 5 - bGetNumPerTime;
                System.out.println("第" + (++count) + "次循環(huán)," + personB + "拿走了" + bGetNumPerTime
                        + "根火柴,剩下" + (leftNum - bGetNumPerTime) + "根," + personA + "拿走了" 
                        + aGetNumPerTime + "根火柴,剩下" + (leftNum - bGetNumPerTime - aGetNumPerTime));
                leftNum -= aGetNumPerTime + bGetNumPerTime;
            }
            if (leftNum == 1) {
                System.out.println("最后一次循環(huán),剩余火柴數(shù)量為1," + personB + "拿走," + personA + "獲勝!");
            }
        } else {
            System.out.println("不滿足(5n+1),結(jié)論:先拿者必勝。實(shí)際過(guò)程如下:");
            int bGetNumFirstTime = matchNum - (5 * n + 1);
            System.out.println(personB + "先拿,拿走" + bGetNumFirstTime + "根火柴。剩余:" + (leftNum - bGetNumFirstTime));
            leftNum -= bGetNumFirstTime;
            while (leftNum > 1) {
                //乙已經(jīng)拿過(guò)了,現(xiàn)在輪到甲拿,數(shù)量隨機(jī)
                aGetNumPerTime = 1 + random.nextInt(4);
                bGetNumPerTime = 5 - aGetNumPerTime;
                System.out.println("第" + (++count) + "次循環(huán)," + personA + "拿走了" + aGetNumPerTime
                        + "根火柴,剩下" + (leftNum - aGetNumPerTime) + "根," + personB + "拿走了" 
                        + bGetNumPerTime + "根火柴,剩下" + (leftNum - bGetNumPerTime - aGetNumPerTime));
                leftNum -= aGetNumPerTime + bGetNumPerTime;
            }
            if (leftNum == 1) {
                System.out.println("最后一次循環(huán),剩余火柴數(shù)量為1," + personA + "拿走," + personB + "獲勝!");
            }
        }
    }

    public static void main(String[] args) {
        int matchNum = 21;
        System.out.println("輪流抽火柴游戲,火柴總數(shù):" + matchNum + ",規(guī)則:" + personB + "先拿,每次最少拿1根,最多拿4根;拿到最后一根的判輸。");
        System.out.println();
        int repeatNum = 5;
        System.out.println("重復(fù)玩" + repeatNum + "次,看看結(jié)果是否一致");
        for (int i = 0; i < repeatNum; i++) {
            System.out.println("********************");
            alwaysWinQ(matchNum);
            System.out.println();
        }

    }
}

????當(dāng)matchNum=21時(shí),以下是部分輸出:

????輪流抽火柴游戲,火柴總數(shù):21,規(guī)則:乙先拿,每次最少拿1根,最多拿4根;拿到最后一根的判輸。
????滿足(5n+1),結(jié)論:先拿者必輸。實(shí)際過(guò)程如下:
????第1次循環(huán),乙拿走了1根火柴,剩下20根,甲拿走了4根火柴,剩下16
????第2次循環(huán),乙拿走了2根火柴,剩下14根,甲拿走了3根火柴,剩下11
????第3次循環(huán),乙拿走了3根火柴,剩下8根,甲拿走了2根火柴,剩下6
????第4次循環(huán),乙拿走了3根火柴,剩下3根,甲拿走了2根火柴,剩下1
????最后一次循環(huán),剩余火柴數(shù)量為1,乙拿走,甲獲勝!

????當(dāng)matchNum=22時(shí),以下是部分輸出:

????輪流抽火柴游戲,火柴總數(shù):22,規(guī)則:乙先拿,每次最少拿1根,最多拿4根;拿到最后一根的判輸。
????不滿足(5n+1),結(jié)論:先拿者必勝。實(shí)際過(guò)程如下:
????乙先拿,拿走1根火柴。剩余:21
????第1次循環(huán),甲拿走了1根火柴,剩下20根,乙拿走了4根火柴,剩下16
????第2次循環(huán),甲拿走了4根火柴,剩下12根,乙拿走了1根火柴,剩下11
????第3次循環(huán),甲拿走了4根火柴,剩下7根,乙拿走了1根火柴,剩下6
????第4次循環(huán),甲拿走了2根火柴,剩下4根,乙拿走了3根火柴,剩下1
????最后一次循環(huán),剩余火柴數(shù)量為1,甲拿走,乙獲勝!

????從輸出結(jié)果,驗(yàn)證了上面總結(jié)的規(guī)律。
????鑒于本小節(jié)有點(diǎn)長(zhǎng),Kotlin版省略。

(13)新郎和新娘問(wèn)題

????大意是這樣的:有三對(duì)新婚夫婦參加集體婚禮,三個(gè)新郎為A、B、C,三個(gè)新娘為X、Y、Z。主持人一時(shí)忘了誰(shuí)應(yīng)該和誰(shuí)結(jié)婚,于是他便問(wèn)這6個(gè)人中的三個(gè)。這三個(gè)人開(kāi)起了玩笑,全都給了錯(cuò)誤的答案,如下:

  1. 新郎A說(shuō)他將和新娘X結(jié)婚;
  2. 新娘X說(shuō)她將和新郎C結(jié)婚;
  3. 新郎C說(shuō)他將和新娘Z結(jié)婚。

????到底誰(shuí)應(yīng)該和誰(shuí)結(jié)婚呢?
????主要思路:遍歷可能的組合,并將錯(cuò)誤的答案排除。
????分析:這個(gè)問(wèn)題,通過(guò)直觀分析就可以獲得答案。CX、CZ都是錯(cuò)誤的組合,所以只能是CY。AX是錯(cuò)誤的組合,所以只能是AZ,剩下一個(gè)BX。結(jié)果是AZ、BX、CY這種結(jié)婚方案。但是如果夫婦數(shù)量擴(kuò)展了,直觀分析就變得麻煩了。借助于程序,處理起來(lái)就要方便得多。首先,還是基于3對(duì)夫婦進(jìn)行編程,后續(xù)再擴(kuò)展。
????有些算法使用了2個(gè)數(shù)組,分別是新郎、新娘數(shù)組,bridegroomArray[] = {'A','B','C'},brideArray[] = {'X','Y','Z'}。這里簡(jiǎn)化一下,只使用一個(gè)新娘數(shù)組brideArray[] = {'X','Y','Z'}。新郎的順序A、B、C設(shè)定不變,對(duì)應(yīng)的下標(biāo)index固定為0,1,2。i、j、k分別代表著A、B、C對(duì)應(yīng)新娘的下標(biāo),可以得出下面的條件:

brideArray[ i ] \neq 'X' ;
brideArray[ k ] \neq 'X';
brideArray[ k ] \neq 'Z';

????Java版本:

    static void groomBrideQ() {
        char[] brideArray = {'X', 'Y', 'Z'};
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                for (int k = 0; k < 3; k++) {
                    if (i != j && j != k && k != i) {
                        if (brideArray[i] != 'X' && brideArray[k] != 'X' && brideArray[k] != 'Z') {
                            System.out.println("A*" + brideArray[i]);
                            System.out.println("B*" + brideArray[j]);
                            System.out.println("C*" + brideArray[k]);
                        }
                    }
                }
            }
        }
    }

????輸出:

A*Z
B*X
C*Y

????Kotlin版:

    fun groomBrideQ() {
        val brideArray = charArrayOf('X', 'Y', 'Z')
        for (i in 0..2) {
            for (j in 0..2) {
                for (k in 0..2) {
                    if (i != j && j != k && k != i) {
                        if (brideArray[i] != 'X' && brideArray[k] != 'X' && brideArray[k] != 'Z') {
                            println("A*" + brideArray[i])
                            println("B*" + brideArray[j])
                            println("C*" + brideArray[k])
                        }
                    }
                }
            }
        }
    }

????擴(kuò)展到4對(duì)新婚夫婦,新郎為A、B、C、D,新娘為X、Y、Z、W。如果不額外添加錯(cuò)誤條件,還用上面的,是有很多個(gè)組合的。下面的算法額外添加了3個(gè)錯(cuò)誤條件,使得組合只有一個(gè)。錯(cuò)誤條件的文字描述就省略了,看代碼應(yīng)該可以推測(cè)出來(lái)。
????Java版本:

    static void groomBrideQ() {
        char[] brideArray = {'X', 'Y', 'Z', 'W'};
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                for (int k = 0; k < 4; k++) {
                    for (int l = 0; l < 4; l++) {
                        if (i != j && j != k && k != l && l != i && i != k && i != l && j != l) {
                            if (brideArray[i] != 'X' && brideArray[k] != 'X' && brideArray[k] != 'Z'
                                    && brideArray[i] != 'W' && brideArray[j] != 'X' && brideArray[k] != 'W') {
                                System.out.println("A*" + brideArray[i]);
                                System.out.println("B*" + brideArray[j]);
                                System.out.println("C*" + brideArray[k]);
                                System.out.println("D*" + brideArray[l]);
                            }
                        }
                    }

                }
            }
        }
    }

????輸出:

A*Z
B*W
C*Y
D*X

(14)三色球問(wèn)題

????大意是這樣的:一個(gè)黑盒中放著3個(gè)紅球、3個(gè)黃球和6個(gè)綠球,如果從其中取出8個(gè)球,那么取出的球中有多少種顏色搭配?
????分析:這是一個(gè)經(jīng)典的排列組合問(wèn)題,下面分別從數(shù)學(xué)和編程角度來(lái)分析它。
????數(shù)學(xué)角度,以紅球作為出發(fā)點(diǎn),有下面幾種可能:

  1. 無(wú)紅球:只有兩種搭配,即2黃6綠,或者3黃5綠,記為C_1 = 2;
  2. 1個(gè)紅球:?jiǎn)栴}變成從3黃球6綠球中取出7球,那么至少有一個(gè)黃球,故有3種搭配,記為C_2 = 3;
  3. 2個(gè)紅球:?jiǎn)栴}變成從3黃球6綠球中取出6球,那么可以沒(méi)有黃球,比2)要多一種可能,故有4種搭配,記為C_3 = 4;
  4. 3個(gè)紅球:?jiǎn)栴}變成從3黃球6綠球中取出5球,同3)一樣,可以沒(méi)有黃球,也有4種搭配,記為C_4 = 4;

????因此,得出的搭配總數(shù)是:C_1 + C_2 + C_3 + C_4 = 13 ,即取出的球中有13種顏色搭配。
????作為人,很容易在每種情況里,把8這個(gè)要求時(shí)刻放到腦海里。計(jì)算機(jī)雖然可以處理龐大的數(shù)據(jù),但要求它每種情況都記住并分別計(jì)算,那代碼寫(xiě)起來(lái)就要冗長(zhǎng)、困難得多,可讀性也差。所以在編程時(shí),更傾向于通用的方式來(lái)求解??梢远嗵幚硪恍?shù)據(jù),范圍也可以更大,只要能用通用的條件來(lái)過(guò)濾這些數(shù)據(jù),最終求得正確結(jié)果就行。
????那么,現(xiàn)在從編程角度分析三色球問(wèn)題。先不考慮8這個(gè)總數(shù)要求,單論每種球的極限可能:紅球有4種可能(0、1、2、3)、黃球有4種可能、綠球有7種可能。針對(duì)這些可能的各種組合(4*4*7=112種),再用總數(shù)為8作為過(guò)濾條件,就可以得出正確的結(jié)果。舉個(gè)例子,0紅0黃0綠這種方案也在這112種可能當(dāng)中,但它顯然不滿足條件,被過(guò)濾掉。對(duì)計(jì)算機(jī)來(lái)說(shuō),多一定數(shù)量的計(jì)算,無(wú)傷大雅。
????Java版本:

/**
 * 3色球問(wèn)題
 */
public class ThreeBallQ {

    static int threeBallQ(int red, int yellow, int green, int n) {
        int count = 0;
        for (int i = 0; i <= red; i++) {
            for (int j = 0; j <= yellow; j++) {
                for (int k = 0; k <= green; k++) {
                    if (i + j + k == n) {
                        count++;
                    }
                }
            }
        }
        return count;
    }

    public static void main(String[] args) {
        int count = threeBallQ(3, 3, 6, 8);
        System.out.println("有" + count + "種搭配方案");
    }
}

????輸出結(jié)果:

有13種搭配方案

????結(jié)果符合預(yù)期。但從算法實(shí)現(xiàn)來(lái)看,并沒(méi)有像數(shù)學(xué)那樣分不同情況分別計(jì)算。而是使用一種通用的方式,在極限可能的范圍并滿足特定的條件下求解。這顯示出了人腦思維和編程思維的極大不同。這一點(diǎn)在計(jì)算機(jī)世界是隨處可見(jiàn),面對(duì)編程時(shí)常常需要將人腦思維切換到編程思維。希望以本小節(jié)為例,可以加深你的理解。
????Over !

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容