无重复字符的最长子串

我们先说思路
- 因为考虑是要最长子串,没有重复字符,所以利用
Set存储已经遍历过的字符。 - 当遇到一个我们已经存储过的字符时,比如
a,那我们就需要从最前面把遍历过的字符直到我们之前读过a去掉,来保证这次新的子串没有重复字符 - 当遇到一个我们已经存储过的字符时,也就是我们统计子串长度的时候,不过还需要注意,如果只有一个字符或者是遍历到数组末尾而没有出现字符重复时,是不会统计长度的,所以还需要最后再加上一次统计。
- 那就还是利用双指针,
i, j,负责表示当前子串的边界
java
public int lengthOfLongestSubstring(String s) {
Set<Character> charSet = new HashSet<>();
char[] charArr = s.toCharArray();
int length = s.length();
int i = 0, j = 0, ans = 0;
for (; i < length; i++) {
if (!charSet.contains(charArr[i])) {
charSet.add(charArr[i]);
continue;
}
ans = Math.max(ans, i - j); // 子串是 [j, i)
for (; j < i; j++) {
if (charArr[j] == charArr[i]){
j++;
break;
}
charSet.remove(charArr[j]);
}
}
ans = Math.max(ans, i - j);
return ans;
}
当然,我们还可以做一个小小的优化,比如,当我们遍历时出现重复字符 a 时我们是从前面一个一个剔除找到我们之前遍历过的 a,那我们能不能直接就找到呢?
将 Set 换为 Map,由 Map 记录字符下标。但是还是有一个问题,比如 [a, b, b, a],当遍历到第一个 a 时加入了 Map,第一个 b 加入了 Map,第二个 b 时,是从 Map 中找到了第一个 b,并让 j = map.get('b') + 1,当前我们的子串是不包含 a 的,但是 Map 中是有的,之后我们遍历到第二个 a 时,啪唧,发现 Map 中有!我们能继续 j = map.get('a') + 1 吗,不,不可以,我们应该保持 j 不动,然后更新 Map 中到 a。
java
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> map = new HashMap<>();
int j = 0, ans = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (!map.containsKey(c)) {
map.put(c, i); // 不包含则加入
ans = Math.max(ans, i - j + 1); // 更新答案
continue;
}
j = Math.max(j, map.get(c) + 1); // 包含了,变动 j
map.put(c, i); // 更新下标
ans = Math.max(ans, i - j + 1);
}
return ans;
}
但是这样有点重复,put 和 ans 更新重复,所以改成下面这种。
java
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> map = new HashMap<>();
int j = 0, ans = 0;
for (int i = 0; i < s.length(); i++) {
if (map.containsKey(s.charAt(i))) {
j = Math.max(j, map.get(s.charAt(i)) + 1);
}
map.put(s.charAt(i), i); // 更新或者插入 charAt(i) 的 map
ans = Math.max(ans, i - j + 1); // 每一轮计算长度
}
return ans;
}
找到字符串中所有字母异位词

s和p仅包含小写字母,那我们可以使用一个大小为 26 的数组统计字符数- 子串长度固定,为
p.length()
这个题目最简单的思路是,两个数组统计 p 的字符数,以及子串的字符数,然后比较,如果相等就是异位词,起始下标是答案的一种
java
public List<Integer> findAnagrams(String s, String p) {
if (s.length() < p.length()) return new ArrayList<>();
int sLen = s.length(), pLen = p.length();
int[] sCount = new int[26], pCount = new int[26];
List<Integer> ans = new ArrayList<>();
for (int i = 0; i < pLen; i++) {
++sCount[s.charAt(i) - 'a'];
++pCount[p.charAt(i) - 'a'];
}
if (Arrays.equals(sCount, pCount)) ans.add(0);
for (int i = 0; i < sLen - pLen; ++i) {
// 整体后移一位
--sCount[s.charAt(i) - 'a'];
++sCount[s.charAt(pLen + i) - 'a'];
if (Arrays.equals(sCount, pCount)) ans.add(i + 1);
}
return ans;
}
很明显,有可以优化的点,
- 我们不需要知道子串或者
p有某个字符有多少个,我们只关注他们的差距 - 不需要每次都调用
Arrays.equals进行比较,我们可以通过一个变量,计数differ记录有多少字符不满足个数一致,当它为0时就是一个答案。
java
public List<Integer> findAnagrams(String s, String p) {
if (s.length() < p.length()) return new ArrayList<>();
int sLen = s.length(), pLen = p.length();
int[] count = new int[26];
int differ = 0;
List<Integer> ans = new ArrayList<>();
for (int i = 0; i < pLen; i++) {
++count[s.charAt(i) - 'a'];
--count[p.charAt(i) - 'a']; // 位置信息表示子串多了几个,需要减去的个数
}
for (int i = 0; i < 26; i++) {
if (count[i] != 0) differ += Math.abs(count[i]);
}
if (differ == 0) ans.add(0);
for (int i = 0; i < sLen - pLen; ++i) {
if (count[s.charAt(i) - 'a'] >= 1) differ--;
else if (count[s.charAt(i) - 'a'] < 1) differ++;
--count[s.charAt(i) - 'a'];
if (count[s.charAt(pLen + i) - 'a'] <= -1) differ--;
else if (count[s.charAt(pLen + i) - 'a'] > -1) differ++;
++count[s.charAt(pLen + i) - 'a'];
if (differ == 0) ans.add(i + 1);
}
return ans;
}
区域和检索-数组不可变

典型前缀和
java
class NumArray {
private final int[] sums;
public NumArray(int[] nums) {
sums = new int[nums.length];
if(nums.length == 0) return;
sums[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
sums[i] = sums[i - 1] + nums[i];
}
}
public int sumRange(int left, int right) {
if (left <= 0) return sums[right];
return sums[right] - sums[left - 1];
}
}
这里我们是用 sums[i] 表示数组 [0,i] 的和,但是初始化的时候我们需要额外考虑 sums[0],因为计算前缀和的时候是从下标 1 开始,需要额外初始化一下,另外计算 sumRange 的时候,我们计算 [left, right] 是使用 sums[right] - sums[left - 1],即 [0, right] - [0, left - 1] = [left, right],但是我们需要额外考虑 left = 0 的时候
所以这里还可以小优化一下,多定义一个 sums[0] = 0,然后用 sums[i + 1] 表示 [0, i] 下标值的和,
java
class NumArray {
private final int[] sums;
public NumArray(int[] nums) {
sums = new int[nums.length + 1];
for (int i = 0; i < nums.length; i++) {
sums[i + 1] = sums[i] + nums[i];
}
}
public int sumRange(int left, int right) {
return sums[right + 1] - sums[left];
}
}