Recursive String Concatenation

I was working on a question that requires a concatenation of strings recursively and ran into a problem.

Question states that s(0) = 0, s(1) = 1, s(n) = s(n-1)s(n-2) for n >= 2, where s(n) is the concatenated string of the previous two strings.

Input will indicate how many instances of (n, k) pair will be input as the first integer, followed by each line containing a non-negative integern (0 <= n <= 60) and a positive integer k.

Output is supposed to be printing out the kth character of the concatenated string s(n), where k is less or equal to the number of characters in string s(n).

s(0) = 0
s(1) = 1
s(2) = 10
s(3) = 101
s(4) = 10110
s(5) = 10110101
and so on.

Sample input:

3
5 2
0 1
4 3

Output:

0
0
1

My code:

import java.util.*;
public class recursivestring { public static String recursive(int n, int i, String str1, String str2){ if (i == n - 1) return str1 + str2; return recursive(n, i + 1 , str1 + str2, str1); } public static void main(String[] args) { int lines, i, n, k; String result; Scanner input = new Scanner(System.in); lines = input.nextInt(); for (i = 0; i < lines; i++) { n = input.nextInt(); k = input.nextInt(); if (n == 0) { result = "0"; } else if (n == 1) { result = "1"; } else if (n == 2) { result = "10"; } else { result = recursive(n, 2, "10", "1"); } System.out.println(result.charAt(k-1)); } }
}

This is what I have so far, and it works for the given sample test case. It works for most cases but once n becomes large, I get this error

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

Why is that happening and is there something wrong with my code?

Thank you!

2

5 Answers

The problem with your approach is that it creates too many throw-away strings. Each time you write

return str1 + str2;

a new String object is created. The number of such throw-away objects grows linearly with n, while their total length grows as O(n2).

You have two solutions to this problem:

  • Keep your program linear, and pass StringBuilder at the top level. Each recursive invocation would call append, rather than using operator + for concatenation.
  • Use Memoization - since the number of strings that you need to compute is small, storing the ones that you computed so far and re-using them should fix the problem.

A bigger issue with your problem limits is that its output cannot fit in a String: Java allows strings of up to 231 in length, while the output of your code for 60 is quite a bit longer - namely, 1,548,008,755,920 characters. While you should be able to save this output in a file, there is no way to store it as a String, with or without memoization.

3

Since you're doing this in Java and not in an FP language with tail call optimization, two things are wrong: recursion and string concatenation. In Java you would to iteration and string building with a mutable builder.

Specifically, your recursive function retains all the interim string you produce on your way to the full string. That's O(n2) memory usage.

Well, to be honest this looks like fibonacci to me so me approach looks a little like that...

public static void main(String[] args) {
for (int i = 0; i < 6; i++) { System.out.println(stringbonacci(i));
}
}
private static String stringbonacci(int i) {
if (i == 0) { return "0";
}
if (i == 1) { return "1";
} else { return stringbonacci(i - 1) + stringbonacci(i - 2);
}
}

and the result looks like:

0

1

10

101

10110

10110101

3

I highly recommend caching the result of each method call so that you don't have to re-calculate everything more than once. Will greatly improve your performance at the cost of a small bit of heap. Something like... this?

public class RecursiveString { private static final Map<Integer, String> cache = new HashMap<>(); static { cache.put(0, "0"); cache.put(1, "1"); } public static String generateString(Integer i) { if (i < 0) return generateString(0); // or something... avoid negatives. if (cache.get(i) != null) return cache.get(i); // cache hit. String generated = String.format("%s%s", generateString(i-1), generateString(i-2)); cache.put(i, generated); return generated; }
}

Note that on a 2G heap, generateString works up through i=40 on my machine, simply due to the length of the resulting string. The string's length is going to be 2*fib(i) bytes in size, so your memory requirement is going to explode pretty quickly once you start hitting those indexes.

2

Same idea as the answer with the map (store the intermediate string, don't recompute), but using an array instead.

This could be optimized a little more by reading all the lines, then finding the maximum n value, thereby storing only one array, then looping back over the (n, k) pairs.

import java.util.Scanner;
public class BinaryConcat { private String[] dict; public BinaryConcat(int n) { if (n < 0) throw new IllegalArgumentException(); dict = new String[2 + n]; // Have 2 base cases dict[0] = "0"; dict[1] = "1"; } public String getValue(int n) { if (n < 0) throw new IllegalArgumentException(); if (n <= 1) return dict[n]; if (dict[n] == null || dict[n].isEmpty()) { dict[n] = String.format("%s%s", getValue(n - 1), getValue(n - 2)); } return dict[n]; } public static void main(String[] args) { Scanner input = new Scanner(System.in); int lines = input.nextInt(); input.nextLine(); // consume linefeed for (int i = 0; i < lines; i++) { String line = input.nextLine(); String[] nk = line.split(" "); int n = Integer.parseInt(nk[0]); int k = Integer.parseInt(nk[1]); String value = new BinaryConcat(n).getValue(n); System.out.println(value.charAt(k - 1)); } }
}

Sample run

(same input and output as expected)

3

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like