动态规划实现的一个算法

题目:用户上传若干个 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;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。