Time complexity of Letter Combinations of a Phone Number - recursion

Here is LeetCode question 17:
Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. Return the answer in any order.
(https://leetcode.com/problems/letter-combinations-of-a-phone-number/)
Below is my DFS recursive code:
class Solution {
public static final String[] map = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public List<String> letterCombinations(String digits){
List<String> result = new ArrayList<String>();
if(digits == null || digits.length() == 0){return result;}
int curr_index = 0;
StringBuilder prefix = new StringBuilder("");
update_result(digits, prefix, curr_index, result);
return result;
}
private void update_result(String digits, StringBuilder prefix, int curr_index, List<String> result){
if(curr_index >= digits.length()){
result.add(prefix.toString());
return;
}
else{
String letters = map[ digits.charAt(curr_index) - '0' ];
for(int i = 0; i < letters.length(); i++){
prefix.append( letters.charAt(i) );
update_result(digits, prefix, curr_index+1, result);
prefix.deleteCharAt(prefix.length() -1);
}
return;
}
}
}
In the LeetCode solutions, it says the time complexity is O(n*n^4), where n is the length of the input. I have trouble understanding except the n^4, where the remaining extra n comes from.
My analysis of my code is: T(n) = O(1) + 4T(n-1). (The for loop is repeated for 4 times which length decremented by 1. And in the loop constant time is required for update the prefix String.)
It solves to 1 + 4 + 4^1+ ... + 4^n = O(4^n)
Can anyone help with why the solution says the time complexity is O(n*4^n)?

I agree with you, the time complexity should be O(4^n).
I have several ideas why it can be O(n * 4^n):
StringBuilder's append method can take O(n) time when its capacity reaches threshold, which means copying all elements to a new array. But in your case maximum length of the resulting string is 4 (from the problem constraints), whereas the default value of the threshold is 16 (https://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html#StringBuilder-java.lang.String-). Since 4 < 16, it always takes O(1) time.
StringBuilder's deleteCharAt method takes O(n) time in worst case, because of array copy. But in your case, you are removing only last character, which takes O(1) time.
Used String instead of StringBuilder, where concatenation and deletion with one element takes O(n) time

well I don't know very well Java but in python the reason is O(n4^n)
is because the concatenation takes O(n) in youtube a there is youtuber who says is because of the height of the tree (meaning n is the height of the tree) but i think he is wrong because that does not make sense (atleast for me)
note O(3m4k*n) is also valid(m the number of digits with 3 possibilities and k the number of digits with 4 possibilities)

Its because the width of the tree is O(4^n) and the height of the tree is O(n). Watch this video for a better explanation than I can give: https://www.youtube.com/watch?v=0snEunUacZY

Related

Edit distance leetcode

So I am doing this question of EDIT DISTANCE and before going to DP approach I am trying to solve this question in recursive manner and I am facing some logical error, please help....
Here is my code -
class Solution {
public int minDistance(String word1, String word2) {
int n=word1.length();
int m=word2.length();
if(m<n)
return Solve(word1,word2,n,m);
else
return Solve(word2,word1,m,n);
}
private int Solve(String word1,String word2,int n,int m){
if(n==0||m==0)
return Math.abs(n-m);
if(word1.charAt(n-1)==word2.charAt(m-1))
return 0+Solve(word1,word2,n-1,m-1);
else{
//insert
int insert = 1+Solve(word1,word2,n-1,m);
//replace
int replace = 1+Solve(word1,word2,n-1,m-1);
//delete
int delete = 1+Solve(word1,word2,n-1,m);
int max1 = Math.min(insert,replace);
return Math.min(max1,delete);
}
}
}
here I am checking the last element of both the strings if both the characters are equal then simple moving both string to n-1 and m-1 resp.
Else
Now I am having 3 cases of insertion , deletion and replace ,and between these 3 I have to find minima.
If I am replacing the character then simply I moved the character to n-1 & m-1.
If I am inserting the character from my logic I think I should insert the character at the last of smaller length string and move the pointer to n-1 and m
To delete the element I think I should delete the element from the larger length String that's why I move pointer to n-1 and m but I think I am making mistake here please help.
Leetcode is giving me wrong answer for word1 = "plasma" and word2 = "altruism".
The problem is that the recursive expression for the insert-case is the same as for the delete-case.
Reasoning further, it turns out the one for the insert-case is wrong. In that case we choose to resolve the letter in word2 (at index m-1) through insertion, so it should not be considered any more during the recursive process. On the other hand the considered letter in word1 could still be matched with another letter in word2, so that letter should still be considered during the recursive process.
That means that m should be decremented, not n.
So change:
int insert = 1+Solve(word1,word2,n-1,m);
to:
int insert = 1+Solve(word1,word2,n,m-1);
...and it will work. Then remains to add the memoization for getting a good efficiency.
Python clean DP based solution,
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
return self.edit_distance(word1, word2)
#cache
def edit_distance(self, s, t):
# Edge conditions
if len(s) == 0:
return len(t)
if len(t) == 0:
return len(s)
# If 1st char matches
if s[0] == t[0]:
return self.edit_distance(s[1:], t[1:])
else:
return min(
1 + self.edit_distance(s[1:], t), # delete
1 + self.edit_distance(s, t[1:]), # insert
1 + self.edit_distance(s[1:], t[1:]) # replace
)

Unable to understand how this recursive function evaluates

Please help me understand how the following code always returns the smallest value in the array. I tried moving position of 3 but it always manages to return it irrespective of the position of it in the array.
let myA = [12,3,8,5]
let myN = 4
function F4(A,N)
{
if(N==1){
return A[0]
}
if(F4(A,N-1) < A[N-1]){
return F4(A,N-1)
}
return A[N-1]
}
console.log(F4(myA,myN))
This is quite tricky to get an intuition for. It's also quite important that you learn the process for tackling this type of problem rather than simply be told the answer.
If we take a first view of the code with a few comments and named variables it looks like this:
let myA = [12,3,8,5];
let myN = myA.length;
function F4(A, N) {
// if (once) there is only one element in the array "A", then it must be the minimum, do not recurse
if (N === 1){
return A[0]
}
const valueFromArrayLessLastEl = F4(A,N-1); // Goes 'into' array
const valueOfLastElement = A[N-1];
console.log(valueFromArrayLessLastEl, valueOfLastElement);
// note that the recursion happens before min(a, b) is evaluated so array is evaluated from the start
if (valueFromArrayLessLastEl < valueOfLastElement) {
return valueFromArrayLessLastEl;
}
return valueOfLastElement;
}
console.log(F4(myA, myN))
and produces
12 3 // recursed all the way down
3 8 // stepping back up with result from most inner/lowest recursion
3 5
3
but in order to gain insight it is vital that you approach the problem by considering the simplest cases and expand from there. What happens if we write the code for the cases of N = 1 and N = 2:
// trivially take N=1
function F1(A) {
return A[0];
}
// take N=2
function F2(A) {
const f1Val = F1(A); // N-1 = 1
const lastVal = A[1];
// return the minimum of the first element and the 2nd or last element
if (f1Val < lastVal) {
return f1Val;
}
return lastVal;
}
Please note that the array is not being modified, I speak as though it is because the value of N is decremented on each recursion.
With myA = [12, 3, 8, 5] F1 will always return 12. F2 will compare this value 12 with 3, the nth-1 element's value, and return the minimum.
If you can build on this to work out what F3 would do then you can extrapolate from there.
Play around with this, reordering the values in myA, but crucially look at the output as you increase N from 1 to 4.
As a side note: by moving the recursive call F4(A,N-1) to a local constant I've prevented it being called twice with the same values.

How to find a pair of numbers in a list given a specific range?

The problem is as such:
given an array of N numbers, find two numbers in the array such that they will have a range(max - min) value of K.
for example:
input:
5 3
25 9 1 6 8
output:
9 6
So far, what i've tried is first sorting the array and then finding two complementary numbers using a nested loop. However, because this is a sort of brute force method, I don't think it is as efficient as other possible ways.
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), k = sc.nextInt();
int[] arr = new int[n];
for(int i = 0; i < n; i++) {
arr[i] = sc.nextInt();
}
Arrays.sort(arr);
int count = 0;
int a, b;
for(int i = 0; i < n; i++) {
for(int j = i; j < n; j++) {
if(Math.max(arr[i], arr[j]) - Math.min(arr[i], arr[j]) == k) {
a = arr[i];
b = arr[j];
}
}
}
System.out.println(a + " " + b);
}
}
Much appreciated if the solution was in code (any language).
Here is code in Python 3 that solves your problem. This should be easy to understand, even if you do not know Python.
This routine uses your idea of sorting the array, but I use two variables left and right (which define two places in the array) where each makes just one pass through the array. So other than the sort, the time efficiency of my code is O(N). The sort makes the entire routine O(N log N). This is better than your code, which is O(N^2).
I never use the inputted value of N, since Python can easily handle the actual size of the array. I add a sentinel value to the end of the array to make the inner short loops simpler and quicker. This involves another pass through the array to calculate the sentinel value, but this adds little to the running time. It is possible to reduce the number of array accesses, at the cost of a few more lines of code--I'll leave that to you. I added input prompts to aid my testing--you can remove those to make my results closer to what you seem to want. My code prints the larger of the two numbers first, then the smaller, which matches your sample output. But you may have wanted the order of the two numbers to match the order in the original, un-sorted array--if that is the case, I'll let you handle that as well (I see multiple ways to do that).
# Get input
N, K = [int(s) for s in input('Input N and K: ').split()]
arr = [int(s) for s in input('Input the array: ').split()]
arr.sort()
sentinel = max(arr) + K + 2
arr.append(sentinel)
left = right = 0
while arr[right] < sentinel:
# Move the right index until the difference is too large
while arr[right] - arr[left] < K:
right += 1
# Move the left index until the difference is too small
while arr[right] - arr[left] > K:
left += 1
# Check if we are done
if arr[right] - arr[left] == K:
print(arr[right], arr[left])
break

Course Scheduling from Leetcode

This is my solution to Course Scheduling Problem from leetcode. I am looking for any suggestions to improve my code, even slightest ones.
Here is the question:
There are a total of n courses you have to take, labeled from 0 to n-1.
Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]
Given the total number of courses and a list of prerequisite pairs, return the ordering of courses you should take to finish all courses.
There may be multiple correct orders, you just need to return one of them. If it is impossible to finish all courses, return an empty array.
Example 1:
Input: 2, [[1,0]]
Output: [0,1]
Explanation: There are a total of 2 courses to take. To take course 1 you should have finished course 0. So the correct course order is [0,1] .
Example 2:
Input: 4, [[1,0],[2,0],[3,1],[3,2]]
Output: [0,1,2,3] or [0,2,1,3]
Explanation: There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]. Another correct ordering is [0,2,1,3].
Here is my solution:
class Solution:
def findOrder(self, numCourses, prerequisites):
"""
:type numCourses: int
:type prerequisites: List[List[int]]
:rtype: bool
"""
#Convert prerequisites into an adjacency list
adj = []
for i in range(numCourses):
adj.append(set())
for pair in prerequisites:
adj[pair[0]].add(pair[1])
def DFSHelper(s):
visited.add(s)
stack.add(s)
for neighbor in adj[s]:
# if neighbor vertex has never been visted before, there is no way it could be a backedge.
# visit this unvisited vertex
if(neighbor not in visited):
if(not DFSHelper(neighbor)):
return False
Sorted.append(neighbor)
else:
if(neighbor in stack):
return False
stack.remove(s)
return True
visited = set()
stack = set()
Sorted = []
for j in range(len(adj)):
if(j not in visited):
if(not DFSHelper(j)):
print(j)
return []
Sorted.append(j)
return Sorted
I first converted given prerequisites list into an adjacency list representation of graph, then did topological sorting of the graph. I used DFS recursively to topologically sort the graph. The list Sorted stores the result of sorting. While doing DFS I also checked if the graph contains any cycle, if it does just return []. For purpose of checking cycle I maintained a set called stack that stores all the vertices that are currently in call stack.
This is a simple question first create a graph and then find topological sorting on nodes.
If topological order contains all nodes then we have our answer else not possible to finish all the courses.
class Solution {
public int[] findOrder(int n, int[][] prerequisites) {
List<Integer>[] g = new ArrayList[n];
for(int i = 0; i < n; i++)g[i] = new ArrayList();
int[] deg = new int[n];
for(int[] e: prerequisites) {
g[e[1]].add(e[0]);
deg[e[0]]++;
}
Queue<Integer> q = new LinkedList();
for(int i = 0; i < n; i++) {
if(deg[i] == 0)q.add(i);
}
int[] res = new int[n];
int idx = 0;
while(!q.isEmpty()) {
int u = q.poll();
res[idx++] = u;
for(int v: g[u]) {
deg[v]--;
if(deg[v] == 0) q.add(v);
}
}
return idx == n ? res: new int[0];
}}

Extracting minimum of vector

I'm extracting the min from a vector.
Say vector = [0, inf, inf, inf];
ExtractSmallest(vector) = 0;
and then vector = [0, 1, inf, inf];
but now, we've already seen 0. Thus,
ExtractSmallest(vector) = 1;
I represent this in my code by doing nodes.erase(nodes.begin() + smallestPosition);
But, I now realize that erasing is very bad. Is there a way to achieve this without erasing the vectors? Just skipping over the ones we've already seen?
Node* CGraph::ExtractSmallest(vector<Node*>& nodes)
{
int size = nodes.size();
if (size == 0) return NULL;
int smallestPosition = 0;
Node* smallest = nodes.at(0);
for (int i=1; i<size; ++i)
{
Node* current = nodes.at(i);
if (current->distanceFromStart <
smallest->distanceFromStart)
{
smallest = current;
smallestPosition = i;
}
}
nodes.erase(nodes.begin() + smallestPosition);
return smallest;
}
Option 1 You can have an additional vector<bool> on which you iterate in parallel. When you find the smallest element, mark that position in the bool vector as true. Whenever you iterate, skip the positions in both vectors that are marked as true.
Option 2 If order is not important, keep the number of elements removed so far. When you find the minimum, swap positions with the first non-excluded element. On a new iteration, start from the first non-excluded element.
Option 3 If order is not important, sort the array. (this takes O(n*log(n))). Removal will now take O(1) - you just exclude the first non-excluded element.
Option 4 If there are no duplicates, you can keep a std::set on the side with all excluded elements to this point. When you iterate, check whether the current element was already excluded or not.

Resources