leetcode第22题

简介: 而这个数列,其实除了括号匹配,还有很多类似的问题,其本质是一样的,例如,2n 个人排队买票,其中 n 个人持 50 元,n 个人持 100 元。每张票 50 元,且一人只买一张票。初始时售票处没有零钱找零。请问这 2n 个人一共有多少种排队顺序,不至于使售票处找不开钱?对于一个无限大的栈,一共n个元素,请问有几种合法的入栈出栈形式?P = a1 a2 a3 ... an,其中 ai 是矩阵。根据乘法结合律,不改变矩阵的相互顺序,只用括号表示成对的乘积,试问一共有几种括号化方案?n 个结点可构造多少个不同的二叉树?

image.png

给一个数字 n ,返回所有合法的括号匹配,刚好和20题相反。

自己没想出来,全部参考 LeetCode 给出的 Solution

解法一 暴力破解

列举所有的情况,每一位有左括号和右括号两种情况,总共 2n 位,所以总共 2^{2n}22n 种情况。

publicList<String>generateParenthesis(intn) {
List<String>combinations=newArrayList();
generateAll(newchar[2*n], 0, combinations);
returncombinations;
}
publicvoidgenerateAll(char[] current, intpos, List<String>result) {
if (pos==current.length) {
if (valid(current))
result.add(newString(current));
    } else {
current[pos] ='(';
generateAll(current, pos+1, result);
current[pos] =')';
generateAll(current, pos+1, result);
    }
}
publicbooleanvalid(char[] current) {
intbalance=0;
for (charc: current) {
if (c=='(') balance++;
elsebalance--;
if (balance<0) returnfalse;
    }
return (balance==0);
}

时间复杂度:对每种情况判断是否合法需要 O(n),所以时间复杂度是 O(2^{2n}n)O(22nn)

空间复杂度:O(2^{2n}n)O(22nn),乘以 n 是因为每个串的长度是 2n。此外这是假设所有情况都符合的时候,但其实不可能都符合,后边会给出更精确的情况。

解法二

解法一中,我们不停的加左括号,其实如果左括号超过 n 的时候,它肯定不是合法序列了。因为合法序列一定是 n 个左括号和 n 个右括号。

还有一种情况就是如果添加括号的过程中,如果右括号的总数量大于左括号的总数量了,后边不论再添加什么,它都不可能是合法序列了。因为每个右括号必须和之前的某个左括号匹配,如果右括号数量多于左括号,那么一定有一个右括号没有与之匹配的左括号,后边不论加多少左括号都没有用了。例如 n = 3 ,总共会有 6 个括号,我们加到 ( ) ) 3 个括号的情况的时候,有 1 个左括号,2 个右括号,此时后边 3 个括号无论是什么,已经注定它不会是合法序列了。

基于上边的两点,我们只要避免它们,就可以保证我们生成的括号一定是合法的了。

publicList<String>generateParenthesis(intn) {
List<String>ans=newArrayList();
backtrack(ans, "", 0, 0, n);
returnans;
}
publicvoidbacktrack(List<String>ans, Stringcur, intleft, intright, intn){
if (cur.length() ==n*2) {
ans.add(cur);
return;
    }
//左括号不要超过 nif (left<n)
backtrack(ans, cur+"(", left+1, right, n);
//右括号不要超过左括号if (right<left)
backtrack(ans, cur+")", left, right+1, n);
}

时间复杂度:

空间复杂度:

递归的复杂度分析,继续留坑 =.=。

解法三

解法二中是用列举的方法,仔细想想,我们每次用递归的时候,都是把大问题换成小问题然后去解决,这道题有没有这个思路呢?

我们想一下之前的列举过程,第 0 个位置一定会是左括号,然后接着添加左括号或右括号,过程中左括号数一定大于或等于右括号数,当第一次出现左括号数等于右括号数的时候,假如此时的位置是 c 。那么位置 1 到 c - 1 之间一定是合法序列,此外 c + 1 到最后的 2n -1 也是合法序列。而假设总共是 n 组括号,1 到 c - 1 是 a 组括号, c + 1 到 2n - 1 之间则是 n - 1 - a 组括号,如下图


最重要的是,每一个合法序列都会有这么一个数 c ,且唯一。所以我们如果要求 n 组括号的所有序列,只需要知道 a 组括号以及 ( n - a - 1) 组括号的所有序列,然后两两组合即可。

以 n = 3 为例,我们把 0 到 c 之间的括号数记为 a 组, c + 1 到最后的括号数记为 b 组,则

a = 0,b = 2 对应 ()(())以及 ()()() 两种情况,此时 c = 1。


a = 1,b = 1,对应 (())(()) 一种情况,此时 c = 3。


a = 2,b = 0 对应 ((())), (()()) 两种情况,此时 c = 5。


所以我们如果要想求 n 组括号,只需要知道 a 组和 b 组的情况,然后组合起来就可以了。

看起来我们在迭代 a ,其实本质上是在迭代 c ,c = 2a + 1,迭代 a 从 0 到 n - 1 ,就是迭代 c 从 1 到 2n - 1。看起来 c 都是奇数,其实是可以理解的,因为 0 到 c 间都是一组组的括号, 所以 c 一定是奇数。为什么可以迭代 c ,因为上边说到每一个合法序列都对应着一个 c ,遍历 c 的话,就能得到所有的情况了,看一下代码吧。

publicList<String>generateParenthesis(intn) {
List<String>ans=newArrayList();
if (n==0) {
ans.add("");
    } else {
for (inta=0; a<n; a++)
for (Stringleft: generateParenthesis(a))
for (Stringright: generateParenthesis(n-1-a))
ans.add("("+left+")"+right);
    }
returnans;
}

时间复杂度:

空间复杂度:

留坑。

扩展 卡塔兰数

如果这道题不是让你列举所有的情况, 而是仅仅让你输出 n 对应下有多少种合法序列,该怎么做呢?

答案就是 \frac{1}{n+1}C^n_{2n}n+11C2nn,也可以写成\frac{1}{n+1}\binom{2n}{n}n+11(n2n)。怎么证明呢?我主要参考了这里,说一下。

我们假设不考虑是不是合法序列,那么就一共有C^n_{2n}C2nn种情况,然后我们只需要把里边的非法情况减去就可以了,一共有多少种非法情况呢?

首先我们用C^n_{2n}C2nn就保证了一定是有 n 个左括号,n 个右括号,那么为什么出现了非法序列?

为了方便论述,我们把左括号记为 +1,右括号记为 -1.

ps:下边的 和 都是指两个数的和,不是你和我中的和。

我们假设非法序列的集合是 M ,而非法序列就是列举过程中右括号数比左括号数多了,也就是和小于 0 了,变成 -1 了。这种情况一旦出现,后边无论是什么括号都改变不了它是非法序列的命了。我们将第一次和等于 -1 的时候的位置记为 d 。每一个非法序列一定存在这样一个 d 。然后关键的地方到了!

此时我们把 0 到 d 所有的 -1 变成 1,1 变成 -1,我们将每一个非法序列都这样做,就构成了一个新的集合 N ,并且这个集合 N 一定和 M 中的元素一一对应( N -> M,在集合 N 中第一次出现和为 1 的位置也就是 d ,把 0 到 d 中所有的 -1 变成 1,1 变成 -1 就回到了 M),从而集合 M 的数量就等于集合 N 的数量。集合 N 的数量是多少呢?

我们来分析下集合 N 是什么样的,集合 N 对应的集合 M 原来的序列本来是这样的,在 0 到 d 之间和是 -1 ,也就是 -1 比 +1 多一个,d + 1 到最后的和一定是 1(因为 n 个 +1 和 n 个 -1 的和一定是 0 ,由于 0 到 d 和是 -1,后边的和一定是 1),也就意味着 +1 比 -1 多一个。而在集合 N 中,我们把 0 到 d 的 -1 变成了 +1 ,+1 变成了 -1 ,所以也变成了 +1 比 -1 多一个,所以集合 N 总共就是 +1 比 -1 多 2 个的集合,也就是 n + 1 个 +1 和 n - 1 个 -1 。

所以集合 N 就是 2n 个位置中选 n - 1 个位置放 -1,其他位置放 +1,总共就有 C^{n - 1}_{2n}C2nn1,所以集合 M 也有 C^{n - 1}_{2n}C2nn1种。

所有合法序列就有 C^n_{2n}-C^{n-1}_{2n}=\frac{1}{n+1}C^n_{2n}C2nnC2nn1=n+11C2nn

将集合 M 和集合 N 建立了一一映射,从而解决了问题,神奇!!!!!!!!!!其实,这个数列就是卡塔兰数,可以看下维基百科的定义。


而这个数列,其实除了括号匹配,还有很多类似的问题,其本质是一样的,例如,

2n 个人排队买票,其中 n 个人持 50 元,n 个人持 100 元。每张票 50 元,且一人只买一张票。初始时售票处没有零钱找零。请问这 2n 个人一共有多少种排队顺序,不至于使售票处找不开钱?

对于一个无限大的栈,一共n个元素,请问有几种合法的入栈出栈形式?

P = a1 a2 a3 ... an,其中 ai 是矩阵。根据乘法结合律,不改变矩阵的相互顺序,只用括号表示成对的乘积,试问一共有几种括号化方案?

n 个结点可构造多少个不同的二叉树?

... ...

更多例子可以看维基百科这里

而 Solutin 给出的时间复杂度,其实就是卡特兰数。


维基百科的给出的性质。


本以为这道题挺常规的,然后自己一直卡在解法三的理解上,查来查去,竟然查出了卡塔兰数,虽然似乎和解法三也没什么关系,但又开阔了很多思路。解法三分析出来的迭代方法,以及用映射证明卡塔兰数的求法,棒!

相关文章
|
5月前
leetcode-475:供暖器
leetcode-475:供暖器
44 0
|
索引
|
C++ Python
LeetCode 771. Jewels and Stones
LeetCode 771. Jewels and Stones
77 0
leetcode第39题
对回溯法又有了更深的了解,一般的架构就是一个大的 for 循环,然后先 add,接着利用递归进行向前遍历,然后再 remove ,继续循环。而解法二的动态规划就是一定要找到递进的规则,开始的时候就想偏了,导致迟迟想不出来。
leetcode第39题
leetcode第34题
从左向右遍历,一旦出现等于 target 的值就结束,保存当前下标。如果从左到右没有找到 target,那么就直接返回 [ -1 , -1 ] 就可以了,因为从左到右没找到,那么从右到左也一定不会找到的。如果找到了,然后再从右到左遍历,一旦出现等于 target 的值就结束,保存当前下标。 时间复杂度是 O(n)并不满足题意,但可以了解下这个思路,从左到右,从右到左之前也遇到过。
leetcode第34题
leetcode第51题
较经典的回溯问题了,我们需要做的就是先在第一行放一个皇后,然后进入回溯,放下一行皇后的位置,一直走下去,如果已经放的皇后的数目等于 n 了,就加到最后的结果中。然后再回到上一行,变化皇后的位置,然后去找其他的解。 期间如果遇到当前行所有的位置都不能放皇后了,就再回到上一行,然后变化皇后的位置。再返回到下一行。 说起来可能还费力些,直接看代码吧。
leetcode第51题
|
算法
leetcode第45题
时间复杂度:O(n)。 空间复杂度:O(1)。 这里要注意一个细节,就是 for 循环中,i < nums.length - 1,少了末尾。因为开始的时候边界是第 0 个位置,steps 已经加 1 了。如下图,如果最后一步刚好跳到了末尾,此时 steps 其实不用加 1 了。如果是 i < nums.length,i 遍历到最后的时候,会进入 if 语句中,steps 会多加 1 。
101 0
leetcode第45题
|
机器学习/深度学习
leetcode第50题
求幂次方,用最简单的想法,就是写一个 for 循环累乘。 至于求负幂次方,比如 2^{-10}2−10,可以先求出 2^{10}210,然后取倒数,1/2^{10}1/210 ,就可以了 double mul = 1; if (n > 0) { for (int i = 0; i < n; i++) { mul *= x; } } else { n = -n; for (int i = 0; i < n; i++) { mul *= x; } mul = 1 / mul; }
leetcode第50题
|
算法
leetcode第40题
会发现出现了很多重复的结果,就是因为没有跳过重复的 1。在求 opt [ 1 ] 的时候就变成了 [ [ 1 ],[ 1 ] ] 这样子,由于后边求的时候都是直接在原来每一个列表里加数字,所有后边都是加了两次了。
leetcode第40题