查找有效坑洼字符串的数量

find number of valid potholes string

提问人:Aman 提问时间:9/8/2022 最后编辑:trincotAman 更新时间:9/12/2022 访问量:134

问:

我收到了这个代码挑战:

你住在一个有很多坑洼的社区。给定一个由以下部分组成的字符串:str

  • “P”——表示坑洼。
  • “0” - 表示这里没有坑洼,也没有 (我-1) st 和 (i+1)st 位置。
  • “1” - 表示这里没有坑洼,1 个坑洼 (i-1)st 和 (i+1)st 位置。
  • “2” - 表示这里没有坑洼,2 (I-1)st 和 (i+1)st 位置的坑洼。
  • '?' - 表示我们不知道那里有什么。它可以是 '0'、'1'、 “2”或“P”。

报告可以创建的字符串数。如果给定的输入具有冲突的信息,则报告 0。

使用模数来报告答案。

输入在第一行具有测试数 T。 接下来的每一行都有一个输入字符串。

约束

1 <= T <= 100
1 <= str <= 1e5

输入

2
?01???
PP12

输出

4
0

我在一次在线测试中得到了这个问题,我觉得我几乎达到了解决方案,但即使花了很多时间,我也无法实现它。对此的算法是什么?

字符串 算法 与语言无关

评论

0赞 Bagus Tesa 9/8/2022
我很好奇,为什么不输出 3?另外,你试过什么??01???
0赞 Aman 9/8/2022
我不知道我是否正确,但我在“?01???”中发现了这 4 种可能的方式是 - i) 001PPP ii) 001PP1 iii) 001P10 iv) 001P2P 我试图通过使用给定字符生成所有字符串来实现,最后我将检查它是否有效。
0赞 Bagus Tesa 9/8/2022
那么,4 是从列举所有可能性开始的吗?如果是这样,算数吗?001P00
0赞 Aman 9/8/2022
我们必须报告没有。制作有效字符串的可能方法...并且不知道 4 是如何到来的,因为你又添加了一个有效的字符串。
2赞 Evg 9/8/2022
这个问题是关于数字签名算法(DSA)的吗?为什么它被标记为这样?

答:

1赞 trincot 9/11/2022 #1

这可以通过从左到右扫过字符并将“0”、“1”和“2”的效果应用于它们后面的问号来解决。同时检测不一致之处(例如“0”后跟“P”,或“2”后跟“0”等)。

其效果可能是问号必须解析为“P”,也可以是没有坑洼。为了对“缺席”进行编码,我们应该使用字符“0”、“1”或“2”之一,当整个字符串被解析时,要使用的字符串直接取决于该位置具有的“P”邻居的数量。在下面的算法中,我选择使用一个新字符“_”来表示“没有坑洼”,因为我知道实际上它要么是“0”、“1”或“2”(没有自由选择)。但这并不重要,因为我们没有被要求返回有效的字符串,只要求返回它们的计数。

然后这个想法是在相反的方向上执行相同的过程 - 从右到左扫描。 在第二次扫描中,计算未解决的问号的数量:这些问号显然可以是两者(“P”或“_”)。但是,对于交替的“1”和“?”链,需要特别小心。例如,如果输入是“?1?1?1?”,我们可以将第一个“?”设为“P”,但这将决定所有其他“?”?(它们不再免费)。因此,此输入只有 2 个解决方案,具体取决于如何将其中一个“?”解析为“P”或“_”。简而言之,后跟“1”的问号不应算作“自由”选择。

其余的自由选择各代表一个二元选择,因此可能性的总数是 2 提高到自由“?”的幂。

以下是上述思想在纯 JavaScript 中的实现:

function countSolutions(s) {
    // Helper function to resolve a "?" to either "P" or "_"
    function set(list, i, ch) {
        if (list[i] == "?") list[i] = ch; // Resolve "?"
        return (list[i] == "P") == (ch == "P"); // Is false when there is a contradiction
    }
    
    // Main algorithm for a single sweep from left to right
    function sweep(list) {
        let count = 0;
        for (let i = 2; i < list.length; i++) {
            switch (list[i-1]) {
            case "0":
                if (!set(list, i, "_")) return 0; // Contradiction
                break;
            case "2":
                if (!set(list, i, "P")) return 0; // Contradiction
                break;
            case "1":
                switch (list[i-2]) {
                case "?": 
                    count--; // Undo. Take into account the chain-effect
                    break;
                case "P":
                    if (!set(list, i, "_")) return 0; // Contradiction
                    break;
                default: // "0", "1", or "_"
                    if (!set(list, i, "P")) return 0; // Contradiction
                    break;
                }
                break;
            case "?":
                count++;
            }                
        }
        return 1 << count;
    }

    // Convert character string to array of characters (so it is mutable)
    //    To ease the process, add two extra characters (denoting no pitholes)
    let list = Array.from("_" + s + "_");
    // Perform two sweeps in opposite directions. If the first returns 0
    //    then don't bother with the second sweep as there is no solution.
    return sweep(list) && sweep(list.reverse());
}

// Execute the algorithm for the two test cases given in the question
const tests = [
    "?01???",
    "PP12"
]

for (const test of tests) {
    // Output both the test and the result for it
    console.log(test, countSolutions(test));
}

笔记:

  • 您的问题提到了“模数”,但没有提供使用哪个模数。我想你自己应用它不会有困难。
  • 代码质询指定输入/输出格式。同样,我想您首先将测试读取到变量中,然后对这些变量应用算法并生成输出不会有任何困难。

评论

0赞 Aman 9/11/2022
感谢您的回答,但我怀疑它是否报告了“?01???”的 4,但我们计算了 5 个有效字符串 i) 001PPP ii) 001PP1 iii) 001P10 iv) 001P2P v)001P00 所以你能解释一下哪个不是有效字符串或它如何报告 4 以及哪些是那些。
0赞 trincot 9/11/2022
最后一个无效。由于规则的原因,不能在 A 前面。0P
1赞 Matt Timmermans 9/11/2022 #2

您应该记住的一般技术是:

  1. 有效字符串集是一种常规语言

  2. 因此,您可以制作一个接受它的 DFA

  3. 对于字符串中的每个位置,从开头开始,您可以计算有多少种方法可以进入每种状态。

答案是最终进入接受状态的方法的总和。

这种技术适用于许多“多少个字符串”的问题。

在这种情况下,DFA 非常简单,只需要 4 个状态。让我们称它们为:

  • S - 字符串的开头。尚未处理任何字符
  • F - 由于前面的 1 或 2 而被迫出现下一个坑洼
  • N - 接下来不能出现坑洼,但所有其他字符都有效
  • H - 在坑洼之后 -- 除了 0 之外的任何字符都可以是下一个

过渡是:

 S: 0 -> N, 1 -> F, P -> H
 F: P -> H
 N: 0 -> N, 1 -> F
 H: 1 -> N, 2 -> F, P -> H

假设元组 (s,f,n,h) 给出了您可以在任何位置进入每种状态的多少种方式。在字符串的开头,您必须位于 S 中,并且您有 (1,0,0,0)

要获得下一个位置的计数,您必须处理下一个字符。从 (s,f,n,h) 开始,每个字符产生以下计数:

 0 -> (0, 0, s+n, 0)
 1 -> (0, s+n, h, 0)
 2 -> (0, h, 0, 0)
 P -> (0, 0, 0, s+f+h)
 ? -> (0, s+n+h, s+n+h, s+f+h) // sum of the above

DFA 中的 S、N、H 状态是接受的,因此在对每个字符执行此转换后,只需将元组中这些状态的计数相加即可得到答案。