目录
1 找到字符串中所有字母异位词
分析:
代码展示:
代码展示:
2 串联所有单词的子串
分析:
代码展示:
3 串联所有单词的子串
分析:
代码展示:
4 水果成篮
分析:
代码展示:
1 找到字符串中所有字母异位词
分析:
这道题我们首先想到的暴力解法就是枚举出所有的可能字串然后进行判断就行。而最大的难点在于判断是否相同。
我们先来讲该暴力解法的优解,滑动窗口。经过前几题的练习应该很容易想到这种字串问题用滑动窗口。滑动窗口就不多赘述,我们来介绍这道题的细节和难点;
1 遍历详细过程:我们首先要定义两个Map<Character,Interger>,分表存储p和遍历过的s。定义两个指针left 和 right。right向右遍历,当遇到一个字符就存入map。假设p的字符数为3,直到right和left之间存入三个字符时,进行判断,成立的话则存入list,然后left++,right++。直到right遍历到s最后一个字符。
2 判断相同的两种方式
1 创建两个map,一个存储字符串p,另一个存储遍历过s的,并且用一个for遍历字符串p,如果两个map.getOrDefault(key,0)返回的值均相同那么就符合要求;
2 创建两个map,mapS和mapP。我们创建一个变量count。来记录right遍历的时候存入的有效字符。什么是有效字符,比如p的字符个数为2,那么我们用滑动窗口遍历字符串s的时候,如果这个字符在mapP中,并且mapS的该字符个数小于等于mapP时我们就count++,如果遇到的字符不在mapP中count就不++。最后判断count是否等于p.length()即可。
后面会多次用到整个方法,请理解记住。
代码展示:
class Solution {public List<Integer> findAnagrams(String s, String p) {int left = 0;int right = 0;Map<Character,Integer> mapS = new HashMap<>();Map<Character,Integer> mapP = new HashMap<>();int count = 0;List<Integer> list = new ArrayList<>();for (int i = 0; i < p.length(); i++) {int total = mapP.getOrDefault(p.charAt(i) , 0) + 1;mapP.put(p.charAt(i) , total);}while( right < s.length()) {int total = mapS.getOrDefault(s.charAt(right) , 0) + 1;mapS.put(s.charAt(right) , total);if(mapS.getOrDefault(s.charAt(right) , 0) <= mapP.getOrDefault(s.charAt(right),0) ) {count++;}right++;if(count == p.length()) {list.add(left);}if(right - left >= p.length()) {if(mapS.getOrDefault(s.charAt(left) , 0) <= mapP.getOrDefault(s.charAt(left),0) ) {count--;}int out = mapS.getOrDefault(s.charAt(left) , 0) - 1;mapS.put(s.charAt(left) , out);left++;}}return list;}
}
这个运行结果时间不是最优的,是因为hashMap,我们将map换成计数数组即可。即创建int[] c = new int[26],字符a的存储下标:'a' - 'a' 。不过map的通用性较强。
代码展示:
class Solution {public List<Integer> findAnagrams(String s, String p) {int left = 0;int right = 0;int[] mapS = new int[26];int[] mapP = new int[26];int count = 0;List<Integer> list = new ArrayList<>();for (int i = 0; i < p.length(); i++) {mapP[p.charAt(i) - 'a']++;}while( right < s.length()) {if(++mapS[s.charAt(right) - 'a'] <= mapP[s.charAt(right) - 'a']) {count++;}if(count == p.length()) {list.add(left);}if(++right - left >= p.length()) {if(mapS[s.charAt(left) - 'a']-- <= mapP[s.charAt(left) - 'a']) {count--;}left++;}}return list;}
}
我们看出这个代码简化了很多。
2 串联所有单词的子串
分析:
这道题和上一题如出一辙,只是上一题是字符,这道题是字符串,核心解法是不变的。假设word每个元素长度是3 ,我们遍历s的时候每次以3为一组进行遍历即可。
这道题有两个难点:
demo: s = "cabcd" words = ["ab",'cd'].; len = words[0].length();
1 示例中未给出所有可能情况;
这个示例的返回结果应该是[1];但是当我们以0开始遍历的时候,每次遍历程度为2,此时是无法得出结果的。因此,我们要让left和right从0开始完整遍历一次,从1开始完整遍历。当我们再2开始遍历的时候可以发现和从0开始遍历是相同的,因此到2的时候就无需再遍历一次了。这时候得到的才是完整答案;
2 条件。
当我们从0开始遍历的时候,如果整个遍历的循环条件是:right < s.length()。那么由于right每次遍历两个元素,就会越界,因此我们的条件应该是:right + len <= s.length();
代码展示:
class Solution {public List<Integer> findSubstring(String s, String[] words) {List<Integer> list = new ArrayList<>();int len = words[0].length();int totalL = len * words.length;int sum = 0;while(sum < len) {int left = sum;int right = sum;Map<String, Integer> map1 = new HashMap<>();Map<String, Integer> map2 = new HashMap<>();int count = 0;for (int i = 0; i < words.length; i++) {int tmp = map2.getOrDefault(words[i], 0) + 1;map2.put(words[i], tmp);}while (right + len <= s.length()) {
// int k = right;
// char[] c = new char[len];
// while (k < s.length() && k < len + right) {
// c[k - right] = s.charAt(k);
// k++;
// }
// String s1 = new String(c);String s1 = s.substring(right,right + len);int tmp = map1.getOrDefault(s1, 0) + 1;map1.put(s1, tmp);right = right + len;if (map1.getOrDefault(s1, 0) <= map2.getOrDefault(s1, 0)) {count++;}if (right - left >= totalL) {if (count == words.length) {list.add(left);}//int index = left;
// char[] c2 = new char[len];
// while (index < s.length() && index < len + left) {
// c2[index - left] = s.charAt(index);
// index++;
// }s1 = s.substring(left,left + len);if (map1.getOrDefault(s1, 0) <= map2.getOrDefault(s1, 0)) {count--;}tmp = map1.get(s1) - 1;map1.put(s1, tmp);left = left + len;}}sum++;}return list;}
}
3 串联所有单词的子串
分析:
这道题仍然是不管先后顺序,只要包含t中的字串就行。因此我们判断字串是否包含t的全部元素并相同的时候我们仍然可以使用count去标记。
首先看到这样的题目描述以及求字串那么我们想到的解法一定是滑动窗口;那么我们就要想一下进窗口,出窗口,和更新结果的时机以及条件。
我们先是定义两个指针,left和right。right向后依次遍历。每次遍历过的字符都放入集合里。并且判段该元素是否在t中存在,并且数量小于t中对应字符的数量,如果是那么count++; 接下来我们需要判断,当前的right和left之间的字符串是否满足条件,即count是否等于t.length。相等话如果长度小于之前满足要求的字符串则更新结果。之后再对left进行++。
以上就是大体思路
优化点:
我们可以将map或作数组,方法和题一一样,将string转为字符,然后-'a'存入数组。这样时间可以减短很多
代码展示:
public static String minWindow(String s, String t) {Map<String ,Integer> map1 = new HashMap<>();Map<String ,Integer> map2 = new HashMap<>();String result = "";for (int i = 0; i < t.length(); i++) {map2.put(t.substring(i,i+1),map2.getOrDefault(t.substring(i,i+1),0) + 1);}int right = 0;int left = 0;int count = 0;int min = s.length();while(right < s.length()) {String tmp = s.substring(right,right + 1);map1.put(tmp,map1.getOrDefault(tmp,0) + 1);if(map1.get(tmp) <= map2.getOrDefault(tmp , 0)) {count++;}right++;if(map1.containsKey(tmp)) {while(count == t.length()) {if(min >= (right - left)) {min = right - left;result = s.substring(left , right);}tmp = s.substring(left,left + 1);if(map1.get(tmp) <= map2.getOrDefault(tmp , 0)) {count--;}map1.put(tmp,map1.getOrDefault(tmp,0) - 1);left++;}}}return result;}
4 水果成篮
分析:
遇到这种连续数组求字串或者长度之类的用滑动窗口。这道题的思考点在于如何直到是否有两种以上的不同数字,我们可以用map,如果map.size()没有超过2,那么继续往里面加,如果超过2那么就左移left。然后每次移动的时候更新最大值即可。
代码展示:
class Solution {public int totalFruit(int[] fruits) {Map<Integer , Integer> map = new HashMap<>();int right = 0;int left = 0;int max = 0;while(right < fruits.length) {int tmp = map.getOrDefault(fruits[right] , 0) + 1;map.put(fruits[right] , tmp);right++;while (map.size() > 2) {tmp = map.getOrDefault(fruits[left] , 0) - 1;map.put(fruits[left] , tmp);if (map.get(fruits[left]) == 0) {map.remove(fruits[left]);}left++;}if(right - left> max) {max = right - left ;}}return max;}
}
下一章节我们开始讲二分法。