无重复字符的最长子串

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>?

综合来看,这个方案应该是正确的。

为了找到无重复字符的最长子串的长度,我们可以使用滑动窗口算法。这种方法通过维护一个窗口,确保窗口内的字符都是唯一的,从而高效地解决问题。以下是详细的解决方案:

方法思路

  1. 滑动窗口:使用两个指针 left 和 right 分别表示窗口的左右边界。

  2. 字符位置记录:使用一个数组 last_pos 记录每个字符最后一次出现的位置。

  3. 窗口调整:当遇到重复字符时,移动左指针到重复字符的下一个位置,确保窗口内无重复。

  4. 计算最大长度:在每次调整窗口后,计算当前窗口长度并更新最大值。

这种方法的时间复杂度为 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;
}

代码解释

  1. 初始化last_pos 数组初始化为 -1,表示所有字符最初未出现。

  2. 遍历字符串:右指针 right 遍历每个字符。

  3. 处理重复字符:如果当前字符已在窗口内(last_pos[current_char] >= left),移动左指针到重复字符的下一个位置。

  4. 更新字符位置:记录当前字符的最新位置。

  5. 计算窗口长度:每次迭代后计算当前窗口长度,并更新最大长度。

这种方法确保在每次迭代中高效地维护窗口,从而快速找到最长无重复子串的长度。

个人思考

本题最主要的是对于最后一次出现位置数组的理解和语句

if (last_pos[current_char] >= left) 

的理解

last_pos[256]使用last_pos['字符']的方式记录字符串中字符最后出现的位置,当right增大时,right所指字符如果是原来出现过的字符,那么last_pos中就会有上一次出现的位置,如果这个位置比left的值小,说明不在窗口内,如果比left大,说明窗口中已经有了,那么就需要改变left的值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值