通过子序列知道序列


18

介绍

假设您和您的朋友正在玩游戏。您的朋友想到一些特定的n位序列,而您的任务是通过向他们提问来推断序列。但是,您只能问的唯一类型的问题是“序列的最长公共子序列有多长时间?S”,其中S任何位序列在哪里。您需要的问题越少越好。

任务

您的任务是编写一个程序或函数,该程序或函数将一个正整数n和一个二进制R长度序列作为输入n。该序列可以是整数数组,字符串或您选择的其他某种合理类型。您的程序将输出序列R

你的程序是不是允许访问序列R直接。在只有它允许做的事情R是给它输入到功能len_lcs与其他二进制序列一起S。该函数len_lcs(R, S)返回的最长公共子序列的长度RS。这意味着最长的位序列在R和中都作为(不一定是连续的)子序列出现S。其输入len_lcs可以具有不同的长度。程序应R多次调用此函数以及其他序列,然后R根据该信息重建序列。

考虑输入n = 4R = "1010"。首先,我们可以评估len_lcs(R, "110"),这给了3,因为"110"是最长公共子"1010""110"。然后我们知道这R是通过"110"在某个位置插入一位获得的。接下来,我们可能会尝试len_lcs(R, "0110")返回,3因为最长的公共子序列是"110""010",因此返回,这"0110"是不正确的。然后我们尝试len_lcs(R, "1010"),它返回4。现在我们知道了R == "1010",因此我们可以将该序列作为正确的输出返回。这需要3次调用len_lcs

规则和计分

此存储库中,您将找到一个名为的文件,该文件subsequence_data.txt包含100个长度在75到124之间的随机二进制序列。它们是通过取0到1之间的三个随机浮点并将其平均值作为a,然后翻转有a偏的硬币n时间而生成的。您的得分是这些序列上的平均调用次数len_lcs,得分越低越好。您的提交应记录通话次数。没有时间限制,只是您应在提交文件之前在文件上运行程序。

您的提交应具有确定性。允许使用PRNG,但它们必须使用今天的日期200116(或最接近的等效日期)作为随机种子。您无权针对这些特定测试用例优化提交。如果我怀疑这种情况正在发生,我将生成一个新批次。

这不是代码打高尔夫球,因此鼓励您编写可读的代码。罗塞塔码(Rosetta Code)上有最长的共同子序列页 ; 您可以使用它以len_lcs您选择的语言来实现。


好主意!请问有什么应用吗?
瑕疵的

@flawr我不知道有任何直接的应用程序。这个想法来自查询复杂性理论,它是计算机科学的一个子领域,具有大量的应用程序。
Zgarb

我认为再次遇到同样的挑战会很棒,但是您可以在此处访问lcs而不是len_lcs
瑕疵的

@flawr这不会是非常有趣的,因为lcs(R, "01"*2*n)回报R。;)但是,如果调用lcs(R, S)将得分提高len(S)而不是1或类似的水平,那可能会起作用...
Zgarb

1
我希望看到其他答案= S
瑕疵的,2016年

Answers:


10

Java中,99.04 98.46 97.66 LCS()调用

怎么运行的

例如:我们要重建的线是00101。首先,通过将零与仅零字符串进行比较(在这里=将lcs与原始字符串进行比较),找出有多少个零00000。然后我们遍历每个位置,将其翻转0到a 1并检查我们现在是否具有更长的公共子字符串。如果是,则接受并转到下一个位置;如果否,则将当前值翻转1回a 0并转到下一个位置:

For our example of "00101" we get following steps:
input  lcs  prev.'best'
00000  3    0           //number of zeros
̲10000  3    3           //reject
0̲1000  3    3           //reject
00̲100  4    3           //accept
001̲10  4    4           //reject
0010̲1  5    4           //accept

最佳化

这仅仅是“天真”的实现,也许有可能找到一次更复杂的算法来一次检查多个位置。但我不知道是否有真的一个更好的(例如,基于类似海明码奇偶校验位计算),你可以永远只是评估长度的共同子串的。

对于一个给定的数字行,此算法需要精确#ofDigitsUntilTheLastOccurenceOf1 + 1检查。(如果最后一位是,请减去一个1。)

编辑:一个小的优化:如果我们只是检查了倒数第二位,并且我们仍然需要插入a 1,我们肯定会知道它必须在最后一个位置,并且可以省略相应的检查。

EDIT2:我刚刚注意到您可以将上述想法应用到最后k一个想法。

当然,通过首先对所有行进行重新排序,使用此优化程序当然可能会获得稍低的分数,因为这样可以使您最终获得更多行,但显然这是当前的优化测试用例,这不再是有趣的。

运行

上限为O(#NumberOfBits)

完整代码

这里是完整的代码:

package jcodegolf;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

// http://codegolf.stackexchange.com/questions/69799/know-a-sequence-by-its-subsequences

public class SequenceReconstructor { 
    public static int counter = 0;
    public static int lcs(String a, String b) { //stolen from http://rosettacode.org/wiki/Longest_common_subsequence#Java
        int[][] lengths = new int[a.length()+1][b.length()+1];

        // row 0 and column 0 are initialized to 0 already

        for (int i = 0; i < a.length(); i++)
            for (int j = 0; j < b.length(); j++)
                if (a.charAt(i) == b.charAt(j))
                    lengths[i+1][j+1] = lengths[i][j] + 1;
                else
                    lengths[i+1][j+1] =
                        Math.max(lengths[i+1][j], lengths[i][j+1]);

        // read the substring out from the matrix
        StringBuffer sb = new StringBuffer();
        for (int x = a.length(), y = b.length();
             x != 0 && y != 0; ) {
            if (lengths[x][y] == lengths[x-1][y])
                x--;
            else if (lengths[x][y] == lengths[x][y-1])
                y--;
            else {
                assert a.charAt(x-1) == b.charAt(y-1);
                sb.append(a.charAt(x-1));
                x--;
                y--;
            }
        }

        counter ++;
        return sb.reverse().toString().length();
    }


    public static String reconstruct(String secretLine, int lineLength){
        int current_lcs = 0; 
        int previous_lcs = 0;
        char [] myGuess = new char[lineLength];
        for (int k=0; k<lineLength; k++){
            myGuess[k] = '0';
        }

        //find the number of zeros:
        int numberOfZeros = lcs(secretLine, String.valueOf(myGuess));
        current_lcs = numberOfZeros;
        previous_lcs = numberOfZeros;

        if(current_lcs == lineLength){ //were done
            return String.valueOf(myGuess);
        }


        int numberOfOnes = lineLength - numberOfZeros;
        //try to greedily insert ones at the positions where they maximize the common substring length
        int onesCounter = 0;
        for(int n=0; n < lineLength && onesCounter < numberOfOnes; n++){

            myGuess[n] = '1';
            current_lcs = lcs(secretLine, String.valueOf(myGuess));

            if(current_lcs > previous_lcs){ //accept

                previous_lcs = current_lcs;
                onesCounter ++;

            } else { // do not accept
                myGuess[n]='0';     
            }

            if(n == lineLength-(numberOfOnes-onesCounter)-1 && onesCounter < numberOfOnes){ //lets test if we have as many locations left as we have ones to insert
                                                                // then we know that the rest are ones
                for(int k=n+1;k<lineLength;k++){
                    myGuess[k] = '1';
                }
                break;
            }

        }

        return String.valueOf(myGuess);
    }

    public static void main(String[] args) {
        try {

            //read the file
            BufferedReader br;

            br = new BufferedReader(new FileReader("PATH/TO/YOUR/FILE/LOCATION/subsequence_data.txt"));

            String line;

            //iterate over each line
            while ( (line = br.readLine()) != null){

                String r = reconstruct(line, line.length());
                System.out.println(line);     //print original line
                System.out.println(r);        //print current line
                System.out.println(counter/100.0);  //print current number of calls
                if (! line.equals(r)){
                    System.out.println("SOMETHING WENT HORRIBLY WRONG!!!");
                    System.exit(1);
                }

            }


        } catch(Exception e){
            e.printStackTrace();;
        }

    }

}

1
由于当尾数为1时您得到的呼叫较少,因此,如果在第一个猜测告诉您0大于1之后,如果您切换为寻找0位置而不是1位置,那么您似乎可以做得更好。您甚至可以多次这样做。
histocrat

1
@histocrat我认为一旦他用完了last 1,他已经在停止,这相当于只剩下零。
马丁·恩德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.