动态规划实现的一个算法

题目:用户上传若干个 adapter index 序列,这些序列是由 “A”、“T”、“C”、“G” 四种字母组成的字符串,长度可能是6、8、16。然后指定一个数字 n,需要输出 count 组的 adapter index 的组合,每组中必须有 n 个adapter index,且使这些组合中,每一位的四种字母占比要尽量均匀,而且都在 12.5%-60% 之间,优先满足前 count 个分组,最终不足 n 个或者不满足占比的要舍弃。如果最后输出这样一组 adapte indexr:

ACTGCAG
TGAAGCT
GCATTAG
GATGATG

那么第一位的 A 占比为 25%,T占比25%,G占比50%,C占比0%;第二位的 A 占比25%,T占比0%, G占比25%,C占比50%......依次计算。

由题目可知,我们最终想要的结果是一些字符串的分组,这个问题就非常类似动态规划里的背包问题或者是股票问题。

背包问题中,我们要找的是把物品放入限制容量的背包的最大价值,需要从从若干个物品中挑选,本问题也可以看成是从若干个字符串中挑选出最符合要求的(即 ATCG 最均匀的),所以对于单个分组,我们可以使用动态规划,来求得最佳的组合。问题中也提到,可以优先满足前几个组,之后不满足就可以舍弃,我们就可以使用递归,先满足第一组,然后再在剩下的 adapter 中继续挑选满足要求的最佳组合。

先来构建动态规划的框架。

动态规划中,我们第一步先要找出状态选择

在这个问题中,我们要做的是遍历所有的 adapter,然后判断把当前 adapter 加入到数组中时,是否为“最佳”的情况。

状态有两种:数组中 adapter 的数量、可选择的 adapter。

选择:当前的 adapter 是否进入数组。

找到状态和选择之后,动态规划的问题基本就可以解决了,有一个动态规划的框架:

for 状态1 in 状态1的所有取值
      for 状态2 in 状态2的所有取值
          for ...
              dp[状态1][状态2][...] = 择优(选择1,选择2....)
第二步要明确 dp 数组的定义

dp 数组是什么?其实就是描述局部问题的一个数组,刚才提到的“状态”,现在就要用 dp 数组把状态表示出来。

我们的状态有两个,所以我们就需要一个二维数组,一维表示可选择的 adapter,一维表示当前数组中已有的 adapter 的数量。

dp[i][w]的定义如下:对于前 i 个adapter,当前数组中的 adapter 数量为 w,这种情况下当前数组的最优均匀程度为 dp[i][w]

注:题目中的最优解,我们可以理解为最均匀,所以这里的最优情况即为最均匀程度。

如果 dp[3][5] = 6,其含义就是对于给定的所有 adapter ,如果只对前 3 个 adapter 进行选择,当数组中有 5 个adapter 时,最均匀程度就为 6。

设 adapter 总数为 N 个,数组的容量为 W。

根据这个定义,我们想求的最佳答案就是 dp[N][W]

还要记得处理 base case,这个问题的 base case 就是 dp[0][..]dp[..][0],因为当没有可选 adapter 或者数组容量为 0 时,均匀程度无法计算。

如此,细化动态规划的框架:

      for i in count(indexes):
          for w in [1...8]
              dp[i][w] = max(
                  把当前 index 放入结果集,      
                  不把当前 index 放入结果集
              )
       return dp[count(indexes)][8];
         
根据“选择”,思考状态转移的逻辑

简单来说,就是上面的把“把当前 index 放入结果集” 和 “不把当前 index 放入结果集” 如何用代码体现呢?

前面提到,对于前 i 个adapter,当前数组中的 adapter 数量为 w,这种情况下当前数组的最优均匀程度为 dp[i][w]**。

如果没有把第 i 个 adapter 放入数组,那么最均匀程度 dp[i][w] 就应该等于 dp[i-1][w],继承之前的结果。

如果把第 i 个 adapter 放入了数组,那么最均匀程度 dp[i][w] 就应该是在dp[i-1][w-1] 的基础上进行计算,当数组新添加了一个元素 ,就应该计算新元素加入之后的分值。

既然我们最后要输出的是一个数组,所以还需要一个数组来记录数组中的 adapter。

细化一下代码:

private function filtrateAdapters($indexes, $count)
{
        // 初始化数组
        $adapters = [];
        $indexesLen = count($indexes);

        // 放入第一个 index
        $adapters[0][0] = [];
        $percent[0][0] = -100000;

        // 动态规划
        for ($i = 0; $i <= $indexesLen; $i ++) {
            for ($w = 0; $w <= $count; $w ++) {
                // 初始化的一部分,分值初始化为0,数组初始化为空数组
                if ($i == 0 || $w == 0) {
                    $percent[$i][$w] = 0;   
                    $adapters[$i][$w] = [];
                    continue;
                }
                // 判断最优
                // 放入的情况
                $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];
}

这里我们需要一个计算“均匀程度”的方法,这里就不再贴出来了,就是简单地每一位计算一下。

然后我们需要用递归的方法来求出最终的若干个数组,整理出代码:

    private function filtrateAdapters(&$indexes, $count, &$res=[])
    {
        // 初始化数组
        $result = [];
        $adapters = [];
        $indexesLen = count($indexes);

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

        // 动态规划
        for ($i = 0; $i <= $indexesLen; $i ++) {
            for ($w = 0; $w <= $count; $w ++) {

                // 初始化的一部分,分值初始化为0,数组初始化为空数组
                if ($i == 0 || $w == 0) {
                    $percent[$i][$w] = 0;
                    $adapters[$i][$w] = [];
                    continue;
                }

                // 判断最优
                // 放入的情况
                $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];
                }
            }
        }

        // 最后判断结果,如果是负值,代表结束
        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数组中去除已选的元素,继续下一轮
        $newIndexes = array_diff($indexes, $adapters[$indexesLen][$count]);
        $this->filtrateAdapters($newIndexes, $count, $res);
    }

    // 根据占比来计算分值
    private function percentageScore($baseBank)
    {
        // 如果超出界限,则返回-1,其他情况计算平均值

        /**
         * 结构:
         * 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;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335