美文网首页
动态规划实现的一个算法

动态规划实现的一个算法

作者: 如梦又似幻 | 来源:发表于2020-03-15 17:21 被阅读0次

    题目:用户上传若干个 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;
        }
    

    相关文章

      网友评论

          本文标题:动态规划实现的一个算法

          本文链接:https://www.haomeiwen.com/subject/yubpjhtx.html