可能的DNA链

时间:2015-09-15 17:28:15

标签: java algorithm

我遇到了一个挑战,要在java中制作一个算法来计算一个字符串形式可能有多少DNA链。字符串可以包含这5个字符 (A,G,C,T,?)

?在字符串中可以是(A,G,C或T)但是?可能不会导致字符串中的一对。例如,在这个字符串中" A?G" 只能是C或T.可以有无限的问号对,因为它们最后都是字符。

功能形式是这个

public static int chains(String base) {
    // return the amount of chains
}

如果 base 字符串是" A?C?"可能的组合是6 =(AGCA,AGCG ,AGCT ,ATCA ,ATCG ,ATCT)

??? - 36) ( AGAG - 1) ( A ??? T - 20)

- 4) ( A? - 3) (?A - 3) ( ?? - 12) ( A?A - 3) ( A?C - 2) ...

给定基数(pohja)字符串的最大长度为10!

标准 1.连续两个字符的组合是非法组合,因此不计算。

到目前为止我所拥有的:

    public static int chains(String pohja) {
    int sum = 1;
    int length = pohja.length();
    char[] arr = pohja.toCharArray();
    int questionMarks = 0;


    if (length == 1) {
        if (pohja.equals("?"))
            return 4;
        else
            return 1;
    } else if (length == 2) {
        boolean allQuestionMarks = true;
        for (int i = 0; i < 2; i++) {
            if (arr[i] != '?')
                allQuestionMarks = false;
            else
                questionMarks++;
        }

        if (allQuestionMarks) return 12;
        if (questionMarks == 1) {
            return 3;
        } else {
            return 2;
        }
    } else {
        questionMarks = 0;

        for (int i = 0; i < length; i++) {
            if (arr[i] == '?') questionMarks++;
        }

        for (int i = 1; i < length - 1; i++) {
            boolean leftIsLetter = isLetter(arr[i - 1]);
            boolean rightIsLetter = isLetter(arr[i + 1]);
            boolean sameSides = false;

            if (arr[i - 1] == arr[i + 1]) sameSides = true;

            if (arr[i] != '?') { // middle is char
                if (leftIsLetter && rightIsLetter) { // letter(left) == letter(right)
                    if (sameSides) {
                        // Do nothing!
                    } else {
                        sum *= 3;
                    }
                } else if (!leftIsLetter && !rightIsLetter) { // !letter(left) == !letter(right)

                } else { // letter(left) != letter(right)

                }
            } else { // Middle is ?
                if (leftIsLetter && rightIsLetter) { // letter(left) == letter(right)
                    if (sameSides) {
                        sum *= 3;
                    } else {
                        sum *= 2;
                    }
                } else if (!leftIsLetter && !rightIsLetter) { // !letter(left) == !letter(right)
                    sum *= 9;
                } else { // letter(left) != letter(right)
                    if (arr[i - 1] == '?') { // ? is on the left

                    } else { // ? is on the right
                        sum *= 2;
                    }
                }
            }
        }
    }

    return sum;
}

public static boolean isLetter(char c) {
    boolean isLetter = false;
    char[] dna = { 'A', 'G', 'C', 'T' };

    for (int i = 0; i < 4; i++) {
        if (c == dna[i]) isLetter = true;
    }

    return isLetter;
}

是的,我知道,我的代码很乱。如果 pohja (base)的长度为3或更多,我的算法将一次检查3个字符,并根据算法检查的字符修改 sum 。< / p>

任何人都可以暗示如何解决这个问题吗? :)提前致谢TuukkaX。

3 个答案:

答案 0 :(得分:5)

注意:我会保留这个有点模糊,因为你只是要求提示。如果您希望我进一步深思,请随时提问。

为了在数学上解决这个问题,你需要知道的是你可以在每个问号序列上进行的替换量(即在基本字符串"A?GCT???T?G"中,你有三个问号序列 - 两个包含一个问号,一个包含三个问号。在这种情况下,您可以拥有的替换总量等于您可以为每个序列进行的替换量的乘积。

简单示例:在字符串"A?G?"中,第一个问号可以替换为两个字符,而第二个问号可以替换为三个。总的来说,这是2*3 = 6合法的可能性。

计算结果的挑战在于找出如何计算更长的问号序列可以进行的替换量。我会给你最后一个提示并将解决方案作为扰流板包含在内:合法替换的数量取决于问号前后的字符。不过,我会以哪种方式离开你。

澄清:

  

您可以进行的替换数量取决于问号前后的字符是否相等。例如,"A??A"总共有6种法律可能性,"A??G"有7种。这需要加以考虑。

以下是如何将其解决方案:

  

现在,如何解决像"A????A"这样的问题?记住,取代的总量=每个单独序列的取代产物。 "A????A"是一个由四个问号组成的序列,它们之前和之后的字符是相等的。替换第二个字符有三种合法的可能性,并且它们中的每一个都留下"[G|C|T]???A" - 如同三个问号的序列,前一个和后一个字符不相等。您可以继续以递归方式执行此操作以获取可能的结果字符串总数。请记住,在基本字符串的开头和结尾处的问号需要特殊处理。

如果您仍然无法解决问题,我会给您一个方法的标题,以计算序列的合法替换数量:

 private int calcSequenceSubs(int length, boolean prevFollEqual)

这可能是身体:

if (prevFollEqual){
    if (length == 1) return 3;
    else return 3 * calcSequenceSubs(length-1, false);
} else {
    if (length == 1) return 2;
    else return 2 * calcSequenceSubs(length-1, false) + calcSequenceSubs(length-1, true);
}

编辑(没有剧透的简化版):

整个字符串的合法解决方案的数量等于每个问号序列的解决方案数量的乘积。例如,“A?A?A”有两个问号序列,每个问号都有三个合法替换,因此整个字符串共有3 * 3 = 9个法律解决方案。

所以,需要做的是:

  1. 在字符串中搜索问号序列
  2. 计算每个序列的可能解决方案的数量
  3. 将所有这些
  4. 相乘

    棘手的部分实际上是对每个序列的合法替换量进行了计算。这取决于两件事:序列的长度(显然)以及序列之前和之后的字符是否相等(例如,单个问号,当前一个和后一个字符相等时有3个可能的结果,否则有两个)

    现在,对于更长的序列,可以反复计算合法替代的总量。例如,“A ?? T”是两个问号的序列,前后字符不相等。第一个问号可以用T,G或C代替,产生“T?T”,“G?T”或“C?T”。其中两个是一个问号的序列,其中前一个和后一个字符不相等,其中一个是一个问号的序列,其中前一个和后一个字符相等。

    递归算法的模式总是相同的 - 如果序列的前一个和后一个字符不相等,则两个选项会导致前一个和后一个字符不同的序列和一个它们相同的序列。 同样,当原始序列中的前一个和后一个字符相等时,所有3个选项都会导致下一步是前一个字符和后一个字符不同的序列。

    可能解决方案的代码示例:

    public static int DNAChains(String base) {
    
    if (base == null || base.length() == 0) {
        return 0;
    }
    
    int curSequence = 0;
    int totalSolutions = 1;
    boolean inSequence = false;
    //flag to check whether there are any sequences present.
    //if not, there is one solution rather than 0
    char prevChar = 'x';
    char follChar = 'y';
    int i = 0;
    
    char[] chars = base.toCharArray();
    
    //handle starting sequence if present
    while (i < chars.length && chars[i] == '?') {
        curSequence++;
        i++;
    }
    
    if (curSequence > 0) {
    
        //exclusively ?'s needs to be treated even differently
        if (i < chars.length) {
            //? at the edge can be anything, so 3*false, 1*true
            //if length is 1 though, there are just 3 solutions
            totalSolutions *= (curSequence > 1) ? 3 * solveSequence(curSequence - 1, false) + solveSequence(curSequence - 1, true) : 3;
            curSequence = 0;
        } else {
            //result is 4*3^(length-1)
            totalSolutions = 4* ((int) Math.pow(3, chars.length-1));
        }
    }
    
    //check for sequences of question marks
    for (; i < chars.length; i++) {
    
        if (chars[i] == '?') {
            if (!inSequence) {
                inSequence = true;
                prevChar = chars[i - 1];
    
                //there is at least one sequence -> set flag
            }
            curSequence++;
        } else if (inSequence) {
            inSequence = false;
            follChar = chars[i];
            totalSolutions *= solveSequence(curSequence, prevChar == follChar);
            curSequence = 0;
        }
    
    }
    
    //check if last sequence ends at the edge of the string
    //if it does, handle edge case like in the beginning
    if (inSequence) {
        //? at the edge can be anything, so 3*false, 1*true
        //if length is 1 though, there are just 3 solutions
        totalSolutions *= (curSequence > 1) ? 3 * solveSequence(curSequence - 1, false) + solveSequence(curSequence - 1, true) : 3;
    }
    
    return totalSolutions;
    }//end DNAChains
    
    private static int solveSequence(int length, boolean prevFollEqual) {
    
    if (prevFollEqual) {
        //anchor
        if (length == 1) {
            return 3;
        } else {
            return 3 * solveSequence(length - 1, false);
        }
    } else {
        //anchor
        if (length == 1) {
            return 2;
        } else {
            return 2 * solveSequence(length - 1, false) + solveSequence(length - 1, true);
        }
    }
    }//end solveSequence
    

    我没有彻底测试,但似乎有效。我设法处理边缘情况(不是100%确定我是否得到了所有这些)。

答案 1 :(得分:1)

首先,查找包含? s的所有部分:

Sections

部分可以分为4类,具体取决于它们是如何包围的?基因:

  1. 两侧相同的基因
  2. 两侧不同的基因
  3. 基因只在一侧
  4. 两边都没有基因
  5. 可以很容易地看到每个链最多可以包含2个3类序列,而4类序列只能存在于仅包含? s的链中。

    如果您可以计算每个部分可以填充的方式,那么您只需要将这些数字相乘即可。在此示例中,第1部分,第2部分和第3部分可以21781方式填充,总共21*7*81 = 11 907种方式可以填充此链。

    如何计算每个部分的填充方式?

    让我们从类别3和4开始,因为它们更容易。对于类别4(满链? s),我们有4*3^(n-1)种填充方式(n是长度)。为什么?因为第一个基因可以是任何基因(4个选择),所有其他基因可以是其他3个,除了前面的基因(3个选择)。

    对于类别3,结果为3^n。如果该部分在最后(如示例中所示),我们将从左到右填充,每步有3个选项。部分处于乞讨时相同,但我们从右到左填充。

    问题在于类别1和2.让我们定义S(n)是填充由相同基因包围的n ?部分的方式的数量,{ {1}}由不同的人。对于D(n),我们可以将这两者放在一个关系中:

    n>=2

    relationship between S(n) and D(n)

    您可以在不进行任何进一步调查的情况下实施这些内容,但don't do it with simple recursion。使用查找表或类似的东西。或者您可以使用数学技能查找S(n) = 3*D(n-1) D(n) = 2*D(n-1) + S(n-1) 的公式而不递归:

    通过替换,S(n) and D(n)可获得D(n) = 2*D(n-1) + 3*D(n-2)。我们也知道n>=3D(1) = 2(手工和纸张计算)。像这样的方程可以通过几种技术解决,我使用了线性代数中的Matrix Exponentiation。结果如下:

    D(2) = 7

答案 2 :(得分:0)

我没有做足够的工作来说服我的答案,但如果你正在寻找一个直截了当的答案,那么&#34;可能&#34;工作。您将不得不测试并验证是否已完成类似的操作。同样地,我给出了一些伪代码来帮助更好地解释我的想法。

基本上,在链中的每个点,您都有许多选项,这些选项等同于您可以进行的最大组合数。例如字符串&#34; GC?&#34;具有相应的值&#34; 1 X 1 X 3 = 3种组合&#34;。同样地,价值&#34; A ??? T&#34;具有值&#34; 1 X 3 X 3 X 2 X 1 = 18种组合&#34;。当然假设我对此的理解是正确的,我没有错过任何明显的东西。这意味着您应该能够在每个点计算值,前提是您已知道先决条件。因此,为我们的代码设置一些规则。

  1. 任何角色都会自动且始终等于1.
  2. 根据位置,问号在3到2之间。
  3. 其他问号中间的任何问号自动具有值3。
  4. 任何由角色限制在一侧的问号都是3。
  5. 双方约束的问号将有一个小于3的值(例如,至少一个问号必须为2)。
  6. 请注意以任何以相同字符开头和结尾的子字符串的特殊情况(例如A ??? A)。
  7. 注意:我认为这就是所有情况。如果有人想尝试确认免费。

    所以一些伪代码可能看起来像......

    int currentValue = 1;
    for(each character) {
        if(character is fixed)
            currentValue *= 1
        else if (character is question mark)
            //... find the proper case, might have to create a look ahead function
    

    我可能会推荐某种布尔值来跟踪等式的左侧(例如,当遇到一个角色时打开一个标志,所以你不需要创建一个前瞻或后面的功能)。 / p>