今天看到有人分享了一个想法: 大厂裁员的消息,变少了,想跳槽加薪的人,变多了。 。 。
是这样吗?
确实从各个渠道得知,好像是有所回归,比去年这个时候强很多。
其实从社群的讨论也能发现,讨论面试的多了起来,讨论入职的也逐渐多了起来~
今天咱们趁着这个话题,给大家分享关于在面试中,非常常见的「滑动窗口」问题。
使用leetcode中,最为经典的2个问题。
感兴趣的同学,一起来看看~
无重复字符的最长子串 - 中等 3. 无重复字符的最长子串
给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc" ,所以其长度为 3。
示例 2:
输入: s = "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b" ,所以其长度为 1。
示例 3:
输入: s = "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke" ,所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
滑动窗口是一种用来解决数组或字符串中子串问题的有效算法。它可以将问题的时间复杂度从 降低到 ,其中 是字符串或数组的长度。
详细步骤
初始化 :定义一个滑动窗口,初始时窗口的左边界和右边界都指向字符串的起始位置(一开始窗口大小为 0)。同时定义一个哈希表用于记录字符的出现位置。
移动右边界 :从左到右遍历字符串,不断移动右边界,同时更新窗口和哈希表。
如果当前字符在哈希表中不存在,说明当前字符是新字符,将其加入窗口并更新哈希表。 如果当前字符在哈希表中已经存在,说明窗口中存在重复字符,需要收缩窗口,即移动左边界到哈希表记录的重复字符的下一个位置,同时更新哈希表中重复字符的位置。 更新最大长度 :在移动右边界的过程中,保持记录最大的窗口长度。
下面通过示例来说明这个算法的执行过程:
对于输入字符串 s = "abcabcbb"
:
移动右边界:依次遍历字符,第一个字符 'a' 加入窗口和哈希表,当前窗口为 "a"。 继续移动右边界:字符 'b' 加入窗口和哈希表,当前窗口为 "ab"。 继续移动右边界:字符 'c' 加入窗口和哈希表,当前窗口为 "abc"。 继续移动右边界:字符 'a' 在哈希表中已存在,窗口需要收缩,将左边界移动到哈希表记录的 'a' 的下一个位置(即原来的位置加1),此时窗口变为 "bca"。 继续移动右边界:字符 'b' 在哈希表中已存在,窗口需要收缩,将左边界移动到哈希表记录的 'b' 的下一个位置,此时窗口变为 "cab"。 继续移动右边界:字符 'b' 在哈希表中已存在,窗口需要收缩,将左边界移动到哈希表记录的 'b' 的下一个位置,此时窗口变为 "abc"。 继续移动右边界:字符 'b' 在哈希表中已存在,窗口需要收缩,将左边界移动到哈希表记录的 'b' 的下一个位置,此时窗口变为 "c"。 继续移动右边界:字符 'b' 加入窗口和哈希表,当前窗口为 "cb"。 继续移动右边界:字符 'b' 在哈希表中已存在,窗口需要收缩,将左边界移动到哈希表记录的 'b' 的下一个位置,此时窗口变为 "b"。 在遍历过程中,记录窗口长度的最大值为 3,因此答案是 3。
这就是使用滑动窗口算法解决这个问题的完整思路和步骤。
Python代码
def length_of_longest_substring (s: str) -> int: # 初始化左右指针和最大长度 left = 0 max_length = 0 # 使用字典记录字符的索引位置 char_index = {} for right in range(len(s)): # 如果当前字符已经在窗口中出现过 if s[right] in char_index: # 更新左指针位置,确保不会包含重复字符 left = max(left, char_index[s[right]] + 1 ) # 更新当前字符的索引位置 char_index[s[right]] = right # 更新最大长度 max_length = max(max_length, right - left + 1 ) return max_lengthdef main () : # 测试用例 test_cases = ["abcabcbb" , "bbbbb" , "pwwkew" ] for s in test_cases: print(f"Input: {s} " ) print(f"Output: {length_of_longest_substring(s)} " )if __name__ == "__main__" : main()
Java代码
import java.util.HashMap;import java.util.Map;public class LongestSubstringWithoutRepeatingCharacters { public static int lengthOfLongestSubstring (String s) { // 初始化左右指针和最大长度 int left = 0 ; int maxLength = 0 ; // 使用HashMap记录字符的索引位置 Map<Character, Integer> charIndex = new HashMap<>(); for (int right = 0 ; right < s.length(); right++) { // 如果当前字符已经在窗口中出现过 if (charIndex.containsKey(s.charAt(right))) { // 更新左指针位置,确保不会包含重复字符 left = Math.max(left, charIndex.get(s.charAt(right)) + 1 ); } // 更新当前字符的索引位置 charIndex.put(s.charAt(right), right); // 更新最大长度 maxLength = Math.max(maxLength, right - left + 1 ); } return maxLength; } public static void main (String[] args) { // 测试用例 String[] testCases = {"abcabcbb" , "bbbbb" , "pwwkew" }; for (String s : testCases) { System.out.println("Input: " + s); System.out.println("Output: " + lengthOfLongestSubstring(s)); } } }
这些代码都会输出测试用例的结果以验证算法的正确性。
找到字符串中所有字母异位词 - 中等 438. 找到字符串中所有字母异位词
给定两个字符串 s
和 p
,找到 s
中所有 p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
示例 1:
输入: s = "cbaebabacd" , p = "abc" 输出: [0,6] 解释: 起始索引等于 0 的子串是 "cba" , 它是 "abc" 的异位词。 起始索引等于 6 的子串是 "bac" , 它是 "abc" 的异位词。
示例 2:
输入: s = "abab" , p = "ab" 输出: [0,1,2] 解释: 起始索引等于 0 的子串是 "ab" , 它是 "ab" 的异位词。 起始索引等于 1 的子串是 "ba" , 它是 "ab" 的异位词。 起始索引等于 2 的子串是 "ab" , 它是 "ab" 的异位词。
解决这个问题的一种方法是使用滑动窗口。
详细步骤
初始化 :定义一个滑动窗口,初始时窗口的左边界和右边界都指向字符串的起始位置,同时定义一个哈希表用于记录窗口中字符的频次。
移动右边界 :从左到右遍历字符串,不断移动右边界,同时更新窗口和哈希表。
在每一步中,将右边界的字符加入窗口并更新哈希表中该字符的频次。 如果当前窗口大小等于目标字符串 p
的长度,即达到了需要匹配的长度,就需要进行下一步。 检查是否为异位词 :在窗口达到目标长度后,需要检查窗口中的字符频次是否与目标字符串 p
的字符频次相匹配。
然后,移动左边界,并更新哈希表中左边界字符的频次,以继续滑动窗口。 这样,通过滑动窗口的方法,我们可以找到所有 p
的异位词的起始索引。
下面通过示例来说明这个算法的执行过程:
对于输入字符串 s = "cbaebabacd"
和目标字符串 p = "abc"
:
移动右边界:依次遍历字符,第一个字符 'c' 加入窗口和哈希表,当前窗口为 "c"。 继续移动右边界:字符 'b' 加入窗口和哈希表,当前窗口为 "cb"。 继续移动右边界:字符 'a' 加入窗口和哈希表,当前窗口为 "cba"。 窗口大小达到目标长度,检查是否为异位词,匹配成功,记录当前窗口的起始索引 0。 移动左边界:将左边界字符 'c' 的频次减一,窗口变为 "ba"。 继续移动右边界:字符 'e' 加入窗口和哈希表,当前窗口为 "bae"。 继续移动右边界:字符 'b' 加入窗口和哈希表,当前窗口为 "baeb"。 窗口大小达到目标长度,检查是否为异位词,匹配成功,记录当前窗口的起始索引 3。 最终,得到所有 p
的异位词的起始索引 [0, 6]。
这就是使用滑动窗口算法解决这个问题的完整思路和步骤。
当使用滑动窗口算法解决这个问题时,我们可以维护一个哈希表,用于记录窗口中字符的频次。我们还需要一个计数器,用于记录当前窗口中已匹配字符的数量。
Python代码
from collections import Counterdef findAnagrams (s: str, p: str) : # 初始化窗口左右指针和计数器 left, right = 0 , 0 count = len(p) result = [] # 记录目标字符串p中每个字符的频次 p_count = Counter(p) # 遍历字符串s while right < len(s): # 如果当前字符在p中出现过,则将计数器减一 if p_count[s[right]] > 0 : count -= 1 # 更新p_count中当前字符的频次 p_count[s[right]] -= 1 # 右指针右移 right += 1 # 如果当前窗口长度等于p的长度 if count == 0 : # 判断当前窗口是否为p的异位词 if right - left == len(p): result.append(left) # 移动左指针 if p_count[s[left]] >= 0 : count += 1 p_count[s[left]] += 1 left += 1 return resultdef main () : # 测试用例 s1 = "cbaebabacd" p1 = "abc" print("Input:" , s1, p1) print("Output:" , findAnagrams(s1, p1)) # [0, 6] s2 = "abab" p2 = "ab" print("Input:" , s2, p2) print("Output:" , findAnagrams(s2, p2)) # [0, 1, 2] if __name__ == "__main__" : main()
Java代码
import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class FindAllAnagramsInAString { public static List<Integer> findAnagrams (String s, String p) { List<Integer> result = new ArrayList<>(); // 初始化窗口左右指针和计数器 int left = 0 , right = 0 , count = p.length(); // 记录目标字符串p中每个字符的频次 Map<Character, Integer> pCount = new HashMap<>(); for (char c : p.toCharArray()) { pCount.put(c, pCount.getOrDefault(c, 0 ) + 1 ); } // 遍历字符串s while (right < s.length()) { // 如果当前字符在p中出现过,则将计数器减一 if (pCount.containsKey(s.charAt(right)) && pCount.get(s.charAt(right)) > 0 ) { count--; } // 更新pCount中当前字符的频次 pCount.put(s.charAt(right), pCount.getOrDefault(s.charAt(right), 0 ) - 1 ); // 右指针右移 right++; // 如果当前窗口长度等于p的长度 if (count == 0 ) { // 判断当前窗口是否为p的异位词 if (right - left == p.length()) { result.add(left); } // 移动左指针 if (pCount.containsKey(s.charAt(left)) && pCount.get(s.charAt(left)) >= 0 ) { count++; } pCount.put(s.charAt(left), pCount.getOrDefault(s.charAt(left), 0 ) + 1 ); left++; } } return result; } public static void main (String[] args) { // 测试用例 String s1 = "cbaebabacd" ; String p1 = "abc" ; System.out.println("Input: " + s1 + ", " + p1); System.out.println("Output: " + findAnagrams(s1, p1)); // [0, 6] String s2 = "abab" ; String p2 = "ab" ; System.out.println("Input: " + s2 + ", " + p2); System.out.println("Output: " + findAnagrams(s2, p2)); // [0, 1, 2] } }
这些代码实现了滑动窗口算法,用于找到字符串 s
中所有字符串 p
的异位词的起始索引。
大家可以根据解释以及代码中的注释进行学习~
好了,今天的内容先这样,大家如果觉得有用,狠狠的赞起来~
有其他想看的内容,底部发消息。
我们下期再见喽~