j goes from i to i * 2. But i itself is growing linearly with respect to n, so the j loop, being dependent on the range of i, is also growing linearly. Combining that (linear*linear), you get O(n^2).
example values:
n = 4, i = n/4 --> j goes from 1 to 2 (range = 1)
n = 4, i = n/2 --> j goes from 2 to 4 (range = 2)
n = 4, i = n ==> j goes from 4 to 8 (range = 4)
So as i doubles (n/4, n/2, n), the range that j needs to iterate over also doubles (1, 2, 4). Which means that as i increases linearly, the range that j needs to iterate over also increases linearly. So you essentially have two linear loops, and the overall complexity is O(n^2).
Sorry if I'm not clearly explaining it, as I tend to think of these things on a case-by-case basis. But in general, you need to look at how j is growing based on what it's dependent on, and then you need to know how fast the thing that it's dependent on is growing.
It often helps if you plug in some values of n and count the number of times the inner-most loop iterates. It's almost cheating, but if you're confused it helps a lot to get an idea of how the algorithm's no. of instructions grows with respect to n.
_____________________
Edit: Here's an easier way of thinking about that loop.
j starts at i in your inner loop, but this is equivalent (in terms of complexity) to subtracting i from both the initial condition of j and the terminating condition of j.
So
for (int j = i; j <= i * 2; j++)
is essentially the same as
for (int j = i - i; j <= i * 2 - i; j++)
which is
for (int j = 0; j <= i; j++)
Now, your loop algorithm is:
1 2 3 4 5 6 7
|
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= i; j++)
{
print(“*”);
}
}
|
and you already know that answer to that.