What's the space complexity of this permutations algorithm? - algorithm

The time complexity of this algorithm to compute permutations recursively should be O(n!*n), but I am not 100% sure about the space complexity.
There are n recursions, and the biggest space required for a recursion is n (space of every permutation * n! (number of permutations). Is the space complexity of the algorithm` O(n!*n^2)?
static List<String> permutations(String word) {
if (word.length() == 1)
return Arrays.asList(word);
String firstCharacter = word.substring(0, 1);
String rest = word.substring(1);
List<String> permutationsOfRest = permutations(rest);
List<String> permutations = new ArrayList<String>(); //or hashset if I don’t want duplicates
for (String permutationOfRest : permutationsOfRest) {
for (int i = 0; i <= permutationOfRest.length(); i++) {
permutations.add(permutationOfRest.substring(0, i) + firstCharacter + permutationOfRest.substring(i));
}
}
return permutations;
}

No, the space complexity is "just" O(n! × n), since you don't simultaneously hold onto all recursive calls' permutationsOfRest / permutations. (You do have two at a time, but that's just a constant factor, so isn't relevant to the asymptotic complexity.)
Note that if you don't actually need a List<String>, it might be better to wrap things up as a custom Iterator<String> implementation, so that you don't need to keep all permutations in memory at once, and don't need to pre-calculate all permutations before you start doing anything with any of them. (Of course, that's a bit trickier to implement, so it's not worth it if the major use of the Iterator<String> will just be to pre-populate a List<String> anyway.)

Related

Worst-case time complexity for adding two binary numbers using bit manipulation

I'm looking over the solution to adding 2 binary numbers, using the bit manipulation approach. The input are 2 binary-number strings, and the expected output is the string for the binary addition result.
Here is the Java code similar to the solution,
class BinaryAddition {
public String addBinary(String a, String b) {
BigInteger x = new BigInteger(a, 2);
BigInteger y = new BigInteger(b, 2);
BigInteger zero = new BigInteger("0", 2);
BigInteger carry, answer;
while (y.compareTo(zero) != 0) {
answer = x.xor(y);
carry = x.and(y).shiftLeft(1);
x = answer;
y = carry;
}
return x.toString(2);
}
}
While the algorithm makes a lot of sense, I'm somehow arriving at a different worst-case time complexity than the O(M + N) stated in the Leetcode solution, where M, N refers to the lengths of input strings.
In my opinion, it should be O(max(M, N)^2), since the while loop can run up to max(M, N) times, and each iteration can take max(M, N) operations. For example, adding 1111 and 1001 would take 4 iterations of the while loop.
Appreciate your advice on this or where I might have gone wrong, thanks.

Time Complexity of this Word Break DFS + Memorization solution

when dealing with wordBreak problem, I found this solution is really concise. But not sure about the time complexity. anyone can help?
my understanding is worst case, O(n*k), n is the size of the wordDict, and k is the length of the String.
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
return wordBreak(s, wordDict, new HashMap<String, Boolean>());
}
private boolean wordBreak(String s, List<String> wordDict, Map<String, Boolean> memo) {
if (s == null) return false;
if (s.isEmpty()) return true;
if (memo.containsKey(s)) return memo.get(s);
for (String dict : wordDict) { //number of words O(n)
//startsWith is bounded by the length of dict word, avg is O(m), can be ignored
//substring is bounded by the length of dict word, avg is O(k), k is the length of s
//wordBreak will be executed k/m times, k is the length of s, worse case k times... when a single letter is in the dict
if (s.startsWith(dict) && wordBreak(s.substring(dict.length()), wordDict, memo)) {
memo.put(s, true);
return true;
}
}
memo.put(s, false);
return false;
}
}
It's worse than O(nk) for several reasons:
You ignore "m", but m is Omega(log k). (Because k < |A|^(m+1) where |A| is the size of your alphabet).
s.substring is probably O(n). Your code looks like Java, and it's O(n) in Java.
Even if s.substring were linear, Your Map requires the string to be hashed, so your map operations are O(n) (where note carefully -- n is the size of the string rather than the size of the hashtable like it would normally be).
Probably this means you have complexity O(n^2k*logk).
You can fix 3 easily -- you can use s.length rather than s as the key to your hashtable.
Problem 2 is easy but slightly annoying to fix -- rather than slicing your string, you can use a variable that indexes into the string. You probably have to re-write startsWith yourself to using this index (or use a trie -- see below). If your programming language has an O(1) slice operation (for example, string_view in C++) then you could use that instead.
Problem 1 is only theoretical, since for real word lists, m is really small compared to either the length of the dictionary or the potential length of input strings.
Note that using a trie for the dictionary rather than a word list is likely to result in a huge time improvement, with realistic examples being linear excluding dictionary construction (although worst-case examples where the dictionary and input strings are chosen maliciously will be O(nk)).

Which of the following two implementation of string permutation algorithm is better in terms of time and space complexity?

Problem Statement: Given a string, write a program which finds all the permutations of the string.
I have implemented it in two different ways, and according to my (possibly erroneous) time complexity analysis the second one does lesser computations, yet in real-time performance, the first one outperforms the second one.
Questions:
Is my time analysis correct? Why does the computational performance differ?
Which of the implementations would be better in terms of space requirements?
In the recursive calls, I am returning immutable datatypes instead of using an mutable accumulator (I read somewhere that this is good practice). Is this advisable, or could better performance be achieved (in space or time) by using a mutable variable or static variable?
Instead of returning List, or would it better to wrap things up as a Iterator? I read somewhere that might be better as so that you don't need to keep all permutations in memory at once, and don't need to pre-calculate all permutations before you start doing anything with any of them. How would the code look then?
Could I optimize it further by using StringBuffer or StringBuilder instead of Strings? I think the compiler automatically implements the concatenations in using StringBuilder, so is this fine?
Any other suggestions to improve the code?
The first version:
private static List<String> permuteOne (String partial, String rest) {
List<String> result = new ArrayList<>();
if (rest.length() == 1) {
result.add(partial + rest);
} else {
for (int j = 0; j < rest.length(); j++) {;
result.addAll(permuteOne(partial + rest.charAt(j), rest.substring(0,j) + rest.substring(j+1)));
}
}
return Collections.unmodifiableList(result);
}
Time complexity T(n) = n * T(n-1)
numOperations(firstVersion) = n + n(n-1) + n(n-1)(n-2) + ...n!
Space complexity: ??
The Second Version:
private static List<String> permuteTwo (String remaining) {
List<String> newList = new ArrayList<>();
if (remaining.length() == 1) {
newList.add(remaining);
} else {
List<String> partialPerms = permuteTwo(remaining.substring(0, remaining.length()-1));
for(String permutation: partialPerms )
for(int j = 0; j <= permutation.length(); j++){
newList.add(permutation.substring(0, j) + remaining.charAt(remaining.length()-1) + permutation.substring(j));
}
}
return newList;
}
Time complexity T(n) = n! + T(n-1)
numOperations(secondVersion) = 1! + 2! + 3! + ...n! < n + n(n-1) + n(n-1)(n-2) + ...n! = numOperations(firstVersion)
Space Complexity: n*n!? <- Not sure of this

What is Big O complexity of K-Complementary algorithm below?

I'm implementing efficient algorithm to find K-complementary pairs of numbers in given A array.
I intended to implement O(n) algorithm. And it is O(n) for sure when all numbers in A array are different. However I wonder if it is still O(n) if numbers in A array may be equal, i.e. like in test provided below, where all elements are equal 1. In this test where 3 elements provided we clearly see that we go through external loop 3 times and internal 3 times. However, if A table has n elements where n belongs to set of numbers, than it is untrue that all elements are equal.
That's why I believe complexity of this inner loop is reduced to O(1).
UPDATED
Inspired but not fully satisfied with answers to my question I did some deeper research about complexity notation definitions and discovered I wasn't precise with my calculations above.
f(n) = O(g(n)) means there are positive constants c and k, such that 0 ≤ f(n) ≤ cg(n) for all n ≥ k. The values of c and k must be fixed for the function f and must not depend on n.
By definition Big-O gives asymptotic upper bound. It means that if we have worst-case with internal for loop which potentially iterate through n elements than complexity is O(n^2). Even if statistically it is very rare scenario we cannot say that complexity is less than O(n^2). However, what is interesting, if complexity O(n^2) true than O(n^3) is true by definition as well. Big-O
Moreover, it is also correct to say that Big-Ω complexity for this algorithm is Ω(n). We use big-Ω notation for asymptotic lower bounds, since it bounds the growth of the running time from below for large enough input sizes. Big-Ω
To the best of my knowledge we cannot calculate Big-θ until we tighten Big-O and Big-Ω to the same value.
It might be worth to notice that algorithm below would be linear if it only checked that array contains Complementary Pair. But complexity grows to O(n^2) when it collects all complementary pairs. I watched Google interview presentation with hope to get more clues about topic but they simplified problem to "hasComplementaryPair" function.
Next step about ComplementaryPair algorithm is to find different, faster algorithm, or prove that it can't be done faster.
End of UPDATED
public class ComplementaryPairs {
public Set<Pair<Integer, Integer>> process(Integer[] A, Integer k) {
Set<Pair<Integer, Integer>> pairs = new HashSet<>();
if (A == null) {
return pairs;
}
/*
* 1. Build differential map.
* < k - A[i], i >
*/
Map<Integer, List<Integer>> map = new HashMap<>();
for (int i = 0; i < A.length; i++) {
put(map, k - A[i], i);
}
/*
* 2. Collect pairs
*/
for (int j = 0; j < A.length; j++) {
if (map.containsKey(A[j])) {
/*
* I've wondered if this loop spoils O(n) complexity,
* because in scenario where i.e. we have 10 elements in A and every element is the same,
* we have to go through 10 elements in external loop and 10 elements in List in HashMap.
*
* However, if A table has n elements where n belongs to set of numbers, than situation described above is impossible.
* In other words I believe complexity of this inner used to be counted as O(1)
*
*/
for (Integer iIndex : map.get(A[j])) {
pairs.add(new Pair<>(j, iIndex));
}
}
}
return pairs;
}
private void put(Map<Integer, List<Integer>> map, Integer key, Integer value) {
if (map.containsKey(key)) {
map.get(key).add(value);
} else {
/* This may be improved, so List is not created for one element only */
List<Integer> list = new LinkedList<>();
list.add(value);
map.put(key, list);
}
}
}
Consider test below
#Test
public void testWhenArrayContainElementsOfEqualValue() {
// prepare
Integer[] A = {1, 1, 1};
Integer k = 2;
// execute
Set<Pair<Integer, Integer>> resultSet = complementaryPairs.process(A, k);
System.out.println(resultSet);
// assert
assertTrue(resultSet.contains(new Pair<>(0, 0)));
assertTrue(resultSet.contains(new Pair<>(0, 1)));
assertTrue(resultSet.contains(new Pair<>(0, 2)));
assertTrue(resultSet.contains(new Pair<>(1, 0)));
assertTrue(resultSet.contains(new Pair<>(1, 1)));
assertTrue(resultSet.contains(new Pair<>(1, 2)));
assertTrue(resultSet.contains(new Pair<>(2, 0)));
assertTrue(resultSet.contains(new Pair<>(2, 1)));
assertTrue(resultSet.contains(new Pair<>(2, 2)));
}
Have you read this? https://codereview.stackexchange.com/a/145076
Creating the map is o(n) run time but when you write:
for (int j = 0; j < A.length; j++) {
if (map.containsKey(A[j])) {
for (Integer iIndex : map.get(A[j])) {
pairs.add(new Pair<>(j, iIndex));
}
}
}
You have a potential complexity of o(n^2). You should try to avoid that and
then the time complexity is clear.
As for the code, the map will contain key only when there exists i such that k-A[i]==key.
Example:
A=[0,0,0,0,0,0], k=0
Since key 0 has all elements o the array and A[i]=0 for all i for the following example all i satisfy the condition.
So n^2 is the run time. Big o complexity is o(n^2).
And now some duplications problems are appearing.

Create given string from dictionary entries

During a recent job interview, I was asked to give a solution to the following problem:
Given a string s (without spaces) and a dictionary, return the words in the dictionary that compose the string.
For example, s= peachpie, dic= {peach, pie}, result={peach, pie}.
I will ask the the decision variation of this problem:
if s can be composed of words in the
dictionary return yes, otherwise
return no.
My solution to this was in backtracking (written in Java)
public static boolean words(String s, Set<String> dictionary)
{
if ("".equals(s))
return true;
for (int i=0; i <= s.length(); i++)
{
String pre = prefix(s,i); // returns s[0..i-1]
String suf = suffix(s,i); // returns s[i..s.len]
if (dictionary.contains(pre) && words(suf, dictionary))
return true;
}
return false;
}
public static void main(String[] args) {
Set<String> dic = new HashSet<String>();
dic.add("peach");
dic.add("pie");
dic.add("1");
System.out.println(words("peachpie1", dic)); // true
System.out.println(words("peachpie2", dic)); // false
}
What is the time complexity of this solution?
I'm calling recursively in the for loop, but only for the prefix's that are in the dictionary.
Any idea's?
You can easily create a case where program takes at least exponential time to complete. Let's just take a word aaa...aaab, where a is repeated n times. Dictionary will contain only two words, a and aa.
b in the end ensure that function never finds a match and thus never exits prematurely.
On each words execution, two recursive calls will be spawned: with suffix(s, 1) and suffix(s, 2). Execution time, therefore, grows like fibonacci numbers: t(n) = t(n - 1) + t(n - 2). (You can verify it by inserting a counter.) So, complexity is certainly not polynomial. (and this is not even the worst possible input)
But you can easily improve your solution with Memoization. Notice, that output of function words depends on one thing only: at which position in original string we're starting. E.e., if we have a string abcdefg and words(5) is called, it doesn't matter how exactly abcde is composed (as ab+c+de or a+b+c+d+e or something else). Thus, we don't have to recalculate words("fg") each time.
In the primitive version, this can be done like this
public static boolean words(String s, Set<String> dictionary) {
if (processed.contains(s)) {
// we've already processed string 's' with no luck
return false;
}
// your normal computations
// ...
// if no match found, add 's' to the list of checked inputs
processed.add(s);
return false;
}
PS Still, I do encourage you to change words(String) to words(int). This way you'll be able to store results in array and even transform the whole algorithm to DP (which would make it much simpler).
edit 2
Since I have not much to do besides work, here's the DP (dynamic programming) solution. Same idea as above.
String s = "peachpie1";
int n = s.length();
boolean[] a = new boolean[n + 1];
// a[i] tells whether s[i..n-1] can be composed from words in the dictionary
a[n] = true; // always can compose empty string
for (int start = n - 1; start >= 0; --start) {
for (String word : dictionary) {
if (start + word.length() <= n && a[start + word.length()]) {
// check if 'word' is a prefix of s[start..n-1]
String test = s.substring(start, start + word.length());
if (test.equals(word)) {
a[start] = true;
break;
}
}
}
}
System.out.println(a[0]);
Here's a dynamic programming solution that counts the total number of ways to decompose the string into words. It solves your original problem, since the string is decomposable if the number of decompositions is positive.
def count_decompositions(dictionary, word):
n = len(word)
results = [1] + [0] * n
for i in xrange(1, n + 1):
for j in xrange(i):
if word[n - i:n - j] in dictionary:
results[i] += results[j]
return results[n]
Storage O(n), and running time O(n^2).
The loop on all the string will take n. Finding all suffixes and prefixes will take n + (n - 1) + (n - 2) + .... + 1 (n for first call of words, (n - 1) for second and so on), which is
n^2 - SUM(1..n) = n^2 - (n^2 + n)/2 = n^2 / 2 - n / 2
which in complexity theory is equivalent to n^2.
Checking for existence in HashSet in normal case is Theta(1), but in worst case it is O(n).
So, normal case complexity of your algorithm is Theta(n^2), and worst case - O(n^3).
EDIT: I confused order of recursion and iteration, so this answer is wrong. Actually time depends on n exponentially (compare with computation of Fibonacci numbers, for example).
More interesting thing is the question how to improve your algorithm. Traditionally for string operations suffix tree is used. You can build suffix tree with your string and mark all the nodes as "untracked" at the start of the algo. Then go through the strings in a set and each time some node is used, mark it as "tracked". If all strings in the set are found in the tree, it will mean, that the original string contains all the substrings from set. And if all the nodes are marked as tracked, it will mean, that string consists only of substring from set.
Actual complexity of this approach depends on many factors like tree building algorithm, but at least it allows to divide the problem into several independent subtasks and so measure final complexity by complexity of the most expensive subtask.

Resources