0

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 integer n (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!

Nicolas Filotto
  • 43,537
  • 11
  • 94
  • 122
M. Lee
  • 125
  • 2
  • 6
  • 1
    If you want to stick with recursive (because it might be the actual assignment) you need to move the terminal conditions inside the recursion method. And instead of increasing i you would decrease n at lower levels. – eckes Sep 13 '16 at 18:20
  • 1
    See [this excellent answer](http://stackoverflow.com/a/7249552) to the question: [Determining the individual letters of Fibonacci strings?](http://stackoverflow.com/q/4896720) –  Sep 13 '16 at 23:28

5 Answers5

2

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.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • I feel that this is a REALLY important point to the original question... although it doesn't really constitute much of an answer at this point. However, yes, this is a very good point; given the definition of how your recursion needs to work, you DO NOT want to be doing this with string concatenation. – nasukkin Sep 13 '16 at 18:22
  • I did try StringBuilder just now and ran into the same problem where, as you mentioned, the input of 60 will output too many characters. The deal is, however, the output has to be a standard output to the screen because that was the format requested. – M. Lee Sep 13 '16 at 19:20
  • @M.Lee Then you should be able to code like this:[link](http://ideone.com/Eh54OJ). – Sergey Kalinichenko Sep 13 '16 at 19:28
0

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.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
0

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

ΦXocę 웃 Пepeúpa ツ
  • 47,427
  • 17
  • 69
  • 97
0

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.

nasukkin
  • 2,460
  • 1
  • 12
  • 19
0

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)

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
  • Tried running this, still the same problem where too many output characters using the standard System.out when the input 'n' is something large such as 60. – M. Lee Sep 13 '16 at 19:22
  • I printed 40 to my IDE console and it did slow it down. I assume `n` of 60 would be even worse. There probably is a smarter way to handle certain numbers – OneCricketeer Sep 13 '16 at 19:27
  • And that smarter way very well could be only compute the string up to character `k`. – OneCricketeer Sep 13 '16 at 19:29