deepseek搜索回答
嗯,我现在要解决的是找出一个字符串中无重复字符的最长子串的长度。这个问题看起来有点挑战性,但我得仔细想想怎么用C语言来实现。首先,我得理解题目要求。题目是说,给定一个字符串,找出其中不含有重复字符的最长子串的长度。比如像“abcabcbb”这样的字符串,最长子串是“abc”,长度是3,但后面的例子可能有更长的,比如“pwwkew”最长是“wke”或者“kew”,长度3。哦,对了,可能得考虑不同的情况。
那我该怎么开始呢?首先,我得想到如何判断一个子串是否有重复字符。直观的方法是检查每一个可能的子串,然后找出最长的那个。但是这种方法的时间复杂度会很高,尤其是当字符串很长的时候,可能会有O(n^3)的时间复杂度,因为要遍历所有子串,每个子串还要检查是否有重复字符。这样的方法对于较长的输入可能效率太低,所以得想想有没有更高效的办法。
记得之前学过滑动窗口(Sliding Window)的方法,可以用来处理这种子串问题。滑动窗口通常能将时间复杂度降到O(n)或者O(n^2),这取决于具体实现。所以应该用滑动窗口来解决这个问题。
滑动窗口的基本思想是维护一个窗口,窗口内的字符都是不重复的。然后通过移动左右指针来扩大或缩小窗口,同时记录窗口的最大长度。那具体怎么做呢?
比如,我们可以用两个指针,left和right,分别表示窗口的左右边界。初始时,两个指针都指向字符串的起始位置。然后,right指针不断向右移动,将新的字符纳入窗口。如果遇到重复字符,就需要移动left指针,直到窗口内不再有重复字符为止。
那么问题来了,如何快速判断当前字符是否在窗口内已经存在?这时候,可能需要一个数据结构来记录窗口内的字符出现情况。因为字符的ASCII范围有限,比如假设是256个字符的话,可以用一个数组来记录每个字符最近出现的位置。或者使用哈希表,但C语言中没有现成的哈希表结构,可能需要自己用数组模拟。
比如,我们可以创建一个大小为256的数组(假设所有可能的字符都在ASCII范围内),数组中的每个元素存储该字符最后一次出现的索引位置。初始时,数组的所有元素设为-1或者某个表示未出现的值。
当right指针向右移动时,检查当前字符是否在数组中的值(即上一次出现的位置)是否大于等于当前的left位置。如果是的话,说明这个字符在当前的窗口内已经存在,这时需要将left指针移动到该字符上一次出现位置的下一个位置,以确保窗口内没有重复。同时,更新该字符在数组中的值为当前的right位置。然后,计算当前窗口的长度(right - left +1),并与之前记录的最大长度比较,保存较大的值。
举个例子,假设字符串是“abcabcbb”:
初始化left=0,max_len=0,数组所有元素初始化为-1。
right=0,字符是'a',数组['a']的值是-1,小于left,所以可以更新数组['a']=0。此时窗口长度是1,max_len=1。
right=1,字符是'b',数组['b']=-1,更新为1。窗口长度2,max_len=2。
right=2,字符'c',同样处理,窗口长度3,max_len=3。
right=3,字符'a'。此时数组['a']=0,这个值是否>=left(当前left=0)?是的。所以left需要移动到0+1=1的位置。然后更新数组['a']=3。此时窗口长度是3(3-1+1=3?或者right-left+1?比如left=1,right=3,是3-1+1=3?是的。此时max_len还是3。
right=4,字符'b'。数组['b']的值是1。当前的left是1,而数组中的值是1,等于left。说明这个字符在窗口内存在。所以需要移动left到1+1=2。然后更新数组['b']=4。窗口长度是4-2+1=3,max_len仍为3。
继续这个过程,直到字符串末尾。这样最终得到max_len=3。
看来这个方法是可行的。那这样的话,整个算法的时间复杂度是O(n),因为每个字符最多被访问两次,left和right各一次。空间复杂度是O(1),因为数组的大小固定为256。
那现在需要考虑如何将这个思路转化为C语言代码。
首先,定义一个数组,比如int last_pos[256],初始化为-1。然后,初始化left=0,max_len=0。
然后,遍历字符串,每个字符的位置作为right,从0开始。对于每个right,当前字符是s[right]。
检查last_pos[s[right]]是否>= left。如果是的话,说明该字符在当前的窗口中出现过,需要更新left到last_pos[s[right]] +1的位置。
然后,更新last_pos[s[right]]为当前的right。然后计算当前的窗口长度,即right - left +1,与max_len比较,取较大者。
这样循环直到字符串结束。
需要注意的是,字符串可能为空,或者在处理过程中出现各种情况,比如整个字符串没有重复字符的情况。比如字符串“abcde”,那么每次right移动时,last_pos的值都是-1,所以left保持0,每次窗口长度增加,最终得到长度5。
那现在代码的大概结构应该如下:
int lengthOfLongestSubstring(char * s) {
int max_len = 0;
int left = 0;
int last_pos[256];
memset(last_pos, -1, sizeof(last_pos)); // 初始化为-1
int len = strlen(s);
for (int right = 0; right < len; right++) {
if (last_pos[s[right]] >= left) {
left = last_pos[s[right]] + 1;
}
last_pos[s[right]] = right;
int current_len = right - left + 1;
if (current_len > max_len) {
max_len = current_len;
}
}
return max_len;
}
这里需要注意,字符可能是有符号还是无符号的。比如,在C语言中,char可能是signed的,所以当处理超过127的字符时,数组的索引可能为负数,导致越界。比如,如果字符是0xFF(在signed char中是-1),那么数组的索引会是-1,这显然是不正确的。所以需要将字符转换为无符号类型,确保索引在0~255之间。
因此,在访问last_pos数组的时候,应该将s[right]强制转换为unsigned char,或者将数组的索引用正确的值。例如,可以使用:
last_pos[(unsigned char)s[right]] = right;
这样才能正确处理所有可能的字符,包括ASCII扩展字符。
因此,代码中的相关部分需要修改:
比如:
if (last_pos[(unsigned char)s[right]] >= left) {
同样,在赋值的时候也要用unsigned char转换。
这样修改后的代码应该可以处理所有情况。
那测试一下这个代码是否正确。比如,对于空字符串,返回0。对于“abcabcbb”,应该返回3。对于“bbbbb”返回1。对于“pwwkew”返回3。这些测试用例是否都能通过?
比如“pwwkew”的处理过程:
初始化left=0,max=0。遍历每个字符:
right=0,字符'p',last_pos['p']=-1 < left,所以更新last_pos['p']=0。current_len=1,max=1。
right=1,字符'w',同样,last_pos['w']=-1,更新为1。current_len=2,max=2.
right=2,字符'w'。此时last_pos['w']=1 >= left(0)。所以left=1+1=2。更新last_pos['w']=2。current_len=2-2+1=1。max保持2.
right=3,字符'k'。last_pos['k']=-1。更新为3。current_len=3-2+1=2. max保持2.
right=4,字符'e'。last_pos['e']=-1。更新为4。current_len=4-2+1=3。max变为3.
right=5,字符'w'。此时last_pos['w']=2。当前left是2。因为last_pos['w']=2 >= left(2),所以left=2+1=3。更新last_pos['w']=5。current_len=5-3+1=3。max还是3.
所以最终max是3,正确。
那这个算法应该是正确的。那现在要考虑代码的正确性,尤其是字符处理的问题。例如,字符是signed的情况,所以必须转换成unsigned。
此外,对于输入字符串s,如果是空字符串,则返回0。例如,当len为0时,循环不会执行,直接返回max_len=0,这正确。
那现在把这个思路写成代码。需要注意,在C语言中,函数内部需要处理字符串的长度,可以用strlen函数,但需要包含<string.h>头文件。同时,memset用于初始化last_pos数组,需要包含<string.h>或者<memory.h>?
综合来看,这个方案应该是正确的。
为了找到无重复字符的最长子串的长度,我们可以使用滑动窗口算法。这种方法通过维护一个窗口,确保窗口内的字符都是唯一的,从而高效地解决问题。以下是详细的解决方案:
方法思路
-
滑动窗口:使用两个指针
left
和right
分别表示窗口的左右边界。 -
字符位置记录:使用一个数组
last_pos
记录每个字符最后一次出现的位置。 -
窗口调整:当遇到重复字符时,移动左指针到重复字符的下一个位置,确保窗口内无重复。
-
计算最大长度:在每次调整窗口后,计算当前窗口长度并更新最大值。
这种方法的时间复杂度为 O(n),空间复杂度为 O(1)(固定大小的数组)。
解决代码
c
复制
#include <string.h>
int lengthOfLongestSubstring(char * s) {
int max_len = 0;
int left = 0;
int last_pos[256];
memset(last_pos, -1, sizeof(last_pos)); // 初始化所有字符的位置为-1
int len = strlen(s);
for (int right = 0; right < len; right++) {
unsigned char current_char = s[right]; // 处理signed char的问题
if (last_pos[current_char] >= left) {
left = last_pos[current_char] + 1;
}
last_pos[current_char] = right;
int current_len = right - left + 1;
if (current_len > max_len) {
max_len = current_len;
}
}
return max_len;
}
代码解释
-
初始化:
last_pos
数组初始化为 -1,表示所有字符最初未出现。 -
遍历字符串:右指针
right
遍历每个字符。 -
处理重复字符:如果当前字符已在窗口内(
last_pos[current_char] >= left
),移动左指针到重复字符的下一个位置。 -
更新字符位置:记录当前字符的最新位置。
-
计算窗口长度:每次迭代后计算当前窗口长度,并更新最大长度。
这种方法确保在每次迭代中高效地维护窗口,从而快速找到最长无重复子串的长度。
个人思考
本题最主要的是对于最后一次出现位置数组的理解和语句
if (last_pos[current_char] >= left)
的理解
last_pos[256]使用last_pos['字符']的方式记录字符串中字符最后出现的位置,当right增大时,right所指字符如果是原来出现过的字符,那么last_pos中就会有上一次出现的位置,如果这个位置比left的值小,说明不在窗口内,如果比left大,说明窗口中已经有了,那么就需要改变left的值