Leetcode - Longest Substring with At Most Two Distinct Characters

Given a string, find the length of the longest substring T that contains at most 2 distinct characters.

For example, Given s = “eceba”,

T is "ece" which its length is 3.
[分析] 滑动窗口法,窗口内始终仅保留至多两个相异字符组成的字符串。实现时有两个问题需要考虑:1)何时更新窗口的左边界 2)如何更新窗口的左边界。问题1简单,当前字符不在窗口内且窗口内已包含两种字符。问题2最关键,想清楚了该问题也就解决了。当前窗口的左边界字符必然是要被删除的,但需要全部清除窗口内所有该字符吗?不见得,考虑这个例子abaccc, 遍历到第一个c时若将a全部清除则求得的长度是3,但实际上应该是4。正确的做法是找到窗口内最后位置最靠左的字符,设其最末位置为mostLeftEndPos, 窗口的新左边界则为mostLeftEndPos + 1, 这种做法的正确性在于保证更新时清除的字符串最短。
参考博客中还给出了扩展题,至多保留K个相异字符。更多请阅读原文:
[url]http://blog.csdn.net/whuwangyi/article/details/42451289[/url]

>>>第二次做
思路1:基本思想仍然是两指针式滑动窗口,实现上稍有差异。维护一个哈希表idxMap, key是字符,value是key最后出现的下标。activeTwo数组中两元素表示当前窗口中的两个相异字符。遍历字符串时若遇到新字符,意外着activeTwo中有个字符要被淘汰出窗口了,该淘汰哪个呢?易知当前位置的前一个字符应该是幸运儿,那么自然的另一个就是要被淘汰的。更新窗口前计算当前窗口长度并更新maxLen,然后更新窗口左指针为被淘汰字符最后出现下标的下一个位置。
思路2: 参考[url]https://leetcode.com/discuss/21929/share-my-c-solution[/url]
代码清晰简洁,同longestK的差异在于使用数组代替HashMap。
[color=blue]注意当哈希表key是字符时使用数组代替HashMap,运行效率会稍高些。[/color]


// 第二次做的两种实现方式
// Method 2: map中value是count时, 这种从窗口起始位置开始遍历寻找第一个被淘汰的字符方式扩展性好些,可扩展到保留k个不同的情况
public int lengthOfLongestSubstringTwoDistinct2(String s) {
if (s == null) return 0;
int maxDiffChars = 2;
int maxLen = 0;
int[] map = new int[256]; // key: char, value: occurs of key
int i = 0, j = 0; // i & j is the start and end of the sliding window
int count = 0; // number of diff char in the sliding window
while (j < s.length()) {
char curr = s.charAt(j);
map[curr]++;
if (map[curr] == 1) { // a new char
count++;
while (count > maxDiffChars) {
map[s.charAt(i)]--;
if (map[s.charAt(i)] == 0)
count--;
i++;
}
}
maxLen = Math.max(maxLen, j - i + 1);
j++;
}
return maxLen;
}
// Method 1: map中value是key最后一次出现的下标,下面的实现方式不能方便地扩展到窗口可保留最多k个字符的情况
public int lengthOfLongestSubstringTwoDistinct1(String s) {
if (s == null) return 0;
if (s.length() < 3) return s.length();
int[] idxMap = new int[256];
for (int i = 0; i < 256; i++)
idxMap[i] = -1;
char[] activeTwo = new char[2];
activeTwo[0] = s.charAt(0);
int i = 0, j = 1;
while (j < s.length() && s.charAt(j) == activeTwo[0])
j++;
if (j == s.length()) return s.length();
activeTwo[1] = s.charAt(j);
idxMap[activeTwo[0]] = j - 1;
idxMap[activeTwo[1]] = j;
j++;
char survivor;
int len = 1, maxLen = j;
while (j < s.length()) {
char curr = s.charAt(j);
if (curr == activeTwo[0]) {
idxMap[activeTwo[0]] = j;
} else if (curr == activeTwo[1]) {
idxMap[activeTwo[1]] = j;
} else {
survivor = s.charAt(j - 1);
len = j - i;
maxLen = Math.max(len, maxLen);
if (activeTwo[0] == survivor) {
i = idxMap[activeTwo[1]] + 1;
activeTwo[1] = curr;
idxMap[activeTwo[1]] = j;
} else {
i = idxMap[activeTwo[0]] + 1;
activeTwo[0] = curr;
idxMap[activeTwo[0]] = j;
}
}
j++;
}
maxLen = Math.max(maxLen, j - i);
return maxLen;
}



public class LongestSubstringWithAtMost2DistinctChars {
@Test
public void testSolution() {
Solution obj = new Solution();
Assert.assertEquals(4, obj.longest2("abaa"));
Assert.assertEquals(4, obj.longest2("abaccc"));
Assert.assertEquals(4, obj.longestK("abaa", 2));
Assert.assertEquals(4, obj.longestK("abaccc", 2));
Assert.assertEquals(6, obj.longestK("abacccd", 3));
}
}

class Solution {
public int longest2(String s) {
if (s == null || s.length() == 0)
return 0;
int start = 0;
int N = s.length();
HashMap<Character, Integer> map = new HashMap<Character, Integer>(2);
map.put(s.charAt(0), 0);
int max = 1;
for (int i = 1; i < N; i++) {
char c = s.charAt(i);
if (!map.containsKey(c) && map.size() == 2) {
// update left bound of sliding window
int mostLeftEndPos = i - 1;
char toBeRemoved = ' ';
for (Character key : map.keySet()) {
if (map.get(key) < mostLeftEndPos) {
mostLeftEndPos = map.get(key);
toBeRemoved = key;
}
}
start = mostLeftEndPos + 1;
map.remove(toBeRemoved);
}
map.put(c, i);
max = Math.max(max, i - start + 1);
}
return max;
}
//k 比较大时遍历map就比较低效,不如从start开始寻找第一个最后位置跑出窗口的字符
public int longestK(String s, int k) {
if (s == null || s.length() == 0)
return 0;
int N = s.length();
int start = 0;
int max = 0;

// c: letter; value: the number of occurrences.
HashMap<Character, Integer> map = new HashMap<Character, Integer>();
for (int i = 0; i < N; i++) {
char c = s.charAt(i);
if (map.containsKey(c)) {
map.put(c, map.get(c) + 1);
} else {
map.put(c, 1);
while (map.size() > k) {
char startChar = s.charAt(start++);
int count = map.get(startChar);
if (count > 1) {
map.put(c, count - 1);
} else {
map.remove(startChar);
}
}
}
max = Math.max(max, i - start + 1);
}
return max;
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值