About time complexity of this recurrence relation after memoizing it - c++

I solved a question on LeetCode.com with some online help:
There are 2N people a company is planning to interview. The cost of flying the i-th person to city A is costs[i][0], and the cost of flying the i-th person to city B is costs[i][1]. Return the minimum cost to fly every person to a city such that exactly N people arrive in each city.
as:
class Solution {
public:
int helper(vector<vector<int>>& c, vector<vector<int>>& dp, int start, int a, int b) {
if((a>c.size()/2) || (b>c.size()/2)) return INT_MAX/2;
if(a+b==(int)c.size()) return 0;
if(dp[a][b]!=-1) {
return dp[a][b];
}
int minA=c[start][0]+helper(c, dp, start+1, a+1, b);
int minB=c[start][1]+helper(c, dp, start+1, a, b+1);
int minVal=min(minA, minB);
dp[a][b]=minVal;
return minVal;
}
int twoCitySchedCost(vector<vector<int>>& costs) {
vector<vector<int>> dp(costs.size()+1, vector<int>(costs.size()+1, -1));
int minCost=helper(costs, dp, 0, 0, 0);
return minCost;
}
};
Without the dp table, using the Subtract-and-Conquer Recurrence method (courtesy of this answer, I came up with the time complexity of the recursive solution as O(n) where n is the total number of people (a=b=1 and k=0).
However, I am unsure how to derive the time complexity now, with the inclusion of the dp table. I am confused because, AFAIK, how many times the cached values would be used depends upon the specific problem instance (value of n i.e., the size of the costs table). Obviously the time complexity has improved (since we have cached the results of the overlapping sub-problems) hasn't it?
How can I derive this?
Edit
As I notice this again, I realize that I made a mistake in calculating the time complexity above - my a is not 1, it is 2. Which brings the time complexity to be O(2^n).

As a rule of thumb that you can almost always rely on, the time complexity of a DP is O(size of DP Table * O(transition)). This is because you can assume getting the memory for your DP Table is time itself, not to mention the fact that you, in most cases, would have the possibility to visit all of those states. For most problems, if you don't visit a systematic majority of your states given worst-case input, your state can be reduced. But I digress.
For this specific problem, your runtime would be O(costs.size()^2), since your transition looks O(1) and your memo table is O(costs.size()^2)
Also, just a nice thing that i like to do is return dp[i][j]=minVal. And in general, return dp[state] = returnval is helpful because you know you didn't forget to memoise. Best of luck!

Related

How is binary search applicable here (since the values are not monotonic)?

I am solving a LeetCode problem Search in Rotated Sorted Array, in order to learn Binary Search better. The problem statement is:
There is an integer array nums sorted in ascending order (with distinct values). Prior to being passed to your function, nums is possibly rotated at an unknown pivot index. For example, [0,1,2,4,5,6,7] might be rotated at pivot index 3 and become [4,5,6,7,0,1,2]. Given the array nums after the possible rotation and an integer target, return the index of target if it is in nums, or -1 if it is not in nums.
With some online help, I came up with the solution below, which I mostly understand:
class Solution {
public:
int search(vector<int>& nums, int target) {
int l=0, r=nums.size()-1;
while(l<r) { // 1st loop; how is BS applicable here, since array is NOT sorted?
int m=l+(r-l)/2;
if(nums[m]>nums[r]) l=m+1;
else r=m;
}
// cout<<"Lowest at: "<<r<<"\n";
if(nums[r]==target) return r; //target==lowest number
int start, end;
if(target<=nums[nums.size()-1]) {
start=r;
end=nums.size()-1;
} else {
start=0;
end=r;
}
l=start, r=end;
while(l<r) {
int m=l+(r-l)/2;
if(nums[m]==target) return m;
if(nums[m]>target) r=m;
else l=m+1;
}
return nums[l]==target ? l : -1;
}
};
My question: Are we searching over a parabola in the first while loop, trying to find the lowest point of a parabola, unlike a linear array in traditional binary search? Are we finding the minimum of a convex function? I understand how the values of l, m and r change leading to the right answer - but I do not fully follow how we can be guaranteed that if(nums[m]>nums[r]), our lowest value would be on the right.
You actually skipped something important by “getting help”.
Once, when I was struggling to integrate something tricky for Calculus Ⅰ, I went for help and the advisor said, “Oh, I know how to do this” and solved it. I learned nothing from him. It took me another week of going over it (and other problems) myself to understand it sufficient that I could do it myself.
The purpose of these assignments is to solve the problem yourself. Even if your solution is faulty, you have learned more than simply reading and understanding the basics of one example problem someone else has solved.
In this particular case...
Since you already have a solution, let’s take a look at it: Notice that it contains two binary search loops. Why?
As you observed at the beginning, the offset shift makes the array discontinuous (not convex). However, the subarrays either side of the discontinuity remain monotonic.
Take a moment to convince yourself that this is true.
Knowing this, what would be a good way to find and determine which of the two subarrays to search?
Hints:
A binary search as  ( n ⟶ ∞ )   is   O(log n)
O(log n) ≡ O(2 log n)
I should also observe to you that the prompt gives as example an arithmetic progression with a common difference of 1, but the prompt itself imposes no such restriction. All it says is that you start with a strictly increasing sequence (no duplicate values). You could have as input [19 74 512 513 3 7 12].
Does the supplied solution handle this possibility?
Why or why not?

Why does D'Esopo-Pape algoritham have worst case of exponential time comlexity?

D'Escopo-Pape algorithm is very similar in implementation to the Dijkstra's algorithm and works with negative weight edges but it doesn't work with negative cycles. It is apparently faster then Dijkstra's algorithm and Bellman-Ford algorithm in most cases. But there is apparently special cases where this algorithm takes exponential time, can someone provide some example or point me to some material that analyses this algorithm more thoroughly.
This is the implementation:
struct Edge {
int to, w;
};
int n;
vector<vector<Edge>> adj;
const int INF = 1e9;
void shortest_paths(int v0, vector<int>& d, vector<int>& p) {
d.assign(n, INF);
d[v0] = 0;
vector<int> m(n, 2);
deque<int> q;
q.push_back(v0);
p.assign(n, -1);
while (!q.empty()) {
int u = q.front();
q.pop_front();
m[u] = 0;
for (Edge e : adj[u]) {
if (d[e.to] > d[u] + e.w) {
d[e.to] = d[u] + e.w;
p[e.to] = u;
if (m[e.to] == 2) {
m[e.to] = 1;
q.push_back(e.to);
} else if (m[e.to] == 0) {
m[e.to] = 1;
q.push_front(e.to);
}
}
}
}
}
This is the link of the site i used: D'Esopo-Pape
Which doesn't really go in depth in terms of special cases and time-complexity.
Exponential time
This won't be a full proof, you can read "A Note on Finding Shortest Path Trees" (Aaron Kershenbaum, 1981, https://doi.org/10.1002/net.3230110410) if you want that. An other source you may find interesting is "Properties of Labeling Methods for Determining Shortest Path Trees".
The intuitive reason why this algorithm can go bad is that a node in set M0 is pulled out from it again to be re-examined later if an edge that points to it is found. That already sounds quadratic because there could be |V|-1 edges pointing to it, so every node could potentially be "resurrected" that many times, but it's even worse: that effect is self-amplifying because every time a node is "resurrected" in that way, the edges outgoing from that node can cause more resurrections, and so on. In a full proof, some care must be taken with the edge weights, to ensure that enough of those "resurrections" can actually happen, because they are conditional, so [Kershenbaum 1981] presents a way to build an actual example on which Pape's algorithm requires an exponential number of steps.
By the way, in the same paper the author says:
I have used this algorithm to find routes in very large, very sparse real networks (thousands of nodes and average nodal degree between 2 and 3) with a variety of length functions (generally distance-related) and have found it to outperform all others.
(but since no one uses this algorithm, there are not many available benchmarks, except this one in which Pape's algorithm does not compare so favourably)
In contrast, triggering the exponential behaviour requires a combination of a high degree, a particular order of edges in the adjacency list, and unusual edge weights. Some mitigations are mentioned, for example sorting the adjacency lists by weight before running the running the algorithm.
Why is it rarely used
I could not find any real source on this, and don't expect it to exist, after all you would not have to defend not-using some unusual algorithm that has strange properties to boot. There is the exponential worst case (though on closer inspection it seems unlikely to be triggered by accident, and anyway the Simplex algorithm has an exponential worse case, and it is used a lot), it is relatively unknown, and the only available actual benchmark casts doubt on the claim that the algorithm is "usually efficient" (though I will note they used a degree of 4, which still seems low, but it is definitely higher than the "between 2 and 3" used in the claim that the algorithm is efficient). Also, it should not be expected that Pape's algorithm will perform well on dense graphs in general, even if the exponential behaviour is not triggered.

How do I calculate the time complexity of the following function?

Here is a recursive function. Which traverses a map of strings(multimap<string, string> graph). Checks the itr -> second (s_tmp) if the s_tmp is equal to the desired string(Exp), prints it (itr -> first) and the function is executed for that itr -> first again.
string findOriginalExp(string Exp){
cout<<"*****findOriginalExp Function*****"<<endl;
string str;
if(graph.empty()){
str ="map is empty";
}else{
for(auto itr=graph.begin();itr!=graph.end();itr++){
string s_tmp = itr->second;
string f_tmp = itr->first;
string nll = "null";
//s_tmp.compare(Exp) == 0
if(s_tmp == Exp){
if(f_tmp.compare(nll) == 0){
cout<< Exp <<" :is original experience.";
return Exp;
}else{
return findOriginalExp(itr->first);
}
}else{
str="No element is equal to Exp.";
}
}
}
return str;
}
There are no rules for stopping and it seems to be completely random. How is the time complexity of this function calculated?
I am not going to analyse your function but instead try to answer in a more general way. It seems like you are looking for an simple expression such as O(n) or O(n^2) for the complexity for your function. However, not always complexity is that simple to estimate.
In your case it strongly depends on what are the contents of graph and what the user passes as parameter.
As an analogy consider this function:
int foo(int x){
if (x == 0) return x;
if (x == 42) return foo(42);
if (x > 0) return foo(x-1);
return foo(x/2);
}
In the worst case it never returns to the caller. If we ignore x >= 42 then worst case complexity is O(n). This alone isn't that useful as information for the user. What I really need to know as user is:
Don't ever call it with x >= 42.
O(1) if x==0
O(x) if x>0
O(ln(x)) if x < 0
Now try to make similar considerations for your function. The easy case is when Exp is not in graph, in that case there is no recursion. I am almost sure that for the "right" input your function can be made to never return. Find out what cases those are and document them. In between you have cases that return after a finite number of steps. If you have no clue at all how to get your hands on them analytically you can always setup a benchmark and measure. Measuring the runtime for input sizes 10,50, 100,1000.. should be sufficient to distinguish between linear, quadratic and logarithmic dependence.
PS: Just a tip: Don't forget what the code is actually supposed to do and what time complexity is needed to solve that problem (often it is easier to discuss that in an abstract way rather than diving too deep into code). In the silly example above the whole function can be replaced by its equivalent int foo(int){ return 0; } which obviously has constant complexity and does not need to be any more complex than that.
This function takes a directed graph and a vertex in that graph and chases edges going into it backwards to find a vertex with no edge pointing into it. The operation of finding the vertex "behind" any given vertex takes O(n) string comparisons in n the number of k/v pairs in the graph (this is the for loop). It does this m times, where m is the length of the path it must follow (which it does through the recursion). Therefore, it has time complexity O(m * n) string comparisons in n the number of k/v pairs and m the length of the path.
Note that there's generally no such thing as "the" time complexity for just some function you see written in code. You have to define what variables you want to describe the time in terms of, and also the operations with which you want to measure the time. E.g. if we want to write this purely in terms of n the number of k/v pairs, you run into a problem, because if the graph contains a suitably placed cycle, the function doesn't terminate! If you further constrain the graph to be acyclic, then the maximum length of any path is constrained by m < n, and then you can also get that this function does O(n^2) string comparisons for an acyclic graph with n edges.
You should approximate the control flow of the recursive calling by using a recurrence relation. It's been like 30 years since I took college classes in Discrete Math, but generally you do like pseuocode, just enough to see how many calls there are. In some cases just counting how many are on the longest condition on the right hand side is useful, but you generally need to plug one expansion back in and from that derive a polynomial or power relationship.

Computing the power function in O(1)

I have written a function that computes pow(a,b) in O(logb).
double pow(double a, int b){
double res=1;
while(b>0){
if (b%2==1){
res=res*a;
}
b=b>>1;
a=a*a;
}
return res;
}
I have stumbled upon the question if it would be possible to write a function pow(double a, double b) in O(1) time. Yet I have not found an answer.
If you don't allow yourself the use of the standard pow/exp/log functions nor precomputed tables, but allow floating-point multiplies, then your solution is optimal (constant time is impossible).
If you allow a few restrictions on the parameters (a positive, a and b doubles) and on the result (a double) you could use
exp(b * log(a))
This will often not be exact, even when an exact result is possible. In my Borland Delphi days, I coded a similar routine but used base 2 rather than the base e that is often used. That improved both the accuracy and the speed of the code, since the CPU works in base 2. But I have no idea how to do that in C++.
Yes, you can write one with precompute. Compute all possible power into a table and loop up when necessary. Indeed this method works for many problems.
Now, assume that you have found a new algorithm other than the table look up and the complexity of the new algorithm is O(1). Now a result;
The complexity of addition is O(1) by x^b * x^c = x^(a+c). calculate and take log base b, especially if base 2.
So you can reduce almost every arithmetic operation by using your new algorithm.
as Yvies said this is optimal.

What does it mean to satisfy O(f(N)) for a given equation f(N)?

This is the only question on my final review that I'm still uncertain about. I've figured all of the other 74 out, but this one is completely stumping me. I think it has something to do with finding C and k, but I don't remember how to do this or what it even means... and I may not even be on the right track there.
The question I'm encountering is "What is the minimum acceptable value for N such that the definition for O(f(N)) is satisfied for member function Heap::Insert(int v)?"
The code for Heap::Insert(int v) is as follows:
void Insert(int v)
{
if (IsFull()) return;
int p=++count;
while (H[p/2] > v) {
H[p] = H[p/2];
p/= 2;
}
H[p] = v;
}
The possible answers given are: 32, 64, 128, 256.
I'm completely stumped and have to take this exam in the morning. Help would be immensely appreciated.
I admit the question is quite obscure, but I will try to give a reasonable explanation.
If we call f(N) the temporal complexity of the operation executed by your code as a function of the number of elements in the heap, the professor wanted you to remember that f(N) = O(log(N)) for a binary heap insert, that is O(h), where h is the height of the heap and we assume it to be complete (remember how a heap works and that it can be represented as a binary tree). Thus, you have to try those four values of Nmin and find the smallest one that satisfies the definition, i.e. the one for which
f(n) <= k*log(N)
For each N >= Nmin and at least a k. I would give you the details for calculating f(N) if only your code did what the professor or you expected it to do.
Note: I'd really love a LaTeX render over Stack Overflow questions! Like on Math