废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!本篇Blog的主题是最长无重复子串或最长无重复子数组,这类题目出现频率还是很高的。
最长无重复子串【MID】
先来看字符串数据结构的题目
题干
解题思路
整体目标就是获取最大的无重复滑动窗口
- 双指针标识数组或字符串的位置,右指针可以理解为放大窗口指针,左指针可以理解为缩小窗口指针
- 定义一个set用来存储元素位置对应的值
- 右指针先行,如果一直无重复就一直开拓窗口并更新max值,否则移动左指针缩小窗口,直到将重复值缩到窗口以外。
如下图所示:
代码实现
基本数据结构:字符串
辅助数据结构:哈希表
算法:迭代
技巧:双指针、滑动窗口
class Solution { public int lengthOfLongestSubstring(String s) { // 1 判断入参是否为空列表 if (s.length() == 0) { return 0; } // 2 定义返回结果最大值和左右指针以及滑动窗口集合 int max = 0; int left = 0; int right = 0; Set<Character> set = new HashSet<>(); // 3 滑动窗口移动并在无重复时计算最大值 while (left < s.length() && right < s.length()) { // 1 无重复,右指针继续移动,重新计算最大值 if (!set.contains(s.charAt(right))) { set.add(s.charAt(right++)); max = Math.max(max, right - left); } else { // 2 有重复,左指针继续移动,直到将重复元素移出集合 set.remove(s.charAt(left++)); } } return max; } }
复杂度分析
时间复杂度为O(N),因为遍历了字符串;空间复杂度为O(N),借助了HashSet的存储空间
最长回文子串【MID】
一道中心扩展思想解决的MID题目
题干
解题思路
- 每个字符都可以尝试作为中心点看,会出现两种情况:可能是类似 aba 的字符串,也可能是类似 abba 的情况
- 只需要分别计算出以一个和两个字符作为中心点的子串,取出较大的长度即可
- 从left到right开始向两边扩散、比较,如果相等则继续扩散比较;如果不相等则剪枝,不用再继续扩散比较
- 计算每次比较的回文子串长度,取最大
代码实现
给出代码实现基本档案
基本数据结构:字符串
辅助数据结构:无
算法:迭代
技巧:双指针、中心扩散法
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param s string字符串 * @return int整型 */ public String longestPalindrome(String s) { // 1 边界条件判断 if (s.length() < 2) { return s; } // 2 初始化参数,结果参数的第一位存储起始位置,第二位存储长度 int maxLength = 0; int[] result = new int[2]; // 3 for (int i = 0; i < s.length(); i++) { // 中心位置奇数情况下扩展结果 int[] odd = centerSpread(s, i, i); // 中心位置偶数情况下扩展结果 int[] even = centerSpread(s, i, i + 1); // 当前中心位置最大子串 int[] curMax = odd[1] > even[1] ? odd : even; // 当前中心位置最大子串如果大于历史记录最大子串则暂存最大值及预期返回结果 if (curMax[1] > maxLength) { maxLength = curMax[1]; result = curMax; } } // 截取返回结果,本来如果起点是1,长度是2,那么结尾下标应该2(1+2-1),这里结尾为1+2=3,是因为3不会被计入,因为substring左闭右开区间,所以计算为(1+2-1+1(为开区间+1)=1+2=3) return s.substring(result[0], result[0] + result[1]); } // 扩散的核心方法 public int[] centerSpread(String s, int left, int right) { // 双指针在边界内,且满足扩散条件 while (left >= 0 && right <= s.length() - 1 && s.charAt(left) == s.charAt(right)) { left--; right++; } // 回文子串为左右指针开区间内的部分:right-1-(left+1)+1=right-left-1 return new int[] {left + 1, right - left - 1}; } }
复杂度分析
时间复杂度 O(N^2):平均需要遍历每个结点作为中心点O(N),还需要从中心点向左右扩散比较O(N)
空间复杂度 O(1):只用到常量
最长公共前缀【EASY】
来道EASY题散散心
题干
解题思路
当字符串数组长度为 0 时则公共前缀为空,直接返回;令最长公共前缀 ans 的值为第一个字符串,进行初始化;
遍历后面的字符串,依次将其与 ans 进行比较,两两找出公共前缀,最终结果即为最长公共前缀;如果查找过程中出现了 ans 为空的情况,则公共前缀不存在直接返回;
代码实现
基本数据结构:字符串
辅助数据结构:无
算法:迭代
技巧:指针
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param strs string字符串一维数组 * @return string字符串 */ public String longestCommonPrefix (String[] strs) { // 1 入参判断,如果数组列表为空,则返回空 if (strs.length <= 0) { return ""; } // 2 初始化第一个字符串为最长公共前缀 String commonPrefix = strs[0]; for (int i = 1; i < strs.length; i++) { // 两两比较字符串,更新最长公共前缀 commonPrefix = twoStrLongestCommonPrefix(commonPrefix, strs[i]); // 如果最长公共前缀为"",则停止比较,返回"" if(commonPrefix==""){ return ""; } } return commonPrefix; } // 比较两个字符串的最长公共前缀 private String twoStrLongestCommonPrefix(String str1, String str2) { // 1 定义下标及下标最大移动范围 int index = 0; int maxLength = Math.min(str1.length(), str2.length()); // 2 下标移动两两比较字符串对应字符,只要在索引范围内且字符相同就继续移动 while (index < maxLength && str1.charAt(index) == str2.charAt(index)) { index++; } // 3 返回两个子串的最长公共前缀 return str1.substring(0, index); } }
复杂度分析
时间复杂度:O(mn),其中 m是字符串数组中的字符串的平均长度,n 是字符串的数量。最坏情况下,字符串数组中的每个字符串的每个字符都会被比较一次。
空间复杂度:O(1)。使用的额外空间复杂度为常数