動(dòng)態(tài)規(guī)劃實(shí)現(xiàn)的一個(gè)算法

題目:用戶上傳若干個(gè) adapter index 序列迷殿,這些序列是由 “A”儿礼、“T”、“C”庆寺、“G” 四種字母組成的字符串蚊夫,長(zhǎng)度可能是6、8懦尝、16知纷。然后指定一個(gè)數(shù)字 n,需要輸出 count 組的 adapter index 的組合陵霉,每組中必須有 n 個(gè)adapter index琅轧,且使這些組合中,每一位的四種字母占比要盡量均勻踊挠,而且都在 12.5%-60% 之間乍桂,優(yōu)先滿足前 count 個(gè)分組,最終不足 n 個(gè)或者不滿足占比的要舍棄效床。如果最后輸出這樣一組 adapte indexr:

ACTGCAG
TGAAGCT
GCATTAG
GATGATG

那么第一位的 A 占比為 25%睹酌,T占比25%,G占比50%扁凛,C占比0%忍疾;第二位的 A 占比25%,T占比0%谨朝, G占比25%卤妒,C占比50%......依次計(jì)算甥绿。

由題目可知,我們最終想要的結(jié)果是一些字符串的分組则披,這個(gè)問題就非常類似動(dòng)態(tài)規(guī)劃里的背包問題或者是股票問題共缕。

背包問題中,我們要找的是把物品放入限制容量的背包的最大價(jià)值士复,需要從從若干個(gè)物品中挑選图谷,本問題也可以看成是從若干個(gè)字符串中挑選出最符合要求的(即 ATCG 最均勻的),所以對(duì)于單個(gè)分組阱洪,我們可以使用動(dòng)態(tài)規(guī)劃便贵,來求得最佳的組合。問題中也提到冗荸,可以優(yōu)先滿足前幾個(gè)組承璃,之后不滿足就可以舍棄,我們就可以使用遞歸蚌本,先滿足第一組盔粹,然后再在剩下的 adapter 中繼續(xù)挑選滿足要求的最佳組合。

先來構(gòu)建動(dòng)態(tài)規(guī)劃的框架程癌。

動(dòng)態(tài)規(guī)劃中舷嗡,我們第一步先要找出狀態(tài)選擇

在這個(gè)問題中嵌莉,我們要做的是遍歷所有的 adapter进萄,然后判斷把當(dāng)前 adapter 加入到數(shù)組中時(shí),是否為“最佳”的情況锐峭。

狀態(tài)有兩種:數(shù)組中 adapter 的數(shù)量垮斯、可選擇的 adapter。

選擇:當(dāng)前的 adapter 是否進(jìn)入數(shù)組只祠。

找到狀態(tài)和選擇之后,動(dòng)態(tài)規(guī)劃的問題基本就可以解決了扰肌,有一個(gè)動(dòng)態(tài)規(guī)劃的框架:

for 狀態(tài)1 in 狀態(tài)1的所有取值
      for 狀態(tài)2 in 狀態(tài)2的所有取值
          for ...
              dp[狀態(tài)1][狀態(tài)2][...] = 擇優(yōu)(選擇1抛寝,選擇2....)
第二步要明確 dp 數(shù)組的定義

dp 數(shù)組是什么?其實(shí)就是描述局部問題的一個(gè)數(shù)組曙旭,剛才提到的“狀態(tài)”盗舰,現(xiàn)在就要用 dp 數(shù)組把狀態(tài)表示出來。

我們的狀態(tài)有兩個(gè)桂躏,所以我們就需要一個(gè)二維數(shù)組钻趋,一維表示可選擇的 adapter,一維表示當(dāng)前數(shù)組中已有的 adapter 的數(shù)量剂习。

dp[i][w]的定義如下:對(duì)于前 i 個(gè)adapter蛮位,當(dāng)前數(shù)組中的 adapter 數(shù)量為 w较沪,這種情況下當(dāng)前數(shù)組的最優(yōu)均勻程度為 dp[i][w]

注:題目中的最優(yōu)解失仁,我們可以理解為最均勻尸曼,所以這里的最優(yōu)情況即為最均勻程度。

如果 dp[3][5] = 6萄焦,其含義就是對(duì)于給定的所有 adapter 控轿,如果只對(duì)前 3 個(gè) adapter 進(jìn)行選擇,當(dāng)數(shù)組中有 5 個(gè)adapter 時(shí)拂封,最均勻程度就為 6茬射。

設(shè) adapter 總數(shù)為 N 個(gè),數(shù)組的容量為 W冒签。

根據(jù)這個(gè)定義在抛,我們想求的最佳答案就是 dp[N][W]

還要記得處理 base case镣衡,這個(gè)問題的 base case 就是 dp[0][..]dp[..][0]霜定,因?yàn)楫?dāng)沒有可選 adapter 或者數(shù)組容量為 0 時(shí),均勻程度無法計(jì)算廊鸥。

如此望浩,細(xì)化動(dòng)態(tài)規(guī)劃的框架:

      for i in count(indexes):
          for w in [1...8]
              dp[i][w] = max(
                  把當(dāng)前 index 放入結(jié)果集,      
                  不把當(dāng)前 index 放入結(jié)果集
              )
       return dp[count(indexes)][8];
         
根據(jù)“選擇”惰说,思考狀態(tài)轉(zhuǎn)移的邏輯

簡(jiǎn)單來說磨德,就是上面的把“把當(dāng)前 index 放入結(jié)果集” 和 “不把當(dāng)前 index 放入結(jié)果集” 如何用代碼體現(xiàn)呢?

前面提到吆视,對(duì)于前 i 個(gè)adapter典挑,當(dāng)前數(shù)組中的 adapter 數(shù)量為 w,這種情況下當(dāng)前數(shù)組的最優(yōu)均勻程度為 dp[i][w]**啦吧。

如果沒有把第 i 個(gè) adapter 放入數(shù)組您觉,那么最均勻程度 dp[i][w] 就應(yīng)該等于 dp[i-1][w],繼承之前的結(jié)果授滓。

如果把第 i 個(gè) adapter 放入了數(shù)組琳水,那么最均勻程度 dp[i][w] 就應(yīng)該是在dp[i-1][w-1] 的基礎(chǔ)上進(jìn)行計(jì)算,當(dāng)數(shù)組新添加了一個(gè)元素 般堆,就應(yīng)該計(jì)算新元素加入之后的分值在孝。

既然我們最后要輸出的是一個(gè)數(shù)組,所以還需要一個(gè)數(shù)組來記錄數(shù)組中的 adapter淮摔。

細(xì)化一下代碼:

private function filtrateAdapters($indexes, $count)
{
        // 初始化數(shù)組
        $adapters = [];
        $indexesLen = count($indexes);

        // 放入第一個(gè) index
        $adapters[0][0] = [];
        $percent[0][0] = -100000;

        // 動(dòng)態(tài)規(guī)劃
        for ($i = 0; $i <= $indexesLen; $i ++) {
            for ($w = 0; $w <= $count; $w ++) {
                // 初始化的一部分私沮,分值初始化為0,數(shù)組初始化為空數(shù)組
                if ($i == 0 || $w == 0) {
                    $percent[$i][$w] = 0;   
                    $adapters[$i][$w] = [];
                    continue;
                }
                // 判斷最優(yōu)
                // 放入的情況
                $adapters[$i][$w] = array_merge($adapters[$i-1][$w-1], [$indexes[$i]]);

                $baseBank = $this->baseRank($adapters[$i][$w]);
                $percent[$i][$w] = $this->percentageScore($baseBank);

                // 不放的情況
                if ($percent[$i][$w] < $percent[$i-1][$w] && $percent[$i-1][$w] != 0) {
                    // 出棧
                     //dd($adapters[$i][$w], $adapters[$i-1][$w-1], $percent[$i][$w], $percent[$i-1][$w-1]);
                    $adapters[$i][$w] = $adapters[$i-1][$w];
                    $percent[$i][$w] = $percent[$i - 1][$w];
                }
            }
        }
        return $adapters[$indexesLen][$count];
}

這里我們需要一個(gè)計(jì)算“均勻程度”的方法和橙,這里就不再貼出來了仔燕,就是簡(jiǎn)單地每一位計(jì)算一下造垛。

然后我們需要用遞歸的方法來求出最終的若干個(gè)數(shù)組,整理出代碼:

    private function filtrateAdapters(&$indexes, $count, &$res=[])
    {
        // 初始化數(shù)組
        $result = [];
        $adapters = [];
        $indexesLen = count($indexes);

        $adapters[0][0] = [];
        $percent[0][0] = -100000;

        // 動(dòng)態(tài)規(guī)劃
        for ($i = 0; $i <= $indexesLen; $i ++) {
            for ($w = 0; $w <= $count; $w ++) {

                // 初始化的一部分涨享,分值初始化為0筋搏,數(shù)組初始化為空數(shù)組
                if ($i == 0 || $w == 0) {
                    $percent[$i][$w] = 0;
                    $adapters[$i][$w] = [];
                    continue;
                }

                // 判斷最優(yōu)
                // 放入的情況
                $adapters[$i][$w] = array_merge($adapters[$i-1][$w-1], [$indexes[$i]]);

                $baseBank = $this->baseRank($adapters[$i][$w]);
                $percent[$i][$w] = $this->percentageScore($baseBank);

                // 不放的情況
                if ($percent[$i][$w] < $percent[$i-1][$w] && $percent[$i-1][$w] != 0) {
                    $adapters[$i][$w] = $adapters[$i - 1][$w];
                    $percent[$i][$w] = $percent[$i - 1][$w];
                }
            }
        }

        // 最后判斷結(jié)果,如果是負(fù)值厕隧,代表結(jié)束
        if (count($adapters[$indexesLen][$count]) < $count || $percent[$indexesLen][$count] < 0) {
            //dd($res);
            return $res;
        }

        // 判斷是否超出界限
        $baseBank = $this->repository->baseRank($adapters[$indexesLen][$count]);
        foreach ($baseBank as $item) {
            foreach ($item as $value) {
                $p = str_replace("%", '', $value);
                if ($p > 60 || $p < 12.5) {
                    return $res;
                }
            }
        }

        $res[] = [
            'adapters' => $adapters[$indexesLen][$count],
            'percent' => $this->repository->baseRank($adapters[$indexesLen][$count])
        ];

        // 然后從原indexes數(shù)組中去除已選的元素奔脐,繼續(xù)下一輪
        $newIndexes = array_diff($indexes, $adapters[$indexesLen][$count]);
        $this->filtrateAdapters($newIndexes, $count, $res);
    }

    // 根據(jù)占比來計(jì)算分值
    private function percentageScore($baseBank)
    {
        // 如果超出界限,則返回-1吁讨,其他情況計(jì)算平均值

        /**
         * 結(jié)構(gòu):
         * a=>[0:10%,1:10%...]
         * t=>[0:10%,1:10%...]
         * c=>[0:10%,1:10%...]
         * g=>[0:10%,1:10%...]
         */

        $score = 0;
        foreach ($baseBank as $item) {
            foreach ($item as $value) {
                $p = str_replace("%", '', $value);

                if ($p > 60 || $p < 12.5) {
                    $score += -1;
                }

                if ($p > 25) {
                    $score +=  (60-$p)/35 * 100;
                } elseif ($p <= 25) {
                    $score +=  ($p-12.5)/12.5 * 100;
                }
            }
        }

        return $score;
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末髓迎,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子建丧,更是在濱河造成了極大的恐慌排龄,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翎朱,死亡現(xiàn)場(chǎng)離奇詭異橄维,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)拴曲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門争舞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人澈灼,你說我怎么就攤上這事竞川。” “怎么了叁熔?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵委乌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我荣回,道長(zhǎng)遭贸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任心软,我火速辦了婚禮革砸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘糯累。我一直安慰自己,他們只是感情好册踩,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布泳姐。 她就那樣靜靜地躺著,像睡著了一般暂吉。 火紅的嫁衣襯著肌膚如雪胖秒。 梳的紋絲不亂的頭發(fā)上缎患,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音阎肝,去河邊找鬼挤渔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛风题,可吹牛的內(nèi)容都是我干的判导。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼沛硅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼眼刃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起摇肌,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤擂红,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后围小,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體昵骤,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年肯适,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了变秦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疹娶,死狀恐怖伴栓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情雨饺,我是刑警寧澤钳垮,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站额港,受9級(jí)特大地震影響饺窿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜移斩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一肚医、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧向瓷,春花似錦肠套、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春刁赖,著一層夾襖步出監(jiān)牢的瞬間搁痛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工宇弛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸡典,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓枪芒,卻偏偏與公主長(zhǎng)得像彻况,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子病苗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359