字符串常量 - 判断字符串 s2
是否包含字符串 s1
的排列
s2
是否包含字符串 s1
的排列
1. 字符串常量 (string literal)
许多人对 C 语言不存在字符串类型感到奇怪,不过 C 语言提供了字符串常量。C 语言存在字符串的概念:它就是一串以 NUL
字节结尾的零个或多个字符。字符串通常存储在字符数组中,这也是 C 语言没有显式的字符串类型的原因。由于 NUL
字节是用于终结字符串的,所以在字符串内部不能有 NUL
字节。在一般情况下,这个限制并不会造成问题。之所以选择 NUL
作为字符串的终止符,是因为它不是一个可打印的字符。
字符串常量的书写方式是用一对双引号包围一串字符,如下所示:
“Hello”
“\aWarning!\a”
“Line 1\nLine2”
“”
字符串常量 (不像字符常量) 可以是空的。即使是空字符串,依然存在作为终止符的 NUL
宇节。
literal [ˈlɪtərəl]:adj. 文字的,逐字的,无夸张的
string [strɪŋ]:n. 线,弦,细绳,一串,一行
K&R C:
在字符串常量的存储形式中,所有的字符和 NUL
终止符都存储于内存的某个位直。K&R C 并没有提及一个字符串常量中的字符是否可以被程序修改,但它清楚地表明具有相同的值的不同字符串常量在内存中是分开存储的。因此,许多编译器都允许程序修改字符串常量。
ANSIC:
ANSIC 则声明如果对一个字符串常量进行修改,其效果是未定义的。它也允许编译器把一个字符串常量存储于一个地方,即使它在程序中多次出现。这就使得修改字符串常量变得极为危险,因为对一个常量进行修改可能殃及程序中其他字符串常量。许多 ANSI 编译器不允许修改字符串常量,或者提供编译时选项,让你自行选择是否允许修改字符串常量。在实践中,请尽量避免这样做。如果你需要修改字符串,请把它存储于数组中。
我之所以把字符串常量和指针放在一起讨论,是因为在程序中使用字符串常量会生成一个指向字符的常量指针
。当一个字符串常量出现于一个表达式中时,表达式所使用的值就是这些字符所存储的地址,而不是这些字符本身。因此,你可以把字符串常量赋值给一个指向字符的指针
,后者指向这些字符所存储的地址。但是,你不能把字符串常量赋值给一个字符数组,因为字符串常量的直接值是一个指针,而不是这些字符本身。
如果你觉得不能赋值或复制字符串显得不方便,你应该知道标准 C 函数库包含了一组函数,它们就用于操纵字符串,包括对字符串进行复制、连接、比较以及计算字符串长度和在字符串中查找特定字符的函数。
NULL
指针,它可以用零值来表示。
gets
函数从标准输入读取一行文本并把它存储于作为参数传递给它的数组中。一行输入由一串字符组成,以一个换行符 (newline) 结尾。gets
函数丢弃换行符,并在该行的末尾存储一个 NUL
字节 (一个 NUL
字节是指字节模式为全 0 的字节,类似 '\0'
这样的字符常量)。然后,gets
函数返回一个非 NULL
值,表示该行己被成功读取。当 gets
函数被调用但事实上不存在输入行时,它就返回 NULL
值,表示它到达了输入的末尾 (文件尾)。
在 C 程序中,处理字符串是常见的任务之二。尽管 C 语言并不存在 string
数据类型,但在整个语言中,存在一项约定:字符串就是一串以 NUL
字节结尾的字符。NUL
是作为字符串终止符,它本身并不被看作是字符串的一部分。字符串常量 (string literal) 就是源程序中被双引号括起来的一串字符。例如,字符串常量:
“Hello”
在内存中占据 6 个字节的空间,按顺序分别是 H
、e
、l
、l
、o
和 NUL
。
2. 字符串基础
字符串是一种重要的数据类型,但是 C 语言并没有显式的字符串数据类型,因为字符串以字符串常量的形式出现或者存储于字符数组中。字符串常量很适用于那些程序不会对它们进行修改的字符串。所有其他字符串都必须存储于宇符数组或动态分配的内存中。
字符串就是一串零个或多个字符,并且以一个位模式为全 0
的 NUL
字节结尾。因此,字符串所包含的字符内部不能出现 NUL
字节。这个限制很少会引起问题,因为 NUL
字节并不存在与它相关联的可打印字符,这也是它被选为终止符的原因。NUL
字节是字符串的终止符,但它本身并不是字符串的一部分,所以字符串的长度并不包括 NUL
字节。
头文件 string.h
包含了使用字符串函数所需的原型和声明。尽管并非必需,但在程序中包含这个头文件确实是个好主意,因为有了它所包含的原型,编译器可以更好地为你的程序执行错误检查。
NUL
是 ASCII 字符集中 ’\0’
字符的名字,它的字节模式为全 0。NULL
指一个其值为 0 的指针。它们都是整型值,其值也相同,所以它们可以互换使用。然而,你还是应该使用适当的常量,因为它能告诉阅读程序的人不仅使用。这个值,而且告诉他使用这个值的目的。
符号 NULL
在头文件 stdio.h
中定义。另一方面,并不存在预定义的符号 NUL
,所以如果你想使用它而不是字符常量 ’\0’
,你必须自行定义。
3. 判断字符串 s2
是否包含字符串 s1
的排列
给定两个字符串 s1
和 s2
,写一个函数来判断 s2
是否包含 s1
的排列。第一个字符串的排列之一是第二个字符串的子串。
3.1 解决方法:滑动窗口 + 直方图
判断字符串 s2
是否包含字符串 s1
的排列,字符串 s1
中所有的元素可以随机组合。通过比较 s1
长度的 s2
子串与 s1
中各种字符个数是否相同来进行判断 (只要各种元素个数相同,s1
就可以组合成当前 s2
子串的形式)。
滑动的时候,将 s2
当前窗口宽度后面的一个字符加到统计数组里面 (计数加一),将窗口左边的第一个字符在统计数组结果里面去掉 (计数减一)。
示例1:
输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").
示例2:
输入:s1= "ab" s2 = "eidboaoo"
输出:false
注意:
- 输入的字符串只包含小写字母。 (小写字母 共计 26 个。)
- 两个字符串的长度都在
[1, 10,000]
之间。
bool checkInclusion(char * s1, char * s2)
{
char *ps1 = s1;
char *ps2 = s2;
int len_s1 = 0;
int len_s2 = 0;
int head = 0;
int tail = 0;
int idx = 0;
int histogram_num = 26;
int histogram_s1[26] = { 0 };
int histogram_sub[26] = { 0 };
if ((NULL == s1) || (NULL == s2))
{
return false;
}
len_s1 = strlen(s1);
len_s2 = strlen(s2);
if (len_s1 > len_s2)
{
return false;
}
// histogram data
for (idx = 0; idx < len_s1; idx++)
{
histogram_s1[ps1[idx] - 'a']++;
histogram_sub[ps2[idx] - 'a']++;
}
for (head = 0, tail = len_s1; tail < len_s2; head++, tail++)
{
for (idx = 0; idx < histogram_num; idx++)
{
if (histogram_s1[idx] != histogram_sub[idx])
{
break;
}
}
if (idx == histogram_num)
{
return true;
}
histogram_sub[ps2[head] - 'a']--;
histogram_sub[ps2[tail] - 'a']++;
}
for (idx = 0; idx < histogram_num; idx++)
{
if (histogram_s1[idx] != histogram_sub[idx])
{
break;
}
}
if (idx == histogram_num)
{
return true;
}
else
{
return false;
}
}
3.1 特殊测试用例
bool checkInclusion(char * s1, char * s2)
{
char *ps1 = s1;
char *ps2 = s2;
int len_s1 = 0;
int len_s2 = 0;
int head = 0;
int tail = 0;
int idx = 0;
int histogram_num = 26;
int histogram_s1[26] = { 0 };
int histogram_sub[26] = { 0 };
if ((NULL == s1) || (NULL == s2))
{
return false;
}
len_s1 = strlen(s1);
len_s2 = strlen(s2);
printf("len_s1 = %d\n", len_s1);
printf("len_s2 = %d\n", len_s2);
if (len_s1 > len_s2)
{
printf("s1 = \"\*\*\*\", s2 = \"\";");
return false;
}
// histogram data
for (idx = 0; idx < len_s1; idx++)
{
histogram_s1[ps1[idx] - 'a']++;
histogram_sub[ps2[idx] - 'a']++;
}
for (head = 0, tail = len_s1; tail < len_s2; head++, tail++)
{
for (idx = 0; idx < histogram_num; idx++)
{
if (histogram_s1[idx] != histogram_sub[idx])
{
break;
}
}
if (idx == histogram_num)
{
printf("s1 = \"\", s2 = \"\*\*\*\";");
return true;
}
histogram_sub[ps2[head] - 'a']--;
histogram_sub[ps2[tail] - 'a']++;
}
for (idx = 0; idx < histogram_num; idx++)
{
if (histogram_s1[idx] != histogram_sub[idx])
{
break;
}
}
if (idx == histogram_num)
{
printf("s1 = \"\", s2 = \"\";");
return true;
}
else
{
return false;
}
}