Leetcode-括号生成-暴力法

Leetcode

个人学习记录,不做它用。

括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例:

输入:n = 3
输出:[
       "((()))",
       "(()())",
       "(())()",
       "()(())",
       "()()()"
     ]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/generate-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方法一(暴力法)

思路:

  1. 首先找出所有的以左括号开头,右括号结尾的不重复的序列,形成序列集S。
  2. 找出S中所有有效的序列并输出。
序列集S的形成方法:

为了依次输出所有不重复的序列,这里考虑采用树来存储所有分支,再以特定方式遍历树得到所有的序列。

第一步:生成树

很容易的发现,所有可能的序列最开始一定以左括号开头,这里可以提前筛选让暴力算法不是那么暴力。一个简单的递归,其中l_num和r_num的作用就是维持整体的括号数的一致,原因同上。

Node* create_treenode(char value, int l_num, int r_num)
{
	//初始化结点
	Node *node = (Node*)malloc(sizeof(Node));
	node->value = value;
	node->l_child = node->r_child=NULL;
	node->l_num = l_num;
	node->r_num = r_num;

	//进入递归,如果还有可分配的左括号,则调用create_treenode('(', l_num-1, r_mum)
	//右括号类似
	if (l_num > 0 && node->l_child == NULL)
	{
		node->l_child = create_treenode('(', l_num-1, r_num);
	}
	if (r_num > 0 && node->r_child == NULL)
	{
		node->r_child = create_treenode(')', l_num, r_num-1);
	}
	//递归返回
    if(l_num == 0 && r_num == 0){
		types++;
	}
	return node;
}
第二步:通过树获取所有可能的序列,存入一个指针数组中

以一个第一步三对括号生成的树为例,从根结点到每个叶子结点的所有路径即为序列集S,可以看出应采用类似先序遍历的方式,先输出根结点,再向左或向右深入。

但是,问题是怎么在寻找完一个路径之后自动切换到下一个呢?这里考虑的方法是:每次只读出一条路径,去掉已读的部分,再次从根开始遍历即为另一路径。

首先,可以让程序判断,当前的结点如果左右子树都为空,则说明到了路径的末尾,那么就需要在输出值之后把自己free掉,为了保证free掉的结点变成NULL,特设立一个标志位flag,每次free掉一个结点就把flag置1,代表返回之后应该把原来的这个结点置为NULL,而后返回,把free掉的结点改为NULL,flag置0,继续循环。

但是如何只遍历一条路径且删除完不必要的结点之后自行退出呢?正常情况下,遍历该树会遍历完左子树之后遍历右子树,遍历右子树后自然退出,如果只遍历一条路径,绝对不能出现遍历完左子树之后遍历右子树的情况,所以遍历完左子树应该直接回溯,保证每次只走一条路。代码如下:

void print_to_array(Node *root, char array[], int array_startnum)
{
	array[array_startnum] = root->value;
	if(root->l_child != NULL){
		print_to_array(root->l_child, array, array_startnum+1);
		if(flag ==1){
			root->l_child = NULL;
			flag = 0;
		}
		if(root->r_child != NULL){
			return;
		}
	}
	if(root->r_child != NULL){
		print_to_array(root->r_child, array, array_startnum+1);
		if(flag == 1){
			root->r_child = NULL;
			flag = 0;
		}
	}
	if(root->l_child == NULL && root->r_child ==NULL){
		free(root);
		flag = 1;
	}
}

函数体第一行是将值输入的数组中存储,其余代码如上所述。

main函数调用部分如下所示:

int main(int argc, char const *argv[])
{
	Node* root = (Node*)malloc(sizeof(Node));
	root = create_treenode('(', n-1, n);
	char array[2*n];
	char* print_array[types];
	int i = 0;
	int j = 0;
	for(i = 0; i < types; i++){
		if(flag == 1){
			root->l_child = NULL;
			flag = 0;
		}
		print_to_array(root, array, 0);
		print_array[i] = array;
		for(j = 0;j < 2*n;j++){
			printf("%c ",array[j]);
		}
		printf("\n");
	}
	system("pause");
	return 0;
}

仅仅这样的话,其实仍然存在问题,我们可以注意到,flag=1需要回溯到上一次才能置NULL,那么对于根节点的孩子,如果它们左右子树都为空,返回上一层就是返回到了main函数,所以一定要在main函数中也做出置NULL的操作。

types的值在第一个代码块递归返回处体现,因为叶子结点数就是不同序列的个数。

完整代码如下:

#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#define n 4 //定义括号对数
//定义结点
typedef struct tree_node
{
	char value;//符号内容
	struct tree_node * l_child;//左孩子
	int l_num;//可分配的剩余左括号的数量
	struct tree_node * r_child;//右孩子
	int r_num;//可分配的剩余右括号的数量
}Node;
static int types = 0;
static int flag = 0;

Node* create_treenode(char value, int l_num, int r_num)
{
	//初始化结点
	Node *node = (Node*)malloc(sizeof(Node));
	node->value = value;
	node->l_child = node->r_child=NULL;
	node->l_num = l_num;
	node->r_num = r_num;

	//进入递归,如果还有可分配的左括号,则调用create_treenode('(', l_num-1, r_mum)
	//右括号类似
	if (l_num > 0 && node->l_child == NULL)
	{
		node->l_child = create_treenode('(', l_num-1, r_num);
	}
	if (r_num > 0 && node->r_child == NULL)
	{
		node->r_child = create_treenode(')', l_num, r_num-1);
	}
	//递归返回
	if(l_num == 0 && r_num == 0){
		types++;
	}
	return node;

}

//void print_treenode(Node* root)
//{
//	if(root->l_child != NULL){
//		print_treenode(root->l_child);
//	}
//	printf("%c",root->value);
//	if(root->r_child != NULL){
//		print_treenode(root->r_child);
//	}
//}

void print_to_array(Node *root, char array[], int array_startnum)
{
	array[array_startnum] = root->value;
	if(root->l_child != NULL){
		print_to_array(root->l_child, array, array_startnum+1);
		if(flag ==1){
			root->l_child = NULL;
			flag = 0;
		}
		if(root->r_child != NULL){
			return;
		}
	}
	if(root->r_child != NULL){
		print_to_array(root->r_child, array, array_startnum+1);
		if(flag ==1){
			root->r_child = NULL;
			flag = 0;
		}
	}
	if(root->l_child == NULL && root->r_child ==NULL){
		free(root);
		flag = 1;
	}
}

int main(int argc, char const *argv[])
{
	Node* root = (Node*)malloc(sizeof(Node));
	root = create_treenode('(', n-1, n);
	char array[2*n];
	char* print_array[types];
	int i = 0;
	int j = 0;
	for(i = 0; i < types; i++){
		if(flag == 1){
			root->l_child = NULL;
			flag = 0;
		}
		print_to_array(root, array, 0);
		print_array[i] = array;
		for(j = 0;j < 2*n;j++){
			printf("%c ",array[j]);
		}
		printf("\n");
	}
	//print_treenode(root);
	//printf("\n");
	//printf("%d\n",types);
	system("pause");
	return 0;
}
验证序列是否有效的方法:
方法一:
  1. 构造一个栈,初始时栈中有一元素 ‘#’ 。
  2. 遍历序列。
  3. 如果是左括号则入栈,继续遍历,否则检查栈中是否有左括号,有则弹出一个左括号,继续遍历,若无,则序列无效。
  4. 当序列遍历完成时,如果栈顶为 ‘#’ ,则说明序列有效,否则无效。

可以看出,如果序列长为m,最坏的情况下,栈所占用的空间为 m 2 + 1 \frac{m}{2}+1 2m+1字节(char类型占一字节)。

方法二:

将方法一抽象,每次遍历到第n个元素是右括号时,检查栈中是否存在左括号,实际就是在检查[0,n]这个子序列中二者个数关系(每次来一个右括号都会领一个左括号走,可以允许左括号等右括号,因为可能后面还有右括号,但不能允许右括号等左括号,因为这样一定有错误,所以左括号数量一定要大于等于右括号),在序列遍历完成之后,如果还有剩下的左括号,那么就是无效序列,因为不可能有右括号去匹配了。

在序列遍历完成之前,每次见到一个右括号,我们都需要检查二者数量是否相等,序列遍历完成之后,我们要看有没有剩余的左括号,不妨定义一个变量difference,每次遍历到一个左括号,我们就把difference加一,每次来一个右括号,我们就可以把difference减一,在每次减一之后,我们检查一下difference是否小于0,是则序列无效,否则继续遍历,遍历完成后,如果difference大于0,代表还剩左括号,序列无效,为0,刚好配对完成,序列有效。

验证方法比较简单,可以自行举例验证。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值