map.find() time complexity - dictionary

If I have a map:
map myMap<string,vector<int>>
What would the best,average, and worst case time complexity be to find a key, and then iterate through the vector to find a specific int?
I know the map.find() method is O(log n), but does the fact that I have to then search for an int within a vector change the time complexity?
Thanks

It'd help if you had stated this was C++. std::map is usually implemented as a binary tree, so that's where the O(log(n)) on the find operation comes from.
It'd be O(log(n) + m), where n is the size of the map and m is the size of (each) vector. I'm assuming that you only do one lookup and then iterate through the whole vector that corresponds to your key.
Since log(n) grows slowly, unless you have extremely small vectors, log(n) should be < m, therefore your algorithm should be O(m).

Related

Why the time complexity of linked list ( single linked list ) isn't n-1?

My point is that when the pointer traverse in a linked list till n-1 position he get the value of nth easily because as we know the address of nth pointer is at n-1th location. Hence, Time Complexity Must needed to be n-1 instead of n.
any Big O notation always describes an upper bound. this means although n-1 would be more accurate to describe the complexity, it is still included in O(n).
Secondly any constant factor (-1) is reduced from the equation, when describing a Big O complexity class, because for any decent large n any constant factor does not have any noticable influence on the result.
The same as any constant factor for the n is removed. O(3n+5) is the same complexity class as O(n).
This both combined is the reason why you describe the complexity as O(n) and not O(n-1), although it might technically still be correct and more accurate.

Dictionary and factorial of large numbers

For n queries I am given a number x and I have to print its factorial under modulo 1000000007.
def fact_eff(n, d):
if n in d:
return d[n]
else:
ans=n*fact_eff(n-1,d)
d[n]=ans
return ans
d={0:1}
n=int(input())
while(n!=0):
x=int(input())
print(fact_eff(x, d)%1000000007)
n=n-1
The problem is that x can be as large as 100000 and I receive runtime error for values greater than 3000 as maximum recursion depth exceeds. Am I missing something with the modulus operator?
Why would you use recursion in the first place to compute a simple factorial? You can check the dictionary in a loop. Or better, start at the highest valid memoized position and go higher from there, creating new entries as you go.
To save space, maybe only record n! every 32 iterations or something, so future calls need at most 31 multiplies. Still O(1) but trading some computation for huge space savings.
Also, does it work to apply the modulus before you get the final huge product? Like every few multiply steps to keep the numbers small? Or every single step if that keeps the numbers small enough for CPython's single-limb fast path. I think (x * y) % n = ((x%n) * y) % n. (But I didn't double-check that.)
If so, you could combine early modulo with sparse memoization to memoize the final modulo-reduced result.
(For numbers above 2^30, Python BigInteger multiply cost should scale with number of 2^30 chunks required to represent the number. Fortunately one of the multiplicands is always small, being the counter. Keeping the product small buys speed, but division is expensive so it's a tradeoff. And doing any more operations costs Python interpreter overhead which may simply dominate anyway until numbers get really huge.)

Prime factorization in a functional language

I'm writing in OCaml, trying to give a (somewhat) efficient implementation of prime factorization. I figure the best representation of a number 2 or more is in a list of exponents. For simplicity with consing I'll do it in decreasing order of primes. So 2 would be [1] and 3 would be [1;0] and 4 would be [2], and 5 [1;0;0].
I was thinking of using the sieve idea to take a number n and look for all possible divisors between 2 and sqrt(n). Then divide by any divisor and recurse. However, every implementation that I can think of seems to involve repeatedly searching over a list and that seems just unnecessarily inefficient. The outline of my solution is best stated in this code
let rec pf n =
if (n=2) then ([1], 0)
else let sq = int_of_float ( (float_of_int n) ** 0.5 ) in
let primes = getPrimes sq in
match earliestDiv n primes with
| None -> n::(zero_list (n-1))
| Some (x, i) -> let subproblem pf (n/x) in
increment subproblem i
The helper functions here would be:
getPrimes which takes an int and returns a list of all prime numbers less-than-or-equal to it.
earliestDiv which takes an int n and list of ints lst, returns an int*int option corresponding to the earliest number in lst which divides n. That will be the first coordinate of the tuple; the second coordinate will return the index of this prime x in the list of primes.
increment will take an int list and index, and increase by 1 the number located at the index.
All of these helper functions keep making lists, and passing through lists, and so on. And in fact, I often feel like I'm doing this in functional programming. I often have the sense that I'm unnecessarily iterating over lists whereas in imperative languages I would be writing code that is more efficient. Perhaps it's just in my head, and when writing in imperative languages I less often notice how many resources are going into some of the list operations I use. But if I'm missing some important technique that could prevent repeatedly scanning lists, I'd be curious to hear it.
The question: Is it necessary to repeatedly make and iterate over lists in order to write this function?
If you end up indexing a list, or filling a list with default elements up to a fixed size, lists are most probably the wrong data structure. For prime factorisation, you probably want an implementation of a sparse array. Maps would be a better (if not optimal) implementation than fixed-size lists.

What is the inverse of O(log n) time?

I'm doing some math, and today I learned that the inverse of a^n is log(n). I'm wondering if this applies to complexity. Is the inverse of superpolynomial time logarithmic time and vice versa?
I would think that the inverse of logarithmic time would be O(n^2) time.
Can you characterize the inverses of the common time complexities?
Cheers!
First, you have to define what you mean by inverse here. If you mean an inverse by composing two functions together with the linear function being the identity function, i.e. f(x)=x, then the inverse of f(x)=log x would be f(x)=10^x. However, one could define a multiplicative function inverse where the constant function f(x)=1 is the identity function, then the inverse of f(x)=x would be f(x)=1/x. While this is a bit complicated, it isn't that different than saying, "What is the inverse of 2?" and without stating an operation, this is quite difficult to answer. An additive inverse would be -2 while a multiplicative inverse would be 1/2 so there are different answers depending on which operator you want to use.
In composing functions, the key becomes what is the desired end result: Is it O(n) or O(1)? If the latter may be much more challenging in composing functions as I'm not sure if composing O(log n) with a O(1) would give you a constant in the end or if it doesn't negate the initial count. For example, consider doing a binary search for something with O(log n) time complexity and a basic print statement as something with O(1) time complexity and if you put these together, you'd still get O(log n) as there would still be log n calls within the composed function that prints a number each time going through the search.
Consider the idea of taking two different complexity functions and putting one inside the other, the overall complexity is likely to be the product of each. Consider a double for loop where each loop is O(n) complexity, the overall complexity is O(n) X O(n) = O(n^2) which would mean that in the case of finding something that cancels out the log n would be challenging as you'd have to find something with O(1/(log n)) which I'm not sure exists in reality.

Big O of Recursive Methods

I'm having difficulty determining the big O of simple recursive methods. I can't wrap my head around what happens when a method is called multiple times. I would be more specific about my areas of confusion, but at the moment I'm trying to answer some hw questions, and in lieu of not wanting to cheat, I ask that anyone responding to this post come up with a simple recursive method and provide a simple explanation of the big O of said method. (Preferably in Java... a language I'm learning.)
Thank you.
You can define the order recursively as well. For instance, let's say you have a function f. To calculate f(n) takes k steps. Now you want to calculate f(n+1). Lets say f(n+1) calls f(n) once, then f(n+1) takes k + some constant steps. Each invocation will take some constant steps extra, so this method is O(n).
Now look at another example. Lets say you implement fibonacci naively by adding the two previous results:
fib(n) = { return fib(n-1) + fib(n-2) }
Now lets say you can calculate fib(n-2) and fib(n-1) both in about k steps. To calculate fib(n) you need k+k = 2*k steps. Now lets say you want to calculate fib(n+1). So you need twice as much steps as for fib(n-1). So this seems to be O(2^N)
Admittedly, this is not very formal, but hopefully this way you can get a bit of a feel.
You might want to refer to the master theorem for finding the big O of recursive methods. Here is the wikipedia article: http://en.wikipedia.org/wiki/Master_theorem
You want to think of a recursive problem like a tree. Then, consider each level of the tree and the amount of work required. Problems will generally fall into 3 categories, root heavy (first iteration >> rest of tree), balanced (each level has equal amounts of work), leaf heavy (last iteration >> rest of tree).
Taking merge sort as an example:
define mergeSort(list toSort):
if(length of toSort <= 1):
return toSort
list left = toSort from [0, length of toSort/2)
list right = toSort from [length of toSort/2, length of toSort)
merge(mergeSort(left), mergeSort(right))
You can see that each call of mergeSort in turn calls 2 more mergeSorts of 1/2 the original length. We know that the merge procedure will take time proportional to the number of values being merged.
The recurrence relationship is then T(n) = 2*T(n/2)+O(n). The two comes from the 2 calls and the n/2 is from each call having only half the number of elements. However, at each level there are the same number of elements n which need to be merged, so the constant work at each level is O(n).
We know the work is evenly distributed (O(n) each depth) and the tree is log_2(n) deep, so the big O of the recursive function is O(n*log(n)).

Resources