划分字母区间(C语言)
字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
输入:S = “ababcbacadefegdehijhklij”
输出:[9,7,8]
解释:
划分结果为 “ababcbaca”, “defegde”, “hijhklij”。
每个字母最多出现在一个片段中。
像 “ababcbacadefegde”, “hijhklij” 的划分是错误的,因为划分的片段数较少。
本人知识有限,请多包涵。
我的思路:
首先遍历整个字符串,找出其中最大的字母,根据这个字母即可知道总共有多少种字母,如abcdef最大字母是f, f的ASCII码值是102 ,102减去96即为6位,我们就可以创造一个二维数组将这六种字母按顺序存储进入其中 (但是如果是ef这种情况我们就不能直接这样做而应该判断一下字符串长度与最大字母的ASCII码减去96的大小,取小的那一个)
然后,这样方便于我们记录每一种字母起始出现位置和最后出现位置,最后我们进行比较
如果两个字母他们拥有公共区间,那么他们就可以放在一起
这个公共区间可以是全部被包含(如1-9与2-7),也可以是部分相交(如18-23与19-24),这些都需要我们判断,最终我们判断出每一个小区间的最大位置
然后再创造一个一维数组d来保存每一个小区间的长度即可
PS:这道题真的真的真的好复杂呀!!!
先放代码,详解见下:
#include<stdio.h>
#include<string.h>
int far(char a[],int x,int y)/*由这个函数得出一个相同字母相隔的最远距离
x是该字母第一次出现时的位置,y是这个字符串最大长度,max指的是第几位*/
{
const int temp = a[x-1];
int max = x;
for(int i = x;i<y;i++)
{
if(a[i] == temp)
{
max = i+1;//最远的那个值的位置
}
}
return max;
}
int long_(char a[],int x)//求字符串中出现的最大字母在26子母中的位置,见下方的max
{
int max = a[0],i;
for(i = 0;i<x;i++)
{
if(a[i] >= max)
{
max = a[i];
}
}
for(i = 0;a[i] != '\0';)
{
i++;
}
if(i < max)
{
max = i;
}
return max;
}
int main()
{
int i,j,All = 0;//all用来记录二维数组的最大有效位数
char a[30];
printf("请输入一个字符串:\n");
scanf("%s",a);
const int sum = strlen(a);//获取字符串长度
int max = long_(a,sum);
if(max > 96)/*max就是字符串中出现的最大字母在26子母中的位置,如a = 1
这个时候我们应该输入的是小写字母*/
{
max = long_(a,sum) - 96;
}
else
{
max = long_(a,sum);
}
char b[20][3];
b[0][0] = a[0];
b[0][1] = far(a,1,sum);//第一个位置,所以上方函数中要写a[0]即a[x-1]
int count = 1;
for(i = 1;i<sum;i++)/*将这 max 位数归纳在一起录入二维数组中,效果位:如abcdefg 则b[0][0] == 'a' b[1][0] =='b'
b[x(0<= x < max)][1] = 这个字符出现的最后一位*/
{
for(j = 0;j<max;j++)
{
if(a[i] == b[j][0])
{
count++;/*因为这是重复的字母,若后续出现了第一次出现的字母
因为重复字母所引起的 i的多加 也需要被考虑到例如abab 因为有两个a所以i多加了一个*/
break;
}
if(a[i] != b[j][0] && j == i-count)
{
b[j+1][0] = a[i];
All++;
b[j+1][1] = far(a,i+1,sum);//因为数组中是从0开始算起,所以我们在计算长度是需要认为的加一位
break;
}
}
}
for(i = 0;i<=All;i++)//找到每个字母第一次出现的位置
{
for(j = 0;j<max;j++)
{
if(a[j] == b[i][0])
{
b[i][2] = j+1;
break;
}
}
}
int c[20][2],k;//c第一位用来储存每个区间的最大长度,第二位用来储存这个区间的最小值
count = 1;//同上的count一样的用法
c[0][0] = b[0][1];//最长
c[0][1] = b[0][2]; //最短
j = 1;
for(i = 0;i<=All;i++)
{
for(;j<=All+1;j++)//因为j是从1开始的所以我们需要让ALL加一才能检索到所有的数组
{
if(c[i][0] < b[j-count][1])
{
if(c[i][0] > b[j-count][2])
{
c[i][0] = b[j-count][1];
}
else
{
c[i+1][0] = b[j-count][1];
c[i+1][1] = b[j-count][2];
count++;
j++;
break;
}
}
}
}
int d[10];
d[0] = c[0][0];
for(i = 1;i<count;i++)
{
d[i] = c[i][0] - c[i-1][0];
}
for(i = 0;i<count;i++)
{
printf("%d ",d[i]);
}
}
第一步:当然是得出这个数组中有多少种字母
int long_(char a[],int x)//求字符串中出现的最大字母在26子母中的位置
{
int max = a[0],i;
for(i = 0;i<x;i++)
{
if(a[i] >= max)
{
max = a[i];
}
}
for(i = 0;a[i] != '\0';)
{
i++;
}
if(i < max)
{
max = i;
}
return max;
}
第二步:我们建立一个二维数组将这些字母储存起来,并且记录它们的最终位置
找最终位置依靠这个函数来实现:
int far(char a[],int x,int y)/*由这个函数得出一个相同字母相隔的最远距离
x是该字母第一次出现时的位置,y是这个字符串最大长度,max指的是第几位*/
{
const int temp = a[x-1];
int max = x;
for(int i = x;i<y;i++)
{
if(a[i] == temp)
{
max = i+1;//最远的那个值的位置
}
}
return max;
}
字符串最大长度y我们用strlen就可以得到(头文件要加include<string.h>)。
然后我们将这些字母分别储存在二位数组中
char b[20][3];
b[0][0] = a[0];
b[0][1] = far(a,1,sum);//第一个位置,所以上方函数中要写a[0]即a[x-1]
int count = 1;
for(i = 1;i<sum;i++)/*将这 max 位数归纳在一起录入二维数组中,效果为:如abcdefg 则b[0][0] == 'a' b[1][0] =='b'
b[x(0<= x < max)][1] = 这个字符出现的最后一位*/
{
for(j = 0;j<max;j++)
{
if(a[i] == b[j][0])
{
count++;/*因为这是重复的字母,若后续出现了第一次出现的字母
因为重复字母所引起的 i的多加 也需要被考虑到例如abab 因为有两个a所以i多加了一个*/
break;
}
if(a[i] != b[j][0] && j == i-count)
{
b[j+1][0] = a[i];
All++;
b[j+1][1] = far(a,i+1,sum);//因为数组中是从0开始算起,所以我们在计算长度是需要认为的加一位
break;
}
}
}
我认为这是这道题最有意义的地方,原因在于这种方法不止是可以将字符串分类储存,它可以适用于所有的二维数组储存问题 比如我上次发的最大元素那道题这个嵌套中的count避免了因为有元素重复而导致二维数组的录入位数错误问题。
第三步:就到了最繁琐的地方啦,我们需
要判断哪些字母被放在一个区间中,就像我上面说的,如果两个字母他们拥有公共区间,那么他们就可以放在一起
这个公共区间可以是全部被包含(如1-9与2-7),也可以是部分相交(如18-23与19-24),对于这道题我们可以人为的先列出来每个字母的区间 如图:
但是计算机它不晓得这些区间有啥用呀!所以我们还需要判断
int c[20][2],k;//c第一位用来储存每个区间的最大长度,第二位用来储存这个区间的最小值
count = 1;//同上的count一样的用法
c[0][0] = b[0][1];//最长
c[0][1] = b[0][2]; //最短
j = 1;
for(i = 0;i<=All;i++)
{
for(;j<=All+1;j++)//因为j是从1开始的所以我们需要让ALL加一才能检索到所有的数组
{
if(c[i][0] < b[j-count][1])
{
if(c[i][0] > b[j-count][2])
{
c[i][0] = b[j-count][1];
}
else
{
c[i+1][0] = b[j-count][1];
c[i+1][1] = b[j-count][2];
count++;
j++;
break;
}
}
}
}
这么长的嵌套主要意思就是:
首先令第一个元素为第一个区间的最大长度,它的最小值也就是这个区间的最小值,然后我们将它的最大值与后续的比较,如果遇到比它大的那么就判断另一个元素是否与前面的这个有交集,
我们就拿这几个字母举例
20(h的最远值)小于23(i的最远值),我们再来判断 18(i的最小值)小于20(h的最远值)所以它们是有交集的可以被放在一个区间之中,所以这个时候我们令最大值变成23,后续i与j的比较也是这样,如果后续出现一个字母的最远值大于这个值,且它的最小值也大于它 ,比如说后面还有一个m 25-28,28大于目前的最大值24,25也大于24,这说明它们没有交集,所以从这时起就到了另外一个区间了,我们令M 的28 为新的最大值,21为新的最小值,开始下一轮判断。
最后,这道题,我们会得到 9 16 24 这三个数,令后面的值减去前面的值就是每个区间的长度了
int d[10];
d[0] = c[0][0];
for(i = 1;i<count;i++)
{
d[i] = c[i][0] - c[i-1][0];
}
for(i = 0;i<count;i++)
{
printf("%d ",d[i]);
}
这道题的衍生产物就是我上面所说的,二维数组+嵌套的正确形式它可以帮助我们将一大堆元素分类合并,并且不会因为元素重复的原因而是录入的位置错误。