Why O(n) takes longer than O(n^2)? - swift

I have a LeetCode problem:
Given an M x N matrix, return True if and only if the matrix is Toeplitz.
A matrix is Toeplitz if every diagonal from top-left to bottom-right has the same element.
My solution is (Swift):
func isToeplitzMatrix(_ matrix: [[Int]]) -> Bool {
if matrix.count == 1 { return true }
for i in 0 ..< matrix.count - 1 {
if matrix[i].dropLast() != matrix[i + 1].dropFirst() { return false }
}
return true
}
As I understood Big O notation, my algorithm's time complexity is O(n), while LeetCode top answers' is O(n^2).
Top answers example:
func isToeplitzMatrix(_ matrix: [[Int]]) -> Bool {
for i in 0..<matrix.count-1 {
for j in 0..<matrix[0].count-1 {
if (matrix[i][j] != matrix[i+1][j+1]) {
return false;
}
}
}
return true;
}
Still, my algorithm takes 36ms (according to LeetCode), while top answer takes 28ms.
When I commented out if matrix.count == 1 { return true } it took even more time to run - 56ms.

Your time complexity for the function is also O(n^2) because the function call dropLast is O(n).
Edit:
Also mentioned by Rup and melpomene, the comparison of arrays also takes the complexity up to O(n^2).
Also, Big O notation describes how the algorithm scales in response to n, the number of data. It takes away any constants for brevity. Therefore, an algorithm with O(1) can be slower than an algorithm with O(n^3) if the input data is small enough.

Related

I'm working on the algorithm and it only gives an error in 1 case (Runtime Error)

Only case 4 gives runtime error. I looked at other answers but couldn't find a solution
Question
I don't return the array. I'm just adding elements
func circularArrayRotation(a: [Int], k: Int, queries: [Int]) -> [Int] {
var result = [Int]()
for i in queries {
if i < k {
result.append(a[a.count-k+i])
}
else {
result.append(a[i-k])
}
}
return result
}
Isn't the time complexity of this algorithm O(n). Am I calculating wrong?
k can be much larger than the length of the array, so your approach is failing since the index is much larger than the array length.
To correctly handle this, make k equal to k modulus array_length, since rotating the array by array_length times effectively makes no changes to the current ordering.

How to properly call another function within a function in swift?

I'm learning an swift and I've written two functions and have tried them on their own they both work well. However when I try to call one function within another one I can't seem to get the desired out-put that I seek.
The task at hand is that one function should print Prime numbers whilst the other is to calculate and check if the number is prime. I am supposed to call the check if number is prime from the print Prime numbers function.
below is my code:
This function calculates whether or not the X:Int is a prime number. It's set to a boolean because I'm supposed to print "true" or "false" in the function below it.
func isPrime(_ x: Int) -> Bool {
if(x%2 == 0 || x%3 == 0){
if(x == 2 || x == 3){
return(true)
}
return(false)
}
else{
//if the number is less than or equal to 1, we'll say it's not prime
if(x <= 1){
return(false)
}
}
return true
}
This piece calculates the printing of the prime number from 1 to n.
func PrintPrimes(upTo n: Int) {
for x in 1...n {
var count = 0
for num in 1..<x {
isPrime(x)
count += 1
}
if count <= 1 {
print(isPrime(x))
}
}
}
This piece only runs twice and i'm not exactly sure why. I don't know if its because i'm not calling it correctly or I'd have to change up some calculations.
All help is appreciated
EDIT:
Here is the original printPrimes() before I decided to call isPrime within the function. This function calculates the prime numbers only and prints them up to n.
func printPrimes(upTo n: Int) {
for x in 1...n {
var count = 0
for num in 1..<x {
if x % num == 0 {
count += 1
}
}
if count <= 1 {
print(x)
}
}
}
Your second routine is printing only two values because it is calling isPrime, but never doing anything conditional on the value returned, but rather incrementing count regardless. And since you’re printing only if count is <= 1, that will happen only for the first two values of n.
But let’s say you were trying to print the prime numbers up to a certain number, you could do:
func printPrimes(upTo n: Int) {
for x in 1...n {
if isPrime(x) {
print(x)
}
}
}
(As a matter of convention, in Swift, when we say “through n”, we’d iterate 1...n, and if someone said “up to n”, we’d iterate 1..<n. But because your original code snippet uses upTo in conjunction with 1...n, I’ll use that here, but just note that this isn’t very consistent with standard Swift API patterns.)
Unfortunately, isPrime is not correct, either. So you’ll have to fix that first. For example, consider 25. That is not divisible by 2 or 3, but isn’t prime, either.
If you look at the original printPrimes that was provided, what it effectively does is say “by how many whole integers less than x is x divisible ... if only divisible by one other number (namely 1), then it’s a prime.” That logic, although not efficient, is correct. You should go ahead and use that inside your isPrime routine. But that “is divisible by 2 or 3” logic is not correct.
You can do it this way, in your printPrimes you can loop up to the number you want and just check if the number is prime by calling the function with the number. But you have to check your isPrime function. Your printPrimes should only do what its name says (print the prime numbers up to n) and all the logic to check if the number is prime should be on your isPrime function.
Also its a good practice to use camelCase on functions, you should rename your function to printPrimes instead of PrintPrimes.
func printPrimes(upTo n: Int) {
for x in 1...n {
if isPrime(x) {
print(x)
}
}
}

Swift how does my function exceed O(n)?

I am trying to work on a leetcode problem that asks for
Given an array of integers where 1 ≤ a[i] ≤ n (n = size of array), some elements appear twice and others appear once.
Find all the elements of [1, n] inclusive that do not appear in this array.
My solution to the problem is:
func findDisappearedNumbers(_ nums: [Int]) -> [Int] {
var returnedArray = [Int]()
if nums.isEmpty == false {
for i in 1...nums.count {
if nums.contains(i) == false {
returnedArray.append(i)
}
}
} else {
returnedArray = nums
}
return returnedArray
}
However, leetcode tells me that my solution is "Time limit exceeded"
Shouldn't my solution be O(n)? I am not sure where did I made it to be greater than O(n).
If I haven't missed anything your algorithm is O(n^2).
First, you iterate over each element of the array which is O(n), but for each element, you call contains which has to iterate over all the elements again and you end up with O(n^2).
I refrain from telling you the solution since it is for leetcode.

What is the O() complexity of this function?

This is a simple function that checks to see if the string is unique. I reason that the complexity should be N * N -> N^2. Is this correct? Even if the second N will always be smaller than the first.
func isUnique(_ str: String) -> Bool {
let charArr = Array(str.characters)
for (i1 , char) in charArr.enumerated() {
guard i1 != charArr.count - 1 else {
break
}
for (i2, char) in charArr[(i1 + 1)..<charArr.count].enumerated() {
if charArr[i1] == char {
return false
}
}
}
return true
}
Yes, There are a lot of myths behind this problem and when you analysis on Big O topic, you getting so many varying answers. And the most popular question is:
"If two nested for loops contains break statement. so still my complexity
is n*n or O(n2)?"
I think the simple answer is"
Big-O notation isn't about finding exact values given your actual parameters. It is about determining asymptotic runtime.

Merge Sort algorithm efficiency

I am currently taking an online algorithms course in which the teacher doesn't give code to solve the algorithm, but rather rough pseudo code. So before taking to the internet for the answer, I decided to take a stab at it myself.
In this case, the algorithm that we were looking at is merge sort algorithm. After being given the pseudo code we also dove into analyzing the algorithm for run times against n number of items in an array. After a quick analysis, the teacher arrived at 6nlog(base2)(n) + 6n as an approximate run time for the algorithm.
The pseudo code given was for the merge portion of the algorithm only and was given as follows:
C = output [length = n]
A = 1st sorted array [n/2]
B = 2nd sorted array [n/2]
i = 1
j = 1
for k = 1 to n
if A(i) < B(j)
C(k) = A(i)
i++
else [B(j) < A(i)]
C(k) = B(j)
j++
end
end
He basically did a breakdown of the above taking 4n+2 (2 for the declarations i and j, and 4 for the number of operations performed -- the for, if, array position assignment, and iteration). He simplified this, I believe for the sake of the class, to 6n.
This all makes sense to me, my question arises from the implementation that I am performing and how it effects the algorithms and some of the tradeoffs/inefficiencies it may add.
Below is my code in swift using a playground:
func mergeSort<T:Comparable>(_ array:[T]) -> [T] {
guard array.count > 1 else { return array }
let lowerHalfArray = array[0..<array.count / 2]
let upperHalfArray = array[array.count / 2..<array.count]
let lowerSortedArray = mergeSort(array: Array(lowerHalfArray))
let upperSortedArray = mergeSort(array: Array(upperHalfArray))
return merge(lhs:lowerSortedArray, rhs:upperSortedArray)
}
func merge<T:Comparable>(lhs:[T], rhs:[T]) -> [T] {
guard lhs.count > 0 else { return rhs }
guard rhs.count > 0 else { return lhs }
var i = 0
var j = 0
var mergedArray = [T]()
let loopCount = (lhs.count + rhs.count)
for _ in 0..<loopCount {
if j == rhs.count || (i < lhs.count && lhs[i] < rhs[j]) {
mergedArray.append(lhs[i])
i += 1
} else {
mergedArray.append(rhs[j])
j += 1
}
}
return mergedArray
}
let values = [5,4,8,7,6,3,1,2,9]
let sortedValues = mergeSort(values)
My questions for this are as follows:
Do the guard statements at the start of the merge<T:Comparable> function actually make it more inefficient? Considering we are always halving the array, the only time that it will hold true is for the base case and when there is an odd number of items in the array.
This to me seems like it would actually add more processing and give minimal return since the time that it happens is when we have halved the array to the point where one has no items.
Concerning my if statement in the merge. Since it is checking more than one condition, does this effect the overall efficiency of the algorithm that I have written? If so, the effects to me seems like they vary based on when it would break out of the if statement (e.g at the first condition or the second).
Is this something that is considered heavily when analyzing algorithms, and if so how do you account for the variance when it breaks out from the algorithm?
Any other analysis/tips you can give me on what I have written would be greatly appreciated.
You will very soon learn about Big-O and Big-Theta where you don't care about exact runtimes (believe me when I say very soon, like in a lecture or two). Until then, this is what you need to know:
Yes, the guards take some time, but it is the same amount of time in every iteration. So if each iteration takes X amount of time without the guard and you do n function calls, then it takes X*n amount of time in total. Now add in the guards who take Y amount of time in each call. You now need (X+Y)*n time in total. This is a constant factor, and when n becomes very large the (X+Y) factor becomes negligible compared to the n factor. That is, if you can reduce a function X*n to (X+Y)*(log n) then it is worthwhile to add the Y amount of work because you do fewer iterations in total.
The same reasoning applies to your second question. Yes, checking "if X or Y" takes more time than checking "if X" but it is a constant factor. The extra time does not vary with the size of n.
In some languages you only check the second condition if the first fails. How do we account for that? The simplest solution is to realize that the upper bound of the number of comparisons will be 3, while the number of iterations can be potentially millions with a large n. But 3 is a constant number, so it adds at most a constant amount of work per iteration. You can go into nitty-gritty details and try to reason about the distribution of how often the first, second and third condition will be true or false, but often you don't really want to go down that road. Pretend that you always do all the comparisons.
So yes, adding the guards might be bad for your runtime if you do the same number of iterations as before. But sometimes adding extra work in each iteration can decrease the number of iterations needed.