括号匹配问题
问题描述
在某个字符串(长度不超过100)中有左括号、右括号和大小写字母;规定(与常见的算数式子一样)任何一个左括号都从内到外与在它右边且距离最近的右括号匹配。写一个程序,找到无法匹配的左括号和右括号,输出原来字符串,并在下一行标出不能匹配的括号。不能匹配的左括号用“$”标注,不能匹配的右括号用“?”标注。 |
输入
输入包括多组数据,每组数据一行,包含一个字符串,只包含左右括号和大小写字母,字符串长度不超过100。 |
输出
对每组输出数据、输出两行,第一行包含原始输入字符,第二行由“$”,“?”和空格组成,“$”和“?”表示与之对应的左括号和右括号不能匹配。 |
Sample Input
((ABCD(x) )(rttyy())sss)( |
Sample Output
((ABCD(x) $$ )(rttyy())sss)( ? ?$ |
问题分析
这道题是在Coursera中的C语言课程中遇到的,属于递归函数的课后习题,很自然地,我想到用递归的方法去解答这道题,当然,之后才发现递归不是最佳解法。 网上关于本题已有不少样例代码,因此我将谈一下关于这道题我自己的做法。 在这里,我将搜寻与标记的函数放在了一个大循环内,以保证能够遍历整个字符串。在搜寻与标记的函数内,每当遇到一个左括号,我都会在左括号对应的位置标记“$”符号并递归搜寻,只有遇到了右括号后,才会将靠得最近的“$”符号删去,如果没有“$”符号,则标记“?”符号,以此达到整个标记的效果。这里值得注意的是,为了做到后续的删除,我就需要记下所有标记的“$”符号的位置。因此,这种方法的确稍显复杂与冗余。 第二种方法就是使用堆栈来存放左括号的位置,与我的上述方法很类似,只是我的方法需要不断地进行标记与删除,而使用堆栈的思想后,仅仅需要做堆栈的入和出即可记录未配对的左右括号的位置了,更加精妙。 |
代码
//方法一
/***********JSP ©copyright***********/
/*******Version 1.0 2018.08.16*******/
#include <stdio.h>
#include <string.h>
#define MAX_STRING_LENGTH 100
int leftFindMark[MAX_STRING_LENGTH] = { 0 };
int rightFindMark[MAX_STRING_LENGTH] = { 0 };
int point = 0;
int leftFindCount = 0;
int rightFindCount = 0;
void strMarkInit(char strMark[], char str[]);
void searchPair(char str[], char strMark[]);
void main(){
char str[MAX_STRING_LENGTH], strMark[MAX_STRING_LENGTH];
gets_s(str);
strMarkInit(strMark, str);
while (point >= 0 && point < strlen(str)){
searchPair(str, strMark);
}
puts(strMark);
}
void strMarkInit(char strMark[], char str[]){
unsigned int i = 0;
for (i = 0; i < strlen(str); i++){
strMark[i] = ' ';
}
strMark[i] = '\0';
}
void searchPair(char str[], char strMark[]){
if (str[point] == '('){
leftFindMark[leftFindCount++] = point;
strMark[point] = '$';
point++;
searchPair(str, strMark);
}
else if (str[point] == ')'){
rightFindMark[rightFindCount++] = point;
if (leftFindCount > 0){
rightFindCount--;
rightFindMark[rightFindCount] = 0;
strMark[leftFindMark[leftFindCount - 1]] = ' ';
leftFindCount--;
point++;
}
else{
rightFindMark[rightFindCount++] = point;
strMark[point] = '?';
point++;
}
return;
}
else{
point++;
}
}
//方法二
/**************网络共享***************/
#include <stdio.h>
#include <string.h>
#include <stack>
int main(){
int i = 0;
char str[101], strMark[101];
while (scanf("%s", str) != EOF){
std::stack<char> S;
for (i = 0; i < strlen(str); i++){
if (str[i] == '('){
S.push(i);
strMark[i] = ' ';
}
else if (str[i] == ')'){
if (S.empty()){
strMark[i] = '?';
}
else{
S.pop();
strMark[i] = ' ';
}
}
else{
strMark[i] = ' ';
}
}
while (!S.empty()){
strMark[S.top()] = '$';
S.pop();
}
strMark[i] = '\0';
puts(str);
puts(strMark);
}
return 0;
}
放苹果问题
问题描述
把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的放法?(用K表示) 注意:5,1,1和1,5,1是同一种分法。 |
输入数据
第一行是测试数据的数目t(0<=t<=20),以下每行均包含两个整数M和N,以空格分开。1<=M,N<=10。 |
输出要求
对输入的每组数据M和N,用一行输出相应的K。 |
输入样例
1 7 3 |
输出样例
8 |
问题分析
这是一道典型的递归题目,关键在于找到递归函数中的递归式与返回式以及递归的函数关系。 设f(m,n) 为m个苹果,n个盘子的放法数目,则先对n作讨论, 当n>m:必定有n-m个盘子永远空着,去掉它们对摆放苹果方法数目不产生影响。即if(n>m) f(m,n) = f(m,m) 当n<=m:不同的放法可以分成两类: 1、有至少一个盘子空着,即相当于f(m,n) = f(m,n-1); 2、所有盘子都有苹果,相当于可以从每个盘子中拿掉一个苹果,不影响不同放法的数目,即f(m,n) = f(m-n,n) 而总的放苹果的放法数目等于两者的和,即 f(m,n) =f(m,n-1)+f(m-n,n) 递归出口条件说明: 当n=1时,所有苹果都必须放在一个盘子里,所以返回1; 当没有苹果可放时,定义为1种放法; 递归的两条路,第一条n会逐渐减少,终会到达出口n==1; 第二条m会逐渐减少,因为n>m时,我们会return f(m,m) 所以终会到达出口m==0。 |
代码
#include<stdio.h>
int fun(int m, int n)
{
if (m == 0 || n == 1)
return 1;
if (n>m)
return fun(m, m);
else
return fun(m, n - 1) + fun(m - n, n);
}
int main()
{
int t, m, n;
scanf_s("%d", &t);
while (t--)
{
scanf_s("%d%d", &m, &n);
printf("%d\n", fun(m, n));
}
}
//#include<stdio.h>
//int sum;
//void fun(int m, int n, int max)
//{
// if (m == 0 && n == 0)//递归成功出口
// {
// sum++;
// return;
// }
// if (m<0 || n<0)
// return;//递归失败出口
// for (int i = max; i >= 0; i--){
// fun(m - i, n - 1, i);//递归
// }
//}
//int main()
//{
// int T, m, n;
// scanf_s("%d", &T);
// while (T--)
// {
// sum = 0;
// scanf_s("%d%d", &m, &n);
// fun(m, n, m);
// printf("%d\n", sum);
// }
// return 0;
//}