3.无重复字符的最长子串
3.1 题目
给定一个字符串 s
,请你找出其中不含有重复字符的最长子串的长度。
示例:
示例 1:
- 输入:s = “abcabcbb”
- 输出:3
- 解释:因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
- 输入:s = “bbbbb”
- 输出:1
- 解释:因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
- 输入:s = “pwwkew”
- 输出:3
- 解释:因为无重复字符的最长子串是 “wke”,所以其长度为 3。
- 请注意,你的答案必须是 子串 的长度,”pwke” 是一个子序列,不是子串。
提示:
- 0 <= s.length <= 5 * 104
- s 由英文字母、数字、符号和空格组成
3.2 题解
双循环法
// 方法一:双循环法
// 用两层循环,外层循环遍历字符串的每个字符作为子串的起始位置,内层循环遍历以当前字符为起始的所有子串,判断是否包含重复字符,从而找到最长的不含重复字符的子串。
static int LengthOfLongestSubstring1(string s)
{
// 如果输入字符串为空,直接返回0
if (s.Length == 0)
{
return 0;
}
// 如果输入字符串长度为1,直接返回1
if (s.Length == 1)
{
return 1;
}
// 初始化最大长度和对应的子串
int maxNum = 1; //至少有1个元素的话 子串至少都为1 所以初始化为1
string resultString = "";
// 外层循环,遍历所有可能的起始位置
for (int startIndex = 0; startIndex < s.Length; startIndex++)
{
// 临时变量,用于记录当前子串的长度
int tempMaxNum = 0;
// 内层循环,遍历以startIndex为起始位置的所有子串
for (int endIndex = startIndex + 1; endIndex < s.Length; endIndex++)
{
// 判断子串中是否包含当前字符 如果有重复的了往后继续遍历肯定也重复 没有意义 跳出循环
if (s.Substring(startIndex, endIndex - startIndex).Contains(s[endIndex]))
{
// 发现重复字符,结束内层循环
break;
}
// 计算当前子串的长度
tempMaxNum = endIndex - startIndex + 1;
// 如果当前临时最大长度大于全局最大长度,更新全局最大长度和对应子串
if (tempMaxNum > maxNum)
{
maxNum = tempMaxNum;
resultString = s.Substring(startIndex, endIndex - startIndex + 1);
}
}
}
//如果最长子串长度还是1 直接返回第一个元素算了
if (maxNum == 1)
{
resultString = s[0].ToString();
}
// 输出最长子串
Console.WriteLine(resultString);
// 返回最大长度
return maxNum;
}
滑动窗口单循环缓存字典法
// 方法二:滑动窗口单循环缓存字典法
// 使用滑动窗口的思想,维护两个指针startIndex和endIndex,以及一个字典charIndexDictionary来存储字符及其在字符串中的最新索引。
// 通过单循环遍历字符串,判断是否有重复字符,从而找到最长的不含重复字符的子串。
// 每次发现重复元素后,开始位置的变化规律是变为前方重复元素的后一位(关键规律)。注意其实位置不能往前走只能往后走,往后走要特殊处理。
static int LengthOfLongestSubstring2(string s)
{
// 如果输入字符串为空或为null,直接返回0
if (string.IsNullOrEmpty(s))
{
return 0;
}
// 如果输入字符串长度为1,直接返回1
if (s.Length == 1)
{
return 1;
}
string resultString = "";
// 初始化最大长度和起始位置
int maxNum = 0;
int startIndex = 0;
// 创建一个字典,用于记录字符及其在字符串中的索引
Dictionary<char, int> charIndexDictionary = new Dictionary<char, int>();
// 遍历字符串中的每个字符
for (int endIndex = 0; endIndex < s.Length; endIndex++)
{
// 如果当前字符在字典中存在,且其索引大于等于起始位置,要保证起始位置不能王回头
if (charIndexDictionary.ContainsKey(s[endIndex]) && charIndexDictionary[s[endIndex]] >= startIndex)
{
// 每次发现重复元素后,startIndex的变化规律是变为前方重复元素的后一位(关键规律)
// 如果是,更新起始位置为重复字符的下一个位置
startIndex = charIndexDictionary[s[endIndex]] + 1;
}
// 更新字典中字符的索引为当前位置
charIndexDictionary[s[endIndex]] = endIndex;
if (endIndex - startIndex + 1 > maxNum)
{
resultString = s.Substring(startIndex, endIndex - startIndex + 1);
}
// 计算当前不含重复字符的子串长度,并更新最大长度
maxNum = Math.Max(maxNum, endIndex - startIndex + 1);
}
// 输出最长子串
Console.WriteLine(resultString);
// 返回最大长度
return maxNum;
}
3.3 代码
class Program
{
static void Main()
{
#region 题目
// 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
// 示例 1:
// 输入: s = "abcabcbb"
// 输出: 3
// 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
// 示例 2:
// 输入: s = "bbbbb"
// 输出: 1
// 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
// 示例 3:
// 输入: s = "pwwkew"
// 输出: 3
// 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
// 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
// 提示:
// 0 <= s.length <= 5 * 104
// s 由英文字母、数字、符号和空格组成
#endregion
#region 测试
// 示例 1
string s1 = "abcabcbb";
int result11 = LengthOfLongestSubstring1(s1);
Console.WriteLine($"示例1 方法1 输出:{result11}");
int result12 = LengthOfLongestSubstring2(s1);
Console.WriteLine($"示例1 方法2 输出:{result12}");
// 示例 2
string s2 = "bbbbb";
int result21 = LengthOfLongestSubstring1(s2);
Console.WriteLine($"示例2 方法1 输出:{result21}");
int result22 = LengthOfLongestSubstring2(s2);
Console.WriteLine($"示例2 方法2 输出:{result22}");
// 示例 3
string s3 = "pwwkew";
int result31 = LengthOfLongestSubstring1(s3);
Console.WriteLine($"示例3 方法1 输出:{result31}");
int result32 = LengthOfLongestSubstring2(s3);
Console.WriteLine($"示例3 方法2 输出:{result32}");
#endregion
}
#region 答案
// 方法一:双循环法
// 用两层循环,外层循环遍历字符串的每个字符作为子串的起始位置,内层循环遍历以当前字符为起始的所有子串,判断是否包含重复字符,从而找到最长的不含重复字符的子串。
static int LengthOfLongestSubstring1(string s)
{
// 如果输入字符串为空,直接返回0
if (s.Length == 0)
{
return 0;
}
// 如果输入字符串长度为1,直接返回1
if (s.Length == 1)
{
return 1;
}
// 初始化最大长度和对应的子串
int maxNum = 1; //至少有1个元素的话 子串至少都为1 所以初始化为1
string resultString = "";
// 外层循环,遍历所有可能的起始位置
for (int startIndex = 0; startIndex < s.Length; startIndex++)
{
// 临时变量,用于记录当前子串的长度
int tempMaxNum = 0;
// 内层循环,遍历以startIndex为起始位置的所有子串
for (int endIndex = startIndex + 1; endIndex < s.Length; endIndex++)
{
// 判断子串中是否包含当前字符 如果有重复的了往后继续遍历肯定也重复 没有意义 跳出循环
if (s.Substring(startIndex, endIndex - startIndex).Contains(s[endIndex]))
{
// 发现重复字符,结束内层循环
break;
}
// 计算当前子串的长度
tempMaxNum = endIndex - startIndex + 1;
// 如果当前临时最大长度大于全局最大长度,更新全局最大长度和对应子串
if (tempMaxNum > maxNum)
{
maxNum = tempMaxNum;
resultString = s.Substring(startIndex, endIndex - startIndex + 1);
}
}
}
//如果最长子串长度还是1 直接返回第一个元素算了
if (maxNum == 1)
{
resultString = s[0].ToString();
}
// 输出最长子串
Console.WriteLine(resultString);
// 返回最大长度
return maxNum;
}
// 方法二:滑动窗口单循环缓存字典法
// 使用滑动窗口的思想,维护两个指针startIndex和endIndex,以及一个字典charIndexDictionary来存储字符及其在字符串中的最新索引。
// 通过单循环遍历字符串,判断是否有重复字符,从而找到最长的不含重复字符的子串。
// 每次发现重复元素后,开始位置的变化规律是变为前方重复元素的后一位(关键规律)。注意其实位置不能往前走只能往后走,往后走要特殊处理。
static int LengthOfLongestSubstring2(string s)
{
// 如果输入字符串为空或为null,直接返回0
if (string.IsNullOrEmpty(s))
{
return 0;
}
// 如果输入字符串长度为1,直接返回1
if (s.Length == 1)
{
return 1;
}
string resultString = "";
// 初始化最大长度和起始位置
int maxNum = 0;
int startIndex = 0;
// 创建一个字典,用于记录字符及其在字符串中的索引
Dictionary<char, int> charIndexDictionary = new Dictionary<char, int>();
// 遍历字符串中的每个字符
for (int endIndex = 0; endIndex < s.Length; endIndex++)
{
// 如果当前字符在字典中存在,且其索引大于等于起始位置,要保证起始位置不能王回头
if (charIndexDictionary.ContainsKey(s[endIndex]) && charIndexDictionary[s[endIndex]] >= startIndex)
{
// 每次发现重复元素后,startIndex的变化规律是变为前方重复元素的后一位(关键规律)
// 如果是,更新起始位置为重复字符的下一个位置
startIndex = charIndexDictionary[s[endIndex]] + 1;
}
// 更新字典中字符的索引为当前位置
charIndexDictionary[s[endIndex]] = endIndex;
if (endIndex - startIndex + 1 > maxNum)
{
resultString = s.Substring(startIndex, endIndex - startIndex + 1);
}
// 计算当前不含重复字符的子串长度,并更新最大长度
maxNum = Math.Max(maxNum, endIndex - startIndex + 1);
}
// 输出最长子串
Console.WriteLine(resultString);
// 返回最大长度
return maxNum;
}
#endregion
}
3.4 运行结果
abc
示例1 方法1 输出:3
abc
示例1 方法2 输出:3
b
示例2 方法1 输出:1
b
示例2 方法2 输出:1
wke
示例3 方法1 输出:3
wke
示例3 方法2 输出:3
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com