题目很简单,给定数字n,写出所有n组括号的分布情况
例如n=2,则有(()),()(),这两种
n=3,则有
((())),(()()),(())(),()(()),()()() 五种。
看到这个问题,第一反应可能是用递归,要得到 f(n), 等价于再f(n-1)的基础上加一对括号,
只要保证左右括号的相对位置即可!
例如:F(2), 等于再"()"上加一对括号;可能有以下几种情况:
(()),(()),()(),()(),(()),
其中红色是原来的位置,黑色是插入的位置。
显然会出现大量重复组合,所以用这种办法,可能需要借助HashSet来去重。
那有没有更简单的办法呢?
我们将左括号记为 1,右括号记为0,那么n对括号就等价于 n个“1”和n个“0”的组合,构成一个二进制数,且第一位一定是1(左括号),最后一位一定是0(右括号)
所以表现形式位 1xxxxxxxxx0,且每一位的左边1的个数不小于0的个数,每一位的右边,0的个数不小于1的个数。
所以我们有如下算法:
public static void p8_9(List<int> nums,int left,int right,int remain,int index,int pre)
{
if (remain == 0)
{
nums.Add(pre);
return;
}
if(left>right)
{
//补零不变
p8_9(nums, left, right + 1, remain, index - 1, pre);
}
//补1总是没问题,补1则remain减少
pre = pre | (1 << index);
index--;
left++;
remain--;
p8_9(nums, left, right, remain, index, pre);
}
public static void TestP8_9(int n)
{
int num = 1 << (n * 2-1);
List<int> nums = new List<int>();
p8_9(nums, 1, 0, n - 1, 2 * n - 2, num);
nums.ForEach(x => Console.WriteLine(Convert.ToString(x, 2)));
}
思路如下:
首先参数:
1 nums:存放最终的排列结果
2.left:统计当前index左边1的个数
3.right:统计当前index左边0的个数
4.index:游标,从左边第二位开始
5.pre:临时存放的数字, 初始为 1<<(2n-1);
6.remain: 剩余需要将0变为1的次数
假设n=3; 构建一个1<<(2*3-1) =100000;
此时参数为 left=1,right=0,remian=2,index=5,pre=100000
a.如果 ramain==0;即已经有三个1了,那么构造结束,将结果加入nums
b.如果左边1的个数大于0的个数比如 110000,那么index=3时,可以设为0,不出出现right>left的情况 ;如果时100000,那么就不能出现100xxx这种结构了,因为后面无论怎么排列都无法使之合法。所以设为0后,right的数目加1,remain保持不变,index往后移(index--).然后继续迭代
c.无论什么情况,只要剩余变1的次数不为0,就可以一直将该位设为1,极端情况就是111000,
这种情况要记得更新remain,他是终止运行的条件。
下面时n=4的运行结果:
10101010
10101100
10110010
10110100
10111000
11001010
11001100
11010010
11010100
11011000
11100010
11100100
11101000
11110000
将1->( ,0->),即可得到最终结果。
当然有个问题是,如果n太大超过16怎么办(即超过int32位数),这里可以用字符串实现,完全没问题。其次参数时可以简化的,知道index和left,right可以计算出来,remain也可以计算出来,这里为了直观,就没有简化。