5

I am trying to work through a coding problem of string manipulation in Java. The question is that

Given two strings S and T consisting of digits and lowercase letters, you are allowed to remove only one digit from either string, count how many ways of removal to make S lexicographically smaller than T.

I came up with this test case myself. If s = '3ab' and t = 'cd', return 1. If s = '123ab' and t = '423cd', return 6.

My idea is to use 2 for loops and go through each string by checking if a char is digit, remove it and compare with the other string.

private static int numSmaller(String s, String t){
    int ways = 0;

    for(int i = 0; i < s.length(); i++){
        StringBuilder sbs = new StringBuilder(s);
        if(Character.isDigit(s.charAt(i))){
            sbs.deleteCharAt(i);
            String sub = sbs.toString();
            if(sub.compareTo(t) < 0) {
                ways++;
            }
        }
    }

    for(int i = 0; i < t.length(); i++){
        StringBuilder sbt = new StringBuilder(t);
        if(Character.isDigit(t.charAt(i))){
            sbt.deleteCharAt(i);
            String sub = sbt.toString();
            if(s.compareTo(sub) < 0){
                ways++;
            }
        }
    }
    return ways;
}

As you can see the space complexity is pretty bad, and the code also seems redundant. Is there a way to optimize this piece of code? Does anyone see a way to not use a string builder or create a new string each time? Any input is appreciated!

timeRocket
  • 93
  • 1
  • 1
  • 9

7 Answers7

1

I did it using streams and compared it to your approach with random strings of length 10. I ran 1 million test cases of those strings and the two methods provided the same results.

The stream part is fairly straightforward. I use an IntStream to index into a string to build substrings based on digit location. Then I filter based on a passed BiFunction lambda that acts as a two argument predicate. Filtering on that I count the successes.

I do this twice, reversing the arguments and the predicate logic, and sum up the two counts.

long count = count(s1, t1, (a, b) -> a.compareTo(b) < 0);
count += count(t1, s1, (a, b) -> b.compareTo(a) < 0);   

public static long count(String s, String t, BiFunction<String, String, Boolean> comp) {

      return IntStream.range(0, s.length()).filter(
        i -> Character.isDigit(s.charAt(i))).mapToObj(
              i -> s.substring(0, i) + s.substring(i + 1)).filter(
                    ss -> comp.apply(ss, t)).count();
}
Krzysztof Atłasik
  • 21,985
  • 6
  • 54
  • 76
WJS
  • 36,363
  • 4
  • 24
  • 39
1

In C# very easy you can do this code, below code will help you.

        private static int numSmaller(string s, string t)
    {
        int ways = 0;

        for (int i = 0; i < s.Length; i++)
        {
            StringBuilder sbs = new StringBuilder(s);
            if (Char.IsDigit(s[i]))
            {
                sbs.Remove(i,1);
                string sub = sbs.ToString();
                if (sub.CompareTo(t) < 0)
                {
                    ways++;
                }
            }
        }

        for (int i = 0; i < t.Length; i++)
        {
            StringBuilder sbt = new StringBuilder(t);
            if (Char.IsDigit(t[i]))
            {
                sbt.Remove(i,1);
                string sub = sbt.ToString();
                if (s.CompareTo(sub) < 0)
                {
                    ways++;
                }
            }
        }
        return ways;
    }
Inam Abbas
  • 468
  • 3
  • 8
0

You can reduce the time complexity to O(m+n), where m, n are the length of S, T I wrote C++ code and tested it works.

The idea is to compare the first character of S and T:

First count how many digits S and T have, then compare their first characters

if(S[0] < T[0])
    can remove any digits behind except for the first characters of S and T
    then check if you can remove S[0] or T[0] to make S<T
else
    check if you can remove S[0] or  T[0] to make S<T if can not, return 0;
  • It looks like this solution doesn’t work for s=“a1”, t=“a2”. There is additional complexity when there are equal letters. Have you handled this? – Amja Feb 28 '21 at 11:49
0
System.out.println(getcnt(s1, t1) + getcnt(t1, s1));

    public static int getcnt(String a, String b) {
    int ways = 0;
    for (int i = 0; i < a.length(); i++) {

        if (Character.isDigit(a.charAt(i))) {

            String s = a.substring(0, i) + a.substring(i + 1);

            if (s.compareTo(b) < 0 || b.compareTo(s) < 0)
                ways++;

        }
    }
    return ways;
}
0

Here is the working solution with O(n+m) time complexity. Only the first character is important and based on the fact it's number of not we can decide how to deal with it.

public static int numSmaller(String s, String t) {
    return numSmaller(s,t,1);
}
public static int numSmaller(String s, String t,int n) {
    if(n==0){
        if(s.compareTo(t) < 0){
            return 1;
        }else{
            return 0;
        }
    }
    int output = 0;
    if(n==1){
        char sc = s.charAt(0);
        char tc = t.charAt(0);
        int sCount = digitCount(s);
        int tCount = digitCount(t);
        if(sc < tc){
            if(Character.isDigit(sc)){// s = '123ab' and t = 'c23cd',
                output += ((sCount-1)+tCount);//we need to delete exactly one character
                output+=numSmaller(s.substring(1),t,0);
            }else{
                output += (sCount)+tCount;
            }

        }else{
            if(!Character.isDigit(sc) && !Character.isDigit(tc) ){
                return 0;
            }
            if(Character.isDigit(sc)){
                output +=numSmaller(s.substring(1),t,0);
            }
            if(Character.isDigit(tc)){
                output +=numSmaller(s,t.substring(1),0);
            }
        }
    }
    return output;

}
 private static int digitCount(String s){
    int count = 0;
    for(int i=0;i<s.length();i++){
        char c = s.charAt(i);
        if(Character.isDigit(c)){
            count++;
        }
    }
    return count;
}
 /**
    String s="a123ab";
    String t ="b423cd";
    System.out.println( numSmaller(s,t));
    
     * a23ab,b423cd
     * a13ab,b423cd
     * a12ab,b423cd
     * a123ab,b23cd
     * a123ab,b43cd
     * a123ab,b42cd
     *
     */
Reza
  • 1,516
  • 14
  • 23
0

The O(n+m) solutions that relies on checking just the first characters, fail for this input

s = "ab12c"
t = "ab24z"

It is still a great improvement to check just the first characters, but that edge case must be handled

jigabuck
  • 21
  • 1
  • 2
0

This code works in Python, in case it's useful for you:

def solution(s, t):
    counter = 0
    len_s, len_t = len(s), len(t)
    
    for I in range(len_s):
        if s[I].isdigit():
            temp = s[:I] + s[I+1:]  # Remove the digit from s
            if temp < t:
                counter += 1
        elif len_s <= i : 
            break  # Break the loop if a non-digit character in s has a higher ASCII value than the corresponding character in t

    for I in range(len_t):
        if t[I].isdigit():
            temp = t[:I] + t[I+1:]  # Remove the digit from t
            if s < temp:
                counter += 1
        elif len_t <= i : 
            break  # Break the loop if a non-digit character in s has a higher ASCII value than the corresponding character in t

    return counter
Adrien C
  • 1
  • 2