"逐步生成结果"类问题之数值类
1.上楼梯
有一个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶2阶3阶,
请编写程序,计算出小孩有多少种上楼梯的方法
题目分析,只有一个台阶的时候f(1)=1,当有两个台阶的时候f(2)=2,当有三个台阶的时候f(3)=4,当有四个台阶的时候就会有f(4)=f(1)+f(2)+f(3)=7,这样从一部分慢慢的生成结果
//逐步生成结果类问题-数值型
//上楼梯
//DP=f(x-1)+f(x-2)+f(x-3)
#include<stdio.h>
int slt(int n);
int main(void)
{
int n;
scanf("%d",&n);
printf("%d",slt(n));
return 0;
}
int slt(int n)
{
if(n==1)
return 1;
if(n==2)
return 2;
if(n==3)
return 4;
return slt(n-1)+slt(n-2)+slt(n-3);
}
2.机器人走方格
有X*Y的网络,一个机器人只能走格点且只能向右或向下走,从左上角走到右下角,请设计一个算法,计算出机器人有多少种走法。
题目分析:机器人只有两个情况要么向下走,要么向右走,从一个网格点到达下一个网格点又会有两种情况。向下图这样一步一步去推导,便可以得出规律,递归的代码写出来很简单,但是整个的推导过程就不容易看出。
//逐步生成结果类问题-数值型
//机器人走方格
//DP f(x,y)=f(x-1,y)+f(x,y-1);
#include<stdio.h>
int robot_go(int x,int y);
int main(void)
{
int x,y;
scanf("%d %d",&x,&y);
printf("%d",robot_go(x,y));
return 0;
}
int robot_go(int x,int y)
{
if(x==1||y==1)
return 1;
return robot_go(x-1,y)+robot_go(x,y-1);
}
3.硬币表示
有1,2,5,10,20,50,100,200不同面值的硬币,设计一种算法计算出这些硬币表示k有多少种。
题目分析:这个就是选和不选加上选几个的问题,我先选200,或者不选200,其他的交给你们去完成。当我们选了两百又会有两种方式,选100和不选100。不选200也有两种方式,选100和不选100,这样就会有很多种组合方式。在选择200时不光是选和不选还有200选几个,100选几个的问题,理解到这一点代码就好些多了。
//逐步生成结果类问题-数值型
//硬币表示
//输入k,可以1,2,5,10,20,50,100,200表示k的种类
#include<stdio.h>
int select_coins(int arr[],int n,int buf_sub);//参数:硬币种类,相当于输入的k,数组下标
int main(void)
{
int k;
scanf("%d",&k);
int buf[8]={1,2,5,10,20,50,100,200}; //硬币种类
printf("%d",select_coins(buf,k,7));//传入参数
return 0;
}
int select_coins(int arr[],int n,int buf_sub)
{
int res=0;
if(buf_sub==0)//只要到达1,那么就只有一种方法
return 1;
for(int i=0;i*arr[buf_sub]<=n;i++)//先依照个数选,比如先选零个200,其他的交给其他人,下一次我选1个两百,剩下的交给其他人去组合
{//在其他人进行选择中也是,先选零个其他交个别人,下一次在选1个其他的交给别人
int shengyu = shengyu-i*arr[buf_sub];
res+=select_coins(arr,shengyu,buf_sub--);//这里传入的参数
}
return res;
}
"逐步生成结果"类问题之非数字类
1.括号组合
输出n对括号的全部有效组合,例如2对括号,()(),(())
题目分析:逐步生成结果类问题需要一步一步推导,如下图,只有三种插入方式,插入左边右边,包起来。相同的就删去不在往下。
//逐步生成结果类问题-非字符型
//合法括号
//n对括号的全部组合
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<malloc.h>
void kuohao(int n,char arr[][100]);//在这里 char arr[][100]中,第二个方括号内必须是准确的数
int count = 0;
int main(void)
{
int n;
scanf("%d",&n);
char buf[100][100];
kuohao(n,buf);
for(int i=0;i<count;i++)
{
printf("%s\n",buf[i]);
}
return 0;
}
void kuohao(int n,char arr[][100])
{
if(n==1)
{
strcpy(arr[0],"()");
count++;
return ;
}
kuohao(n-1,arr);
//printf("count:%d\n",count);
int cnt=0;//因为每次添加括号都会使字符串数量改变,变量cnt用于刷新字符串的数量
//printf("cnt:%d\n",cnt);
//先创建数组pz,拷贝arr里面的内容
char**pz = (char **) malloc(count * sizeof(char *));
if(pz==NULL)
{
printf("内存分配失败\n");
exit(-1);
}
for(int i=0;i<count;i++)
{
pz[i]=(char *)malloc(strlen(arr[i])*sizeof(char));
strcpy(pz[i],arr[i]);
}
//,拷贝后可将数组初始化
memset(arr,0,sizeof(arr));
//printf("11%s11",arr[0]);
//printf("%s\n",pz[0]);
//对aaa数组里面的每一个字符串进行,加前括号,加后括号,包字符串
//期间要判断加前括号和加后括号是否相等、
for(int i=0;i<count;i++)
{
sprintf(arr[cnt],"%c%s%c",'(',pz[i],')');
cnt++;
sprintf(arr[cnt],"%s%c%c",pz[i],'(',')');
cnt++;
sprintf(arr[cnt],"%c%c%s",'(',')',pz[i]);
if(strcmp(arr[cnt],arr[cnt-1])==0)
memset(arr[cnt],0,sizeof(arr[cnt]));
else
cnt++;
}
//内存释放
for(int i=0;i<count;i++)
{
free(pz[i]);
}
free(pz);
count=cnt;
}
2.非空子集
返回某集合的所有子集,例如{A,B,C}子集有{A}{B}{C}{A,B}{A,C}{B,C}{A,B,C}
题目分析:法一:递归的老板思维,对于例子中的字符集,求子集时先让{A,B}把字符集求好,在用C字符去添加或不加
法二:运用二进制,字符集有3个元素,所组合成的非空子集有2^3-1种,对应的二进制为111,说明二进制1~111种二进制有1的说明该元素能够取到,为0则取不到,这样遍历1~7的二进制对应的1就可以得出子集。
//逐步生成结果类问题-非字符型
//非空子集
//返回某个集合的所有子集
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<malloc.h>
void ziji(char arr[][100],char arr_c[],int n);//n表示前几个元素后面的求非空子集
int cnt=0;//用于字符串计数
int main(void)
{
char buf0[]="AB";
char buf1[100][100];
ziji(buf1,buf0,strlen(buf0));
for(int i=0;i<cnt;i++)
{
printf("%s\n",buf1[i]);
}
return 0;
}
void ziji(char arr[][100],char arr_c[],int n)
{
if(n==1)
{
sprintf(arr[cnt++],"%c",arr_c[n-1]);
return ;
}
ziji(arr,arr_c,n-1);
//先动态创建数组,将数组arr[][]里面的内容复制到创建的数组里面
char **pz=(char **)malloc(cnt*sizeof(char *));
if(pz==NULL)
{
printf("动态分配内存失败\n");
exit(-1);
}
for(int i=0;i<cnt;i++)
{
pz[i]=(char*)malloc(100*sizeof(char));
strcpy(pz[i],arr[i]);
}
//然后初始化arr[][],以便后面元素到arr[][]
memset(arr,0,sizeof(arr));
int count=0;//记录在这个递归过程中,记录arr有多少条字符串
for(int i=0;i<cnt;i++)
{
strcpy(arr[count++],pz[i]);//不选择该元素
sprintf(arr[count++],"%s%c",pz[i],arr_c[n-1]);//选择该元素
}
sprintf(arr[count++],"%c",arr_c[n-1]);//该元素自己成为独立的一个字符串
//内存释放
for(int i=0;i<cnt;i++)
{
free(pz[i]);
}
free(pz);
//更新外围即计数变量
cnt=count;
}
//逐步生成结果类问题-非字符型
//非空子集
//返回某个集合的所有子集
//另一种解法,巧用二进制
#include<stdio.h>
#include<string.h>
#include<math.h>
void get_bin(int num,int arr[]);
int main(void)
{
char arr_char[]="ABC";
int arr_bin[strlen(arr_char)];
int num=pow(2,strlen(arr_char))-1;
while(num)
{
get_bin(num,arr_bin);
for(int i=0;i<strlen(arr_char);i++)//去判断每一位上是0还是1
{
if(arr_bin[i]==1)
printf("%c",arr_char[i]);
}
printf("\n");
memset(arr_bin,0,sizeof(arr_bin));
num--;
}
return 0;
}
void get_bin(int num,int arr[])//得到二进制数
{
int i=0;
while(num)
{
arr[i++]=num%2;
num=num/2;
}
}
3.全排列
对一个字符串,输出它的所有全排列字符串
题目分析:他不是硬币表示的要不要要几个的问题,它是全都要.
法一:插入的方式,先有一个字符串进行全排列就是它本身,又来了一个字符B,那么把他插入到最前面和最后面就有AB,BA,再来一个字符C就有CAB,ACB,ABC,对字符串AB进行插入还有CBA,BCA,BAC对字符串BA进行插入,这样反复。
法二:前缀法,利用回溯一个一个的把他交换到最前面,如下图逐步生成结果。
//ti7-2-4
//全排列
//插入的方式
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<string.h>
void sort_over(char arr[],char rqarr[][100],int n);
bool cheak_str(char arr[],char rqarr[][100],int k);
int count=0;//记录rqarr二维数组中字符串的串数
int main(void)
{
char buf[10]="ABC";
char rqbuf[100][100];
sort_over(buf,rqbuf,strlen(buf));
for(int i=0;i<count;i++)
{
printf("%s\n",rqbuf[i]);
}
return 0;
}
void sort_over(char arr[],char rqarr[][100],int n)
{
if(n==1)
{
sprintf(rqarr[count++],"%c",arr[n-1]);
return ;
}
sort_over(arr,rqarr,n-1);
//分配内存,创建一个指向二维数组的指针
//拷贝一份原始数组的内容
char **pz=(char **)malloc(count*sizeof(char *));
if(pz==NULL)
{
printf("动态内存分配失败\n");
exit(-1);
}
for(int i=0;i<count;i++)
{
pz[i]=(char *)malloc(100*sizeof(char));
strcpy(pz[i],rqarr[i]);
}
//初始化二维字符串数组
memset(rqarr,0,sizeof(rqarr));
//记录新的二维字符串里面有多少
int cnt=0;
//对二维字符串的每一条依次插入,i=0;插入第一条
for(int i=0;i<count;i++)
{
for(int j=0;j<=strlen(pz[i]);j++)
{
char str0_temp[100];//存放插入位置的前面的字符串
char str1_temp[100];//存放插入位置的后面的字符串
char str_temp[100];//存放插入好的字符串
/*
下面的注释代码是哪来避免只有字符串A时,截取字符串前缀和后缀问题
原因是因为在同一个for循环里,被分到同一块内存,在结束时仍然会保留上一次的结果
这个问题用下面的三个memset解决了
*/
// if(j==0)
// {
// sprintf(str_temp,"%c%s",arr[n-1],pz[i]);
// //printf("%s\n",str_temp);
// if(cheak_str(str_temp,rqarr,cnt)==false)//证明没有重复
// {
// strcpy(rqarr[cnt++],str_temp);
// }
// continue;
// }
// if(j==strlen(pz[i]))
// {
// sprintf(str_temp,"%s%c",pz[i],arr[n-1]);
// // printf("%s",str_temp);
// if(cheak_str(str_temp,rqarr,cnt)==false)//证明没有重复
// {
// strcpy(rqarr[cnt++],str_temp);
// }
// continue;
// }
strncpy(str0_temp,pz[i],j);
strncpy(str1_temp,pz[i]+j,strlen(pz[i])-j);
sprintf(str_temp,"%s%c%s",str0_temp,arr[n-1],str1_temp);
//检测字符串strtemp在rqarr数组中是否有重复
if(cheak_str(str_temp,rqarr,cnt)==false)//证明没有重复
{
strcpy(rqarr[cnt++],str_temp);
}
memset(str0_temp,0,sizeof(str0_temp));
memset(str1_temp,0,sizeof(str1_temp));
memset(str_temp,0,sizeof(str1_temp));
}
}
//释放内存
for(int i=0;i<count;i++)
{
free(pz[i]);
}
free(pz);
//更新二维字符串中字符串的数量
count=cnt;
}
bool cheak_str(char arr[],char rqarr[][100],int k)
{
for(int i=0;i<k;i++)
{
if(strcmp(rqarr[i],arr)==0)
return true;//证明有相同的字符串
}
return false;//证明没有相同的字符串
}
//ti7-2-5
//全排列
//回溯的方式
#include<stdio.h>
#include<string.h>
void sort_huisu(char arr[][100],char buf[],int k);
void swap(char arr[],int i,int k);
int count=0;//记录二维数组中字符串的串数
int main(void)
{
char arrr[100][100];
char arr[]="ABCD";
sort_huisu(arrr,arr,0);
for(int i=0;i<count;i++)
{
printf("%s\n",arrr[i]);
}
return 0;
}
void sort_huisu(char arr[][100],char buf[],int k)
{
if(k==strlen(buf))
{
strcpy(arr[count++],buf);
return ;
}
for(int i=k;i<strlen(buf);i++)
{
swap(buf,i,k);
sort_huisu(arr,buf,k+1);
swap(buf,i,k);//回溯
}
}
void swap(char arr[],int i,int k)
{
char temp=arr[i];
arr[i]=arr[k];
arr[k]=temp;
}