Java – Are the two sample codes different in time complexity and how?

Are the two sample codes different in time complexity and how?… here is a solution to the problem.

Are the two sample codes different in time complexity and how?

Given some issues in the recruitment process, one is finding the first distinct character from a given string in Java.
Here are two sample codes, the first of which passes all the test cases, but the second fails in a few test cases due to time complexity. Since I’m new to algorithms and complexity analysis, can someone help me understand if and how the time complexity of these two pieces of code is different?

Sample Code 1:

public static char firstNonRepeatingCharater(String s) {
    for(int i=0; i<s.length(); i++) {
        if(s.indexOf(s.charAt(i)) == s.lastIndexOf(s.charAt(i))) {
            return s.charAt(i);
        }
    }
    return '_';
}

Sample Code 2:

public static char firstNonRepeatingCharater(String s) {
    for(int i=0; i<s.length(); i++) {
        int count = 0;
        for(int j=s.length()-1; j>=0; j--) {
            if(s.charAt(i) == s.charAt(j)) {
                count++;
            }
        }
        if(count == 1) {
            return s.charAt(i);
        }
    }
    return '_';
}

Solution

Calculate complexity

First of all, from your question, I realized that a quick explanation would be nice with time complexity and big oh notation

Quote from wikipedia:

In computer science, the time complexity is the computational
complexity that describes the amount of time it takes to run an
algorithm. Time complexity is commonly estimated by counting the
number of elementary operations performed by the algorithm, supposing
that each elementary operation takes a fixed amount of time to
perform. […]
Since an algorithm’s running time may vary among different inputs of
the same size, one commonly considers the worst-case time complexity,
which is the maximum amount of time required for inputs of a given
size.

Big O symbol

Algorithmic complexities are classified according to the type of
function appearing in the big O notation. For example, an algorithm
with time complexity O(n). O(n) is a linear time
algorithm and an algorithm with time complexity O(n^alpha) for some constant
alpha >1 is a polynomial time algorithm.

About two code examples

Take a look at these two code examples. We immediately noticed something.

    The

  1. amount of code in Sample 1 is much smaller. So we may have fewer operations.
  2. But more importantly, we notice that there is a nested for loop in the second sample. The first sample did not. This does not necessarily reduce the cost of hiding the code in the method.

Let’s do a little experiment. When size of Z is = 1, 10, 100, and 1000, let’s calculate the operands required in the general case (the first distinct character in the middle).

Note: In this example/thought experiment, I’ll evaluate each row as an operation with a cost of 1. This is a rough simplification.
Please forgive any omissions in counting the number of operations.

Algorithm 1: (size of s, lines executed)
-
1, 3
10, (2*5)+1 = 11
100, (2*50)+1 = 101
1000, (2* 500) + 1 = 1001
Total = (2* N/2 ) + 1 

We see that the number of execution results is linearly related to the initial input size.

Algorithm 2: (N = size of s, lines executed)
-
1, 7
10, 2(5*5) + 2
100, 2(50*50) + 2
1000, 2(500*500) + 2
Total = ((N/2) *2 + 2*(N/2)*(N/2) + 2

In logarithmic 1, we see that complexity is linearly related to the input size of s, specifically O(n).
In algorithm 2, we see that it is polynomial time, specifically O(n^2).
However, this becomes wrong once we take into account the actual cost of indexof. and lastIndexOf

Add the cost of indexOf and LastIndexOf

Let n=Size of the String S

Algorithm 1: (Roughly estimated operands).

 for(int i=0; i<s.length(); i++) // -  N/2
     if(s.indexOf(s.charAt(i)) == s.lastIndexOf(s.charAt(i))) {  // ~ worst case =  (N/2 + N/2) * N/2
     return s.charAt(i);  1 operation

Total = N/2 + (N/2 + N/2)*N/2 +1 
    = N^2/2 + N/2  + 1

Algorithm 2: (roughly estimated operands).

    for(int i=0; i<s.length(); i++) { // - executed N/2 times
        int count = 0;                - executed N/2 times
        for(int j=s.length()-1; j>=0; j--) {  // Executed (n/2 * n/2 )
            if(s.charAt(i) == s.charAt(j)) {  // Executed (n/2 * n/2 ) 
                count++;                       Executed 1 time 
            }
        }
        if(count == 1) {                      //Evaluated N/2 times
            return s.charAt(i);                Executed 1 time
        }
    }
    return '_';

Total = N/2 + N/2 + 2(N/2*N/2) + 1
= N^2/2 + N + 1

Note: I did a lot of simplifications. I also assume that the distinct character is in the center (n/2) of the string (character array).
The important point to note is that the estimated number of performed operations increases with size. The above example is intended to demonstrate this. Not 100% accurate.

Also, the whole result/parameter indicated in the comment is how we think about indexOf and lastIndexof. Do we treat them as a single operation? Or do we treat them as N/2 operations? It also depends on the implementation of indexOf and lastIndexOf implementations. If these are searching arrays, they are hidden inside for loops. In the case that they do this (the last example), the costs of the two algorithms become more similar.

Algo1: N^2/4 + N/2  + 1
VS
Algo2: N^2/2 + N + 1

Related Problems and Solutions