-2

I am solving a problem on leetcode.Here is the question link
https://leetcode.com/problems/longest-substring-without-repeating-characters/

Below is my solution which is not passing some test-cases:

  1. abcabcbb -- not correct
  2. pwwkew -- correct
  3. bbbbb -- not correct

Any help would be thankful:)
And also I am a newbie here so you can suggest me about my problem statement.

class Solution {
    public int lengthOfLongestSubstring(String s) {

        int i,max=0;
        List<Character> list = new ArrayList<>();
        String x = "";
        for(i=0;i<s.length();i++)
        {
            if(!list.contains(s.charAt(i)))
            {
                x += s.charAt(i);
                list.add(s.charAt(i));
                 System.out.println(x);
            }
            else
            {
                if(x != null && x.length() > max)
                {
                    max = x.length();     
                    System.out.println(max);
                    x = "";
                    list.clear();
                    System.out.println("x : "+ x);
                    System.out.println("list : "+ list);
                } 
                // else
                // {
                //     list.add(s.charAt(i));
                //     x += s.charAt(i);    
                //     System.out.println("x in else : "+ x);
                //     System.out.println("list in else : "+ list);
                // }
                list.add(s.charAt(i));
                x += s.charAt(i); 
                System.out.println("x in else : "+ x);
                System.out.println("list in else : "+ list);
            }
        }
        return max;
    }
}
moon
  • 21
  • 2
  • 3
  • 1
    I believe all the down-votes are because StackOverflow is not a free debugging service. You're supposed to debug your own code yourself, and we see no evidence of that. Did you debug at all? [What is a debugger and how can it help me diagnose problems?](https://stackoverflow.com/q/25385173/5221149) – Andreas Jan 25 '20 at 18:21
  • You may want to tell us the correct/expected result for each of your test cases and the observed output from your program too. That would give us something to work with. – Ole V.V. Jan 25 '20 at 18:27
  • 1
    You need to **re-think your logic**, since you can't just clear you're tracking data when you encounter a repeating character. E.g. input `"aBcdBefgh"` would cause you to clear when seeing second `B`, leaving you with `"Befgh"` as longest substring, when longest substring is really `"cdBefgh"`. --- When you see a repeating character, you reset (not clear) your tracking of longest substring to start after the earlier occurrence of the repeating character. E.g. `"aXbXcdXefgXh"` needs to find `"cdXefg"` as the longest substring. – Andreas Jan 25 '20 at 18:49

2 Answers2

1

Sometimes it's helpful to remain in the problem domain as much as possible. This approach creates a solution before any thought of coding. This approach leaves us with a set of minimally complex logical operations which then require implementation.

First our initial condition. Columns should be clear: Input (always same), Current (the current substring without repeating characters), Answer (the current answer in String form) and Logic (what logic is applied for this step:

enter image description here

So first iteration starts the same as rest : get next character in Input. Check if it is in the Current substring and since it is not add to Current. Here we also ask the question: Is Answer shorter than Current and if so set Answer to Current.

Note in the Logic column we are developing operations which we'll need to implement in the solution.

enter image description here

Repeat for second character input (no new operations):

enter image description here

And again for third - (no new operations):

enter image description here

Ok now we find next CH in the current substring so we need a new operation: 'Remove chars in current up to but not including CH. Note that "add CH to current" is done in this case as well. Note also some new logic (answer was as long or longer than current so "Do Nothing").

enter image description here

And finish things out - no new operations.

enter image description here

So now we reach the end of input and simply ask the question "How long is the Answer" and that is the result.

enter image description here

So now looking at the Logic column we see operations to perform:

// Initial condition
String answer = "";
String current = "";

Let's work completely in Strings to keep things simple - optimization can come later..

Let's define the "next CH (nextChar)" operation:

// Get the ith character (0-based) from 's' as a String.
private static String nextChar(String s, int i) {}

We'll need an operation which "checks if 'Current contains CH'":

// Does the 'current' String contain the 'nextChar' String?
private static boolean currentContainsCh(String current, String nextChar) {}

We'll need to check if current Answer is shorter than Current:

// True if the 'answer' String is short in length than the 'current' string.
private static boolean isAnswerShorterThanCurrent(String current, String answer) {}

And the ability to append the nextChar to Current:

// Append the 'nextChar' to the 'current' String and return the String.
private static String addChToCurrent(String current, String nextChar) {}

And finally the ability to remove all characters up to but not including current char in Current:

// @return a String which has all characters removed from 'current' up to but not including 'ch'
private static String removeUpToChar(String current, String ch) {}

So putting it all together (essentially still in the problem domain since we haven't implemented any operations but just projected the problem into structure):

public int lengthOfLongestSubstring(String s) {


    String answer = "";
    String current = "";

    for (int i = 0; i < s.length(); i++) {
      String nextChar = nextChar(s,i);

      if (currentContainsCh(current, nextChar)) {
        current = removeUpToChar(current, nextChar);
      }

      current = addChToCurrent(current,nextChar);

      if (isAnswerShorterThanCurrent(current,answer)) {
          answer = new String(current);
      }

    }
    return answer.length();
}

And now implementing the "operations" becomes easier (and fun) since they are isolated and not complex. This is left for you. When implemented it passes the problem.

The next logical step after verifying correctness is to consider optimizations - if needed.

1

Although the pictures in answer by Andy are useful and mostly correct, the code is sub-optimal.

The code in the question, as well as in both current answers, builds a lot of substrings, using string concatenation. That is detrimental to performance.

Here is an O(n) solution that doesn't build any substrings:

public static int lengthOfLongestSubstring(String s) {
    Map<Character, Integer> lastPos = new HashMap<>();
    int start = 0, maxLen = 0, i = 0;
    for (; i < s.length(); i++) {
        Integer pos = lastPos.put(s.charAt(i), i);
        if (pos != null && pos >= start) {
            if (i > start + maxLen)
                maxLen = i - start;
            start = pos + 1;
        }
    }
    return (i > start + maxLen ? i - start : maxLen);
}

This works by remembering the last position of each character, so the potential longest substring's starting position can be adjusted to start right after the previous position of a repeating character.

Since HashMap.put(...) is O(1) (amortized), the solution is O(n).


Since it would be nice to see the substring, we can easily modify the code to return the (first1) longest substring:

public static String longestSubstring(String s) {
    Map<Character, Integer> lastPos = new HashMap<>();
    int start = 0, maxStart = 0, maxLen = 0, i = 0;
    for (; i < s.length(); i++) {
        Integer pos = lastPos.put(s.charAt(i), i);
        if (pos != null && pos >= start) {
            if (i > start + maxLen) {
                maxStart = start;
                maxLen = i - start;
            }
            start = pos + 1;
        }
    }
    return (i > start + maxLen ? s.substring(start) : s.substring(maxStart, maxStart + maxLen));
}

1) To return the last of multiple longest substrings, change both i > to i >=


The above solutions cannot handle strings with Unicode characters from the supplemental planes, e.g. emoji characters.

Emoji's such as and are stored as "\uD83D\uDE00" and "\uD83D\uDE01", so the char value '\uD83D' will be seen as a repeating character.

To make it correctly handle all Unicode characters, we need to change it to:

public static String longestSubstring(String s) {
    Map<Integer, Integer> lastPos = new HashMap<>();
    int start = 0, maxStart = 0, maxLen = 0, i = 0;
    for (int cp; i < s.length(); i += Character.charCount(cp)) {
        cp = s.codePointAt(i);
        Integer pos = lastPos.put(cp, i);
        if (pos != null && pos >= start) {
            if (i > start + maxLen) {
                maxStart = start;
                maxLen = i - start;
            }
            start = pos + Character.charCount(cp);
        }
    }
    return (i > start + maxLen ? s.substring(start) : s.substring(maxStart, maxStart + maxLen));
}

Test

for (String s : new String[] { "abcabcbb", "pwwkew", "bbbbb", "aab", "abba", "xabycdxefghy", "aXbXcdXefgXh", "" }) {
    String substr = longestSubstring(s);
    System.out.printf("%s: %s (%d)%n", s, substr, substr.length());
}

Output

abcabcbb: abc (3)
pwwkew: wke (3)
bbbbb: b (1)
aab: ab (2)
abba: ab (2)
xabycdxefghy: abycdxefgh (10)
aXbXcdXefgXh: cdXefg (6)
:  (6)
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • @OleV.V. Yeah, I wasn't going to give code-answer, but then 2 other answers appeared, with _O(n²)_ and _O(n³)_ time complexities, so my OCD wouldn't let that go, I had to show a good _O(n)_ solution. – Andreas Jan 28 '20 at 01:09