我试图做经典问题来实现一个算法来打印n对括号的所有有效组合。我找到了这个程序(效果很好):
public static void addParen(ArrayList<String> list, int leftRem, int rightRem, char[] str, int count) {
if (leftRem < 0 || rightRem < leftRem) return; // invalid state
if (leftRem == 0 && rightRem == 0) { /* all out of left and right parentheses */
String s = String.copyValueOf(str);
list.add(s);
} else {
if (leftRem > 0) { // try a left paren, if there are some available
str[count] = '(';
addParen(list, leftRem - 1, rightRem, str, count + 1);
}
if (rightRem > leftRem) { // try a right paren, if there’s a matching left
str[count] = ')';
addParen(list, leftRem, rightRem - 1, str, count + 1);
}
}
}
public static ArrayList<String> generateParens(int count) {
char[] str = new char[count*2];
ArrayList<String> list = new ArrayList<String>();
addParen(list, count, count, str, 0);
return list;
}
然后,当我在搜索加泰罗尼亚语数字的应用程序时,我在这里找到了一个非常有趣的应用程序:https://en.wikipedia.org/wiki/Catalan_number#Applications_in_combinatorics 它说:
我们可以使用加泰罗尼亚数字形成山脉,其中n次上行和n次击打都保持在原始线之上。山脉的解释是山脉永远不会低于地平线
因此,我尝试重用上面的代码来实现此问题的解决方案。我不确定,但我认为我们可以重复使用这段代码,因为它们似乎具有相同的“逻辑”。 不幸的是,我尝试了很多东西用'/'和'\'替换括号,但我失败了。
我试着这样做:
str[count] = '/';
addParen(list, leftRem - 1, rightRem, str, count + 1);
}
if (rightRem > leftRem) { // try a right paren, if there’s a matching left
str[count] = '\\';
str[count+1] = '\n';
addParen(list, leftRem, rightRem - 1, str, count + 2);
对我来说,它们具有相同的逻辑,就像在括号中一样,我们添加左括号'('每次都可以,但对于右括号')'我们只在右括号的数量大于剩下。我们可以在这里做同样的捣蛋吗?我们添加'/'每次都可以,但对于'\',我们必须先计算'/'的数量...
我发现这里特别困难的是我们如何在这里插入新线以拥有所有正确的山脉?
如果有可能,你能帮助我重复使用这段代码吗?或者我应该从头开始另一个代码吗?
答案 0 :(得分:5)
有趣的任务。计算可以并行完成。我会在&#34;不回答&#34;中显示代码。标签,因为它不符合问题的语言标准(以并行数组处理语言 Dyalog APL 制作,实际上它使用一行代码完成工作)。请根据需要忽略该部分。但是,我会显示数据以及会发生什么。它非常直观。
&LT;不回答&gt;
fn←{(∧/(0≤+\a-~a),(⍵÷2)=+/a)⌿a←⍉(⍵⍴2)⊤⍳2*⍵} // Dynamic function, generates boolean matrix
format←{⍉↑(-1+(0.5×⍴⍵)-+\⍵-0,¯1↓~⍵)↑¨'\/'[1+⍵]} // Dirty format function
&LT; /不回答&gt;
说参数(山脉的宽度)是n = 6.
步骤1.生成介于0和(2 ^ 6 - 1)之间的所有数字
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
第2步:抓住每个2个底座(它们垂直位于下方.0位于最左侧,然后是1个等):
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1
0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
<子> 1。实际上,只需要生成从32到63的数字,因为我们只需要以1开头的2个基数。请参阅上面数据中的最顶行。顶部为零的列(数字)甚至不应该生成。)
2.实际上只需要生成偶数,因为最后一位必须为0.参见上面数据中最下面的行。底部的列(数字)甚至不应该生成。)
子>
第3步:计算每列中的数量
0 1 1 2 1 2 2 3 1 2 2 3 2 3 3 4 1 2 2 3 2 3 3 4 2 3 3 4 3 4 4 5 1 2 2 3 2 3 3 4 2 3 3 4 3 4 4 5 2 3 3 4 3 4 4 5 3 4 4 5 4 5 5 6
并做一个布尔标记= 1,其中总和是N的一半,即3(即,总共我们必须有像下坡一样多的上坡)。 这是我们的第一个布尔结果:
0 0 0 0 0 0 0 1 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 1 1 0 1 0 0 0 0 0 0 1 0 1 1 0 0 1 1 0 1 0 0 0 0 1 1 0 1 0 0 0 1 0 0 0 0 0 0 0
第4步:确保我们不会在地平线以下&#34;
这意味着我们必须首先计算每列的累积总和:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3
0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 2 2 2 2 3 3 3 3 3 3 3 3 4 4 4 4
0 0 1 1 1 1 2 2 1 1 2 2 2 2 3 3 1 1 2 2 2 2 3 3 2 2 3 3 3 3 4 4 1 1 2 2 2 2 3 3 2 2 3 3 3 3 4 4 2 2 3 3 3 3 4 4 3 3 4 4 4 4 5 5
0 1 1 2 1 2 2 3 1 2 2 3 2 3 3 4 1 2 2 3 2 3 3 4 2 3 3 4 3 4 4 5 1 2 2 3 2 3 3 4 2 3 3 4 3 4 4 5 2 3 3 4 3 4 4 5 3 4 4 5 4 5 5 6
然后换档位(0&#39; s变为1&#39; s,反之亦然):
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0
4 4 4 4 3 3 3 3 3 3 3 3 2 2 2 2 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 2 2 2 2 1 1 1 1 1 1 1 1 0 0 0 0
5 5 4 4 4 4 3 3 4 4 3 3 3 3 2 2 4 4 3 3 3 3 2 2 3 3 2 2 2 2 1 1 4 4 3 3 3 3 2 2 3 3 2 2 2 2 1 1 3 3 2 2 2 2 1 1 2 2 1 1 1 1 0 0
6 5 5 4 5 4 4 3 5 4 4 3 4 3 3 2 5 4 4 3 4 3 3 2 4 3 3 2 3 2 2 1 5 4 4 3 4 3 3 2 4 3 3 2 3 2 2 1 4 3 3 2 3 2 2 1 3 2 2 1 2 1 1 0
然后从第1个减去第2个,得到
¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
¯2 ¯2 ¯2 ¯2 ¯2 ¯2 ¯2 ¯2 ¯2 ¯2 ¯2 ¯2 ¯2 ¯2 ¯2 ¯2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
¯3 ¯3 ¯3 ¯3 ¯3 ¯3 ¯3 ¯3 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 1 1 1 1 1 1 1 1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 ¯1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3 3 3 3 3 3 3
¯4 ¯4 ¯4 ¯4 ¯2 ¯2 ¯2 ¯2 ¯2 ¯2 ¯2 ¯2 0 0 0 0 ¯2 ¯2 ¯2 ¯2 0 0 0 0 0 0 0 0 2 2 2 2 ¯2 ¯2 ¯2 ¯2 0 0 0 0 0 0 0 0 2 2 2 2 0 0 0 0 2 2 2 2 2 2 2 2 4 4 4 4
¯5 ¯5 ¯3 ¯3 ¯3 ¯3 ¯1 ¯1 ¯3 ¯3 ¯1 ¯1 ¯1 ¯1 1 1 ¯3 ¯3 ¯1 ¯1 ¯1 ¯1 1 1 ¯1 ¯1 1 1 1 1 3 3 ¯3 ¯3 ¯1 ¯1 ¯1 ¯1 1 1 ¯1 ¯1 1 1 1 1 3 3 ¯1 ¯1 1 1 1 1 3 3 1 1 3 3 3 3 5 5
¯6 ¯4 ¯4 ¯2 ¯4 ¯2 ¯2 0 ¯4 ¯2 ¯2 0 ¯2 0 0 2 ¯4 ¯2 ¯2 0 ¯2 0 0 2 ¯2 0 0 2 0 2 2 4 ¯4 ¯2 ¯2 0 ¯2 0 0 2 ¯2 0 0 2 0 2 2 4 ¯2 0 0 2 0 2 2 4 0 2 2 4 2 4 4 6
并查看哪些列没有负值; 这是第二个布尔结果:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1
第5步:从上面的两个布尔结果中获取AND:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 0 0
这些是创建良好山脉的二进制数据列的位置。在列左下方,然后转换(为了便于阅读)到右边。 1是uphills, 2 编辑:0是下坡:
1 1 1 1 1 1 0 1 0 1 0 // 1 0 1 0 1 0 means /\/\/\
0 0 1 1 1 1 0 1 1 0 0
1 1 0 0 1 1 1 0 0 1 0
0 1 0 1 0 1 1 0 1 0 0 // means //\/\\
1 0 1 0 0 1 1 1 0 0 0
0 0 0 0 0
这是一个很好的答案。如果需要,我们可以应用格式:
format [the boolean result]
┌──────┬──────┬──────┬──────┬──────┐
│ │ │ │ │ /\ │
│ │ /\ │ /\ │ /\/\ │ / \ │
│/\/\/\│/\/ \│/ \/\│/ \│/ \│
└──────┴──────┴──────┴──────┴──────┘
稍大一点,n = 10:
DISP format¨↓fn 10
┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ /\ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ /\ │ │ │ │ │ │ │ │ │ │ │ │ │ │ /\ │ │ │ │ │ │ │ │ │ /\ │ /\ │ /\ │ /\ │ /\/\ │ / \ │
│ │ │ │ │ /\ │ │ │ │ │ /\ │ /\ │ /\ │ /\/\ │ / \ │ │ │ │ │ /\ │ │ │ │ │ /\ │ /\ │ /\ │ /\/\ │ / \ │ /\ │ /\ │ /\ │ /\ │ /\ /\ │ /\/\ │ /\/\ │ /\/\/\ │ /\/ \ │ / \ │ / \ │ / \/\ │ / \ │ / \ │
│ │ /\ │ /\ │ /\/\ │ / \ │ /\ │ /\ /\ │ /\/\ │ /\/\/\ │ /\/ \ │ / \ │ / \/\ │ / \ │ / \ │ /\ │ /\ /\ │ /\ /\ │ /\ /\/\ │ /\ / \ │ /\/\ │ /\/\ /\ │ /\/\/\ │ /\/\/\/\ │ /\/\/ \ │ /\/ \ │ /\/ \/\ │ /\/ \ │ /\/ \ │ / \ │ / \ /\ │ / \/\ │ / \/\/\ │ / \/ \ │ / \ │ / \/\ │ / \ │ / \ │ / \ │ / \/\ │ / \ │ / \ │ / \ │
│/\/\/\/\/\│/\/\/\/ \│/\/\/ \/\│/\/\/ \│/\/\/ \│/\/ \/\/\│/\/ \/ \│/\/ \/\│/\/ \│/\/ \│/\/ \/\│/\/ \│/\/ \│/\/ \│/ \/\/\/\│/ \/\/ \│/ \/ \/\│/ \/ \│/ \/ \│/ \/\/\│/ \/ \│/ \/\│/ \│/ \│/ \/\│/ \│/ \│/ \│/ \/\/\│/ \/ \│/ \/\│/ \│/ \│/ \/\│/ \│/ \│/ \│/ \/\│/ \│/ \│/ \│/ \│
└──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
编辑:当然,人们也可以在循环中完成所有这些操作。只需取一个数字,然后进行上面的检查(一个数= = n的一半,不低于地平线)。如果检查失败,则跳出。
答案 1 :(得分:3)
使用可用的括号生成代码有更多可能的方法。
完全按原样使用它,并将得到的括号字符串组转换为山形表示。
更新它以直接生成山脉弦乐。这是本答复中详述的替代方案。
这可以防止在构建解决方案时处理插入换行符的复杂性。解决方案完成后,将从此矩阵生成一个新字符串。
连接与矩阵的每一行相关联的字符串,在每行之后添加换行符。另外(未在下面的解决方案中实现),可以删除每行的尾随空格。
我们使用两个位置参数,表示为row
和col
,因为我们现在正在移动两个维度,它们是旧代码中count
参数的通讯员。 row
和col
表示到目前为止山脉引导我们的角落。在我们添加的每个字符后,col
(列)参数增加1。根据当前字符是对应于攀爬还是下降来更改row
参数。
这在1D案例中是隐含的,因为我们总是以固定长度的字符串结束,并且每个新解决方案都覆盖了以前的字符串。但是,在2D情况下,如果我们不清理为解决方案生成的路径,我们可能会在以下解决方案中看到它的一部分。
矩阵的大小为count
行(因为这是将生成的解决方案的最大高度)和2 * count
列(因为这是使用count
时的长度一双笔画)。矩阵最初用白色空格填充。
以下是根据上述想法更新的Java代码。 尽管列举了修改,核心逻辑是相同的(递归结构是相同的 - 决定是否尝试添加向上冲程/向下冲程并且终止标准不会改变)。
public static void addParen(ArrayList<String> list, int leftRem, int rightRem, char[][] str, int row, int col) {
if (leftRem < 0 || rightRem < leftRem) return; // invalid state
if (leftRem == 0 && rightRem == 0) { /* all out of left and right parentheses */
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length; i++) {
sb.append(String.copyValueOf(str[i]));
sb.append(System.lineSeparator());
}
list.add(sb.toString());
} else {
if (leftRem > 0) { // try a left paren, if there are some available
str[row][col] = '/';
addParen(list, leftRem - 1, rightRem, str, row - 1, col + 1);
str[row][col] = ' ';
}
if (rightRem > leftRem) { // try a right paren, if there’s a matching left
str[row + 1][col] = '\\';
addParen(list, leftRem, rightRem - 1, str, row + 1, col + 1);
str[row + 1][col] = ' ';
}
}
}
public static ArrayList<String> generateParens(int count) {
char[][] str = new char[count][count * 2];
for (int i = 0; i < str.length; i++) {
Arrays.fill(str[i], ' ');
}
ArrayList<String> list = new ArrayList<>();
addParen(list, count, count, str, count - 1, 0);
return list;
}
当输入为3时,下面是结果山脉(即,字符串的宽度为6,因为我们有3个向上笔划和3个向下笔划):
/\
/ \
/ \
/\/\
/ \
/\
/ \/\
/\
/\/ \
/\/\/\
现在可以回答一些有关这些字符串的有趣问题。
(Q1)特定宽度有多少个有效字符串?
(Q2)&#39; /&#39;随机序列的概率是多少?和&#39; \&#39;成为一个有效的山?
(Q3)包含相同数量的&#39; /&#39;的随机序列的概率是多少?和&#39; \&#39;成为一个有效的山?
下表针对各种字符串长度回答了这些问题:
Length Valid Total Prob. Q2 Equal / and \ Prob. Q3
2 1 4 25.0000% 2 50.0000%
4 2 16 12.5000% 6 33.3333%
6 5 64 7.8125% 20 25.0000%
8 14 256 5.4688% 70 20.0000%
10 42 1,024 4.1016% 252 16.6667%
12 132 4,096 3.2227% 924 14.2857%
14 429 16,384 2.6184% 3,432 12.5000%
16 1,430 65,536 2.1820% 12,870 11.1111%
18 4,862 262,144 1.8547% 48,620 10.0000%
20 16,796 1,048,576 1.6018% 184,756 9.0909%
22 58,786 4,194,304 1.4016% 705,432 8.3333%
24 208,012 16,777,216 1.2398% 2,704,156 7.6923%
26 742,900 67,108,864 1.1070% 10,400,600 7.1429%
28 2,674,440 268,435,456 0.9963% 40,116,600 6.6667%
30 9,694,845 1,073,741,824 0.9029% 155,117,520 6.2500%