一篇算法講解的註解

前言

從公式到算法以前的完整路徑應該是:
數學公式->中文公式->中文算法->英文算法php

偶然看到一篇算法文章,講解了百度2016校園招聘之編程題的核心算法思路,我根據它又整理出本身的解題思路。算法

第一題

題目在原文中能夠找到,這裏就直接講解具體的是如何作的。
首先,輸入行號(整數n),接着是每行的數據
那能夠定義函數爲編程

/**
 * 解題函數
 * @param  {int}    $row_num 行號
 * @param  {Array}  $rows    每行的數據,其元素爲string型,即一行數據
 * @return {Array}           每行數據在總排列中第幾小,形如:[1, 3, 454]
 */
function question($row_num, $rows = []) {}

讀取到了數據,就要一條一條先由string轉化爲數組,(在PHP中,string類型能夠直接當數組使用,就能夠免掉這一步)再計算其序號。咱們將結果存儲在數組$orders中。數組

如今咱們拿到了函數

$row_num = 3;
$rows = [
    'abcdefghijkl',
    'bcdefghaijkl',
    'bcdefghijkla',
];

$orders = [];
foreach ($rows as $row) {
    $orders[] = get_order($row);
}

get_order方法即計算一行數據序號的方法。優化

依照公式,對於每一行數據,咱們首先仍是要遍歷該數據中每一個字符,再獲取該字符在整個ui

$row = 'abcdefghijkl';

序號= a在字段表中的大小 a在字符串的位號的階乘 + b在字段表中的大小 b在字符串的位號的階乘 + ... l在字段表中的大小 * l在字符串的位號的階乘code

咱們這裏遍歷這個字符串,計算這個字母:
在字段表中的大小 * 在字符串的位號的階乘排序

最後將全部字母的值都加起來,便是該字符串的序號。遞歸

$order = 0;
for($i = 1; $i =< 12; $i ++) {
    $order += get_rank($row[$i]) * get_jeicheng(12 - $i);
}

僞代碼:

$序號 = 0;
for($i = 1; $i =< 12; $i ++) {
    $序號 += 當前字母在字母表中的排序 * 字母在字符中的位號階乘;
}

這裏有一個優化點,即求階乘。
原文中做者是直接計算階乘,但其實咱們已經能夠肯定,要用到的也就是十二個階乘值,因此其實能夠只計算一遍,將這十個階乘值都存在數組中,實際計算過程當中要用到哪一個直接取就好了。

第二道題

第二道題裏,大部分流程都和第一道題同樣,只有核心算法那裏不一樣。

定義函數爲

/**
 * 解題函數
 * @param  {int} $row_num 行號
 * @param  {Array}  $rows 每行數據在總排列中第幾小,形如:[1, 3, 454]
 * @return {Array}        每行的數據,其元素爲string型,形如:['abcdefghijkl', 'abcdeghijklf']
 */
function question2($row_num, $orders = []) {}

如今咱們拿到了

$row_num = 3;
$orders = [
    1
    3,
    454,
];

$rows = [];
foreach ($orders as $order) {
    $rows[] = get_row($order);
}

get_row($order);

得到了單個order。假設是 454 ,則根據公式,其對應字母爲

題目中是12個字符,範圍太大,原文中爲了說明公式,將範縮小至4個字符。
假設只有abcd四個字符,單個order爲9時,其對應字母爲bcda。計算公式爲
一、9 / 6 = 1 ... 3
二、3 / 2 = 1 ... 1
三、1 / 1 = 1 ... 0

因此咱們可獲得計算公式了:
從最高位開始,咱們來將數字還原爲字段
【abcd的排序都是從0位開始排】
1*3! + 1*2! + 1*1! + 0*0! = 9
咱們如今來分解以上公式的意義,先來看1*3!
它的中文意義爲
【abcd中第1位大的字母】*3!
abcd中,a排第0位、b排第1位、c排第2位、d排第3位,因此這裏就是b做爲字符串最高位的字母。【b***】
接下來來看1*2!
因爲abcd四個字母中b已經肯定下來了,因此如今它的中文意義爲 【剩餘字母中第1位大的字母】,即【acd中第1位大的字母】*2!
acd中,a排第0位、c排第1位、d排第2位,因此這裏就是c做爲字符串第3位的字母。
【bc**】
好了,道理都懂了,接下來依次類推,咱們便可將字符串還原爲【bcda】

因此,如今換成12位字母,算法要如何處理呢?
首先,假設咱們拿到了order = 456;
接着,先計算最高位的字母,用order / 11!,得到其取整結果 和 取餘結果。
根據其取整結果,便可知道最高位的字母在當前12字母的排位,咱們能夠肯定當前字母了。
取餘結果便可繼續用於計算下一位字母。依次類推,便可將字符串還原回來。

僞代碼:

$序號 = 456;
$字符串 = '';
$字母表 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'];
for($i = 11; $i >= 0; $i --) {
    $取整結果 = $序號 / $i 的階乘;
    $取餘結果 = $序號 % $i 的階乘;
    $當前字母 = $字母表[$取整結果];
    更改字母表,把取出來的字母后面的字母都向前移動一位
    $字符串 .= $當前字母;
    $序號 = $取餘結果;
}

第三題原文已經解釋得很清楚了,這裏就跳過。

第四題

第四題實際上是四道算法題中最最複雜的,但原文做者多是嫌解釋太麻煩,反倒講解得最少。這裏就重點解析第四道題的算法。

首先,咱們來還原題目。

以樣例爲例:
n = 2, a = 1, b = 3, x = 4,
則從[1, 3]中取出2個數的組合共有六對:
(1, 1), (1, 2), (1, 3),
(2, 2), (2, 3),
(3, 3)
其中,能組合成4,即相加爲4的只有(1, 3), (2, 2)。

假設n=2, x=4的機率表示爲rate(4, 2),其機率計算公式爲
取兩個數和爲4機率 = 取一個數爲1的機率取一個數爲3的機率 + 取一個數爲2的機率取一個數爲2的機率 + 取一個數爲3的機率*取一個數爲1的機率
rate(4, 2) = rate(1, 1)*rate(3, 1) + rate(2,1)*rate(2,1) + rate(3,1)*rate(1,1)
能夠進一步概括爲
rate(4,2) = rate(1,1)*rate((4-1),1) + rate(2,1)*rate(4-2,1) + rate(3,1)*rate((4-3),1)
咱們如今將數字替換爲字母
rate(x,n) = rate(1,n-1)*rate(x-1,n-1) + rate(2,n-1)*rate(x-2,n-1) + rate(3,n-1)*rate(x-3,n-1)

如今,咱們將[1, 3]區間替換成字母[a,b],公式能夠進一步概括爲
rate(x,n) = rate(a,1)*rate(x-a,n-1) + rate(a+1,1)*rate(x-(a+1),n-1) + ... + rate(b,1)*rate(x-b,n-1)
其中x-b>=a

至此,咱們已能夠肯定,在區間[a,b]內取n個數其和爲x的機率即爲rate(x,n),這能夠處理爲一個遞歸函數,當n=1時,便可肯定其值。

僞代碼

$區間下限 = a;
$區間上限 = b;
$區間數字個數 = $區間上限 - $區間下限 + 1;
function rate($x, $n) {
    // 當只取一個數時,其機率能夠肯定下來了
    if($n == 1) {
        if($x > $區間上限 || $x < $區間下限) {
            return 0;
        } else {
            return 1/$區間數字個數;
        }
    }

    $機率 = 0;
    for($i = $區間上限; $i < $區間下限; $i ++) {
        if($x-$i < $區間下限) break;

        $機率 += rate($i,1)*rate($x-$i, $n-1);
    }
    return $機率;
}

雖然個人解析過程過於囉嗦,可是它絕對夠詳細。若是有一天我忘記了這道算法,再回過頭來看,也能保證本身能夠看懂。

相關文章
相關標籤/搜索