import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
/**
* Given a string you need to print longest possible substring that has at most M unique characters.
* <p>
* If there are more than one substring of longest possible length, then print any one of them.
* <p>
* Example:
* input: abcsxfaaxfe, 5
* output: sxfaaxfe
* <p>
* Similar:
* http://www.geeksforgeeks.org/find-the-longest-substring-with-k-unique-characters-in-a-given-string/
* <p>
* Created by kiyan on 6/5/16.
*/
public class LongestContiguousSubstring {
private LongestContiguousSubstring lcs;
/**
* O(n) Time, O(n) Space
* Maintain a window and add elements to the window till it contains less or equal k
* Update our result if required while doing so.
* If unique elements exceeds than required in window, start removing the elements from left side.
* We can reduce the space to O(1) if change the map to an array of all characters
*/
public String findLongestContiguous(String s, int M) {
if (s == null || s.length() <= M) return s;
Map<Character, Integer> map = new HashMap<>();
String res = "";
int start = 0;
int end = 0;
while (start <= end && end < s.length()) {
char c = s.charAt(end);
// Update map
map.put(c, map.containsKey(c) ? map.get(c) + 1 : 1);
// Move start pointer
while (map.size() > M) {
char toRemove = s.charAt(start);
if (map.containsKey(toRemove)) map.put(toRemove, map.get(toRemove) - 1);
if (!map.containsKey(toRemove) || map.get(toRemove) <= 0) map.remove(toRemove);
start++;
}
// Update result
String temp = s.substring(start, end + 1);
if (res.length() < temp.length()) {
res = temp;
}
end++;
}
return res;
}
@Before
public void setUp() {
lcs = new LongestContiguousSubstring();
}
@Test
public void testExample() {
// "aabbcc", k = 1
String input = "aabbcc";
int M = 1;
Assert.assertEquals("aa", lcs.findLongestContiguous(input, M));
// "aabbcc", k = 2
M = 2;
Assert.assertEquals("aabb", lcs.findLongestContiguous(input, M));
// "aabbcc", k = 3
M = 3;
Assert.assertEquals("aabbcc", lcs.findLongestContiguous(input, M));
// "aaabbb", k = 3
input = "aaabbb";
Assert.assertEquals("aaabbb", lcs.findLongestContiguous(input, M));
//
input = "abcsxfaaxfe";
M = 5;
Assert.assertEquals("csxfaaxf", lcs.findLongestContiguous(input, M));
}
@After
public void tearDown() {
lcs = null;
}
}