Substring with Concatenation of All Words, Longest Substring Without Repeating Characters, Minimum Window Substring, Longest Palindromic Substring

Substring with Concatenation of All Words

You are given a string, S, and a list of words, L, that are all of the same length. Find all starting indices of substring(s) in S that is a concatenation of each word in L exactly once and without any intervening characters.
For example, given:
S: “barfoothefoobarman”
L: [“foo”, “bar”]
You should return the indices: [0,9].
(order does not matter).

Brute Force Solution

Two Hashmaps

  • rest: store all words in L, including times of appreance
  • found: renew at each round, store matched words, including times of appreance
  • Two pointers

  • i: starting index of current substring
  • j: numebers of words matched to current substring
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public ArrayList<Integer> findSubstring(String S, String[] L) {
    ArrayList<Integer> res=new ArrayList<Integer>();
    HashMap<String, Integer> rest=new HashMap<String, Integer>();
    HashMap<String, Integer> found=new HashMap<String, Integer>();
    if(S==null||L.length==0) return res;
    int wordlen=L[0].length(), Llen=L.length, Slen=S.length();
    for(int i = 0; i<L.length;i++){
    int num=1;
    if(rest.get(L[i])!=null)
    num+=rest.get(L[i]);
    rest.put(L[i], num);
    }
    for(int i=0; i<Slen-Llen*wordlen+1;i++){
    int j=0;
    found.clear();
    for(;j<Llen;j++){
    int start=i+j*wordlen;
    int end=start+wordlen;
    int num=1;
    String tmp=S.substring(start, end);
    if(!rest.containsKey(tmp)) break;
    if(found.get(tmp)!=null) num+=found.get(tmp);
    if(num>rest.get(tmp)) break;
    found.put(tmp, num);
    }
    if(j==Llen) res.add(i);
    }
    return res;
    }

    Minimun Window Solution

  • outer loop: i increment wordlen times, each time scan through S
  • inner loop: j increment wordlen per time, until first letter of last word
  • each outerloop: reset count=0, reset index=i
  • each innerloop: curdict to store current window
  • If tmp str not found in dict, increment index, reset count and curdict
    If found, add to curdict, count++
    before count++: compare num, if exceeded, increment index ,then decrement count and curdict
    If count equals listlen add index into result, increment index, update curdict

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    public static ArrayList<Integer> findSubstring(String S, String[] L) {
    ArrayList<Integer> res = new ArrayList<Integer>();
    if(S==null||L==null||S.length()==0||L.length==0)
    return res;
    int wordLen = L[0].length();//same length for each word in dictionary
    //put given dictionary into hashmap with each word's count
    HashMap<String, Integer> dict = new HashMap<String, Integer>();
    for(String word: L){
    if(!dict.containsKey(word))
    dict.put(word, 1);
    else
    dict.put(word, dict.get(word) + 1);
    }
    for(int i = 0; i < wordLen; i++){
    int count = 0;
    int index = i;//index of each startpoint
    HashMap<String, Integer> curdict = new HashMap<String, Integer>();
    //till the first letter of last word
    for(int j = i; j <= S.length() - wordLen; j += wordLen){
    String curWord = S.substring(j, j + wordLen);
    //check each word to tell if it existes in give dictionary
    if(!dict.containsKey(curWord)){
    curdict.clear();
    count = 0;
    index = j + wordLen;
    }else{
    //form current dictionary
    if(!curdict.containsKey(curWord))
    curdict.put(curWord, 1);
    else
    curdict.put(curWord, curdict.get(curWord) + 1);
    //count for current found word and check if it exceed given word count
    if(curdict.get(curWord) <= dict.get(curWord)){
    count++;
    }else{
    while(curdict.get(curWord) > dict.get(curWord)){
    String temp = S.substring(index, index + wordLen);
    curdict.put(temp, curdict.get(temp)-1);
    index = index + wordLen;//make index move next
    if(curdict.get(temp)<dict.get(temp))
    count--;
    }
    }
    //put into res and move index point to nextword
    //and update current dictionary as well as count num
    if(count == L.length){
    res.add(index);
    String temp = S.substring(index, index + wordLen);
    curdict.put(temp, curdict.get(temp)-1);
    index = index + wordLen;
    count--;
    }
    }
    }//end for j
    }//end for i
    return res;
    }

    Longest Substring Without Repeating Characters

    Given a string, find the length of the longest substring without repeating characters. For example, the longest substring without repeating letters for “abcabcbb” is “abc”, which the length is 3. For “bbbbb” the longest substring is “b”, with the length of 1.

    Brute force method

    Find all substrings and check if each of it contains only unique characters. As for the maxlength, we use Greedy Algorithm to only keep the longest substring seen so far. There are one inner loop and each check takes linear time, O(N^2) time won’t pass the OJ.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public int lengthOfLongestSubstring(String s) {
    if(s==null||s.length()==0) return 0;
    int maxlen=1;
    Hashtable <Character, Boolean> map=new Hashtable<Character, Boolean>();
    for(int i=0;i< s.length();i++){
    for(int j=i;j<s.length();j++){
    if(map.containsKey(s.charAt(j))){map.clear(); break;}
    else{map.put(s.charAt(j), false);}
    int len=map.size();
    if(len>maxlen){maxlen=len;}
    }
    }
    return maxlen;
    }

    Better Solution
    Use a Hashmap/Hashset recording current substring and two pointers to record current positions.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public int lengthOfLongestSubstring(String s) {
    if(s==null||s.length()==0) return 0;
    int maxlen=1,l=0,r=0;
    Hashtable <Character, Boolean> map=new Hashtable<Character, Boolean>();
    while(r<s.length()){
    char tmp=s.charAt(r);
    if(!map.containsKey(tmp)||map.get(tmp)==false){
    map.put(tmp, true);
    maxlen=Math.max((r-l+1), maxlen);
    }
    else{
    while(s.charAt(l)!=s.charAt(r)){map.put(s.charAt(l), false);l++;}
    l++;
    }
    r++;
    }
    return maxlen;
    }

    Minimum Window Substring

    Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).
    For example,
    S = “ADOBECODEBANC”
    T = “ABC”
    Minimum window is “BANC”.
    Note:
    If there is no such window in S that covers all characters in T, return the emtpy string “”.
    If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S.

    Min Sliding Window Solution

  • Use a Hashmap to record each character, numbers in T
  • Two pointers:

  • left: increment when current substring contains all chars in T, pass unrelevant chars and exceeded chars
    right: increment each loop, if found a match in T decrement its Hashmap value

  • Minlen: when we found a new substring update minlength
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public String minWindow(String S, String T) {
    HashMap<Character,Integer> dict=new HashMap<Character,Integer>();
    int tlen=T.length(), slen=S.length(),minlen=slen+1;
    for (int i=0;i<tlen;i++){
    int num=1;
    if(dict.containsKey(T.charAt(i))) num+=dict.get(T.charAt(i));
    dict.put(T.charAt(i),num);
    }
    int count=0,left=0,start=0;
    for(int i=0;i<slen;i++){
    if(dict.containsKey(S.charAt(i))){
    dict.put(S.charAt(i),dict.get(S.charAt(i))-1);
    if(dict.get(S.charAt(i))>=0) count++;
    if(count==tlen){
    while(count==tlen){
    if(i-left+1<minlen){minlen=i-left+1; start=left; }
    if(dict.containsKey(S.charAt(left))) {
    dict.put(S.charAt(left),dict.get(S.charAt(left))+1);
    if(dict.get(S.charAt(left))>0) count--;
    }
    left++;
    }
    }
    }
    }
    return minlen>slen? "":S.substring(start,start+minlen);
    }

    Another version using two hashmaps:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    public String minWindow(String S, String T) {
    HashMap<Character, Integer> dict = new HashMap<>();
    for (int i = 0; i < T.length(); i++) {
    char c = T.charAt(i);
    if (!dict.containsKey(c))
    dict.put(c, 1);
    else
    dict.put(c, dict.get(c) + 1);
    }
    HashMap<Character, Integer> found = new HashMap<>();
    int foundCounter = 0;
    int start = 0;
    int end = 0;
    int min = Integer.MAX_VALUE;
    String minWindow = "";
    while (end < S.length()) {
    char c = S.charAt(end);
    if (dict.containsKey(c)) {
    if (found.containsKey(c)) {
    if (found.get(c) < dict.get(c))
    foundCounter++;
    found.put(c, found.get(c) + 1);
    } else {
    found.put(c, 1);
    foundCounter++;
    }
    }
    if (foundCounter == T.length()) {
    //When foundCounter equals to T.length(), in other words, found all characters.
    char sc = S.charAt(start);
    while (!found.containsKey(sc) || found.get(sc) > dict.get(sc)) {
    if (found.containsKey(sc) && found.get(sc) > dict.get(sc))
    found.put(sc, found.get(sc) - 1);
    start++;
    sc = S.charAt(start);
    }
    if (end - start + 1 < min) {
    minWindow = S.substring(start, end + 1);
    min = end - start + 1;
    }
    }
    end++;
    }
    return minWindow;
    }

    Array Solution

    To count occurrence of characters, instead of using a hash map, can use an int array of 256
    Two pointer method with one point to end and one point to start.
    Pointer advance condition: move end forward till finds one window, then move start forward till the window no longer valid

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public String minWindow(String S, String T) {
    int[] map=new int[256],found=new int[256];
    int start=0, count=0,end=0,minlen=S.length()+1,tlen=T.length(),slen=S.length();
    String min="";
    for(int i = 0; i < tlen; i++){
    map[T.charAt(i)]++;
    }
    for(;end<slen;end++){
    if(map[S.charAt(end)]>found[S.charAt(end)]) {count++;}
    found[S.charAt(end)]++;
    if(count==T.length()){
    while(found[S.charAt(start)]>map[S.charAt(start)])
    { found[S.charAt(start)]--;
    start++;
    }
    if(end-start+1<minlen) {minlen=end-start+1; min = S.substring(start, end+1);
    }
    found[S.charAt(start)]--;
    start++;
    count--;
    }
    }
    return min;
    }

    Longest Palindromic Substring

    Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.

    Brute Force Solution

    Find all possible substrings and check if each is palindrome. O(N^3) too LTE…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public String longestPalindrome(String s) {
    int maxlen=0;
    String maxstr=null;
    for(int i=0;i<s.length();i++){
    for(int j=i+1;j<s.length();j++){
    String tmp=s.substring(i,j+1);
    if(IsPalindrome(tmp)&&tmp.length()>maxlen)
    {
    maxlen=tmp.length();
    maxstr=tmp;}
    }
    }return maxstr;
    }
    public Boolean IsPalindrome(String s) {
    for(int i=0;i<s.length();i++){
    if(s.charAt(i)!=s.charAt(s.length()-1-i)) return false;
    } return true;
    }

    DP Solution

    Time O(n^2) Space O(n^2) but didn’t pass OJ. Why?
    Three rounds of loops:

  • set diagonal to 1: t[i][i]=0

  • set top-right second diagonal: t[i][i+1]=1 if two consecutive character

  • set whole table [diagonaly]

  • a. start from substring length 3 to full length

  • b. inner loop: from 0 to length-len, check substring(i, i+len), set t[i][i+len-1] according to changing rule:

  • c. If s.charAt(i) == s.charAt(j) then t[i][j] = t[i + 1][j - 1]
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public String longestPalindrome2(String s) {
    int maxlen=0;
    String maxstr=null;
    int[][] t=new int[s.length()][s.length()];
    for(int i=0;i<s.length();i++){t[i][i]=1;}
    for(int i=0;i<s.length()-1;i++){
    if(s.charAt(i)==s.charAt(i+1))
    {t[i][i+1]=1;
    maxlen=2;maxstr=s.substring(i,i+2);}
    else t[i][i+1]=0;
    }
    printTable(t);
    for (int len = 3; len <= s.length(); len++) {
    for (int i = 0; i <= s.length()-len; i++) {
    int j = i + len - 1;
    if (s.charAt(i) == s.charAt(j)) {
    t[i][j] = t[i + 1][j - 1];
    if (t[i][j] == 1 && len > maxlen)
    maxstr = s.substring(i, j + 1);
    }
    else {t[i][j]=0;}
    printTable(t);
    }
    }
    return maxstr;
    }
    public static void printTable(int[][] x){
    for(int [] y : x){
    for(int z: y){
    System.out.print(z + " ");
    }
    System.out.println();
    }
    System.out.println("------");
    }

    Another Algorithm

    i from 0 to length-1, get palindrom substring with center of i or i, i+1, keep updating the longest substring: Time O(n^2), space O(1)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public String longestPalindrome(String s) {
    String maxstr = "";
    for (int i = 0; i < s.length(); i++) {
    // get longest palindrome with center of i eg: BAB
    String tmp = helper(s, i, i);
    if (tmp.length() > maxstr.length()) {
    maxstr = tmp;
    }
    // get longest palindrome with center of i, i+1 eg: ABBA
    tmp = helper(s, i, i + 1);
    if (tmp.length() > maxstr.length()) {
    maxstr = tmp;
    }
    }
    return maxstr;
    }
    public static String helper(String s, int begin, int end) {
    while (begin >= 0 && end <= s.length() - 1 && s.charAt(begin) == s.charAt(end)) {
    begin--;
    end++;
    }
    return s.substring(begin + 1, end);
    }

    Manacher’s algorithm
    Computes the longest palindromic substring in linear time.