Recursive String Concatenation - java

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!

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.

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

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.

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)

Related

Is there a way to reverse a string faster than O(n)?

I have the following code which takes more than 5 seconds to run with the argument -Xmx<1024M>.
I am aware that the for loop takes O(q) time, as well as the reverse() and toString() take O(n) time each.
Is there a way to reverse the string in less than O(n) time? Or is something else slowing the code down? Any help would be welcome!
class Main {
public static void main(String[] args){
String s = "a";
String qa = "200000";
int q = Integer.parseInt(qa);
String[] t = new String[q];
for(int i = 0; i < q; i++) {
if(i%2==0) {t[i] = "2 1 x";}
if(i%2==1) {t[i] = "1";}
if(t[i].toCharArray()[0] == '1') {
StringBuilder rev = new StringBuilder(s).reverse();
s = rev.toString();
} else {
char letter = t[i].toCharArray()[4];
if(t[i].toCharArray()[2] == '1') {
s = letter + s;
} else {
s = s + letter;
}
}
}
System.out.println(s);
}
}
Regardless of what is it supposed to do (I have no idea), I found the following problems:
Multiple instantinations of StringBuilder in each iteration.
String concatenation using + operator.
Repetitive usage of Sring::toCharArray (see the 2nd solution)
You will achieve a faster result using directly only one instance of StringBuilder:
String s = "a";
String qa = "200000";
int q = Integer.parseInt(qa);
String[] t = new String[q];
StringBuilder sb = new StringBuilder(s); // Instantiate before the loop
for (int i = 0; i < q; i++) {
if(i%2==0) {t[i] = "2 1 x";}
if(i%2==1) {t[i] = "1";}
if(t[i].toCharArray()[0] == '1') {
sb.reverse(); // all you did here is just reversing 's'
} else {
char letter = t[i].toCharArray()[4];
if(t[i].toCharArray()[2] == '1') {
sb.insert(0, letter); // prepend a letter
} else {
sb.append(letter); // append a letter
}
}
}
Another thing is that you multiple times define a String such as t[i] = "2 1 x"; and then you compare with t[i].toCharArray()[0]. Pre-definig these immutable values and using char[][] should help too:
String s = "a";
String qa = "200000";
int q = Integer.parseInt(qa);
char[][] t = new char[q][]; // char[][] instead of String[]
char[] char21x = new char[]{'2', '1', 'x'}; // predefined array
char[] char1 = new char[]{'1'}; // another predefined array
StringBuilder sb = new StringBuilder(s); // Instantiate before the loop
for (int i = 0; i < q; i++) {
if(i%2==0) {t[i] = char21x;} // first reuse
if(i%2==1) {t[i] = char1;} // second reuse
if(t[i][0] == '1') { // instead of String::toCharArray, mind the indices
sb.reverse(); // all you did here is just reversing 's'
} else {
char letter = t[i][2]; // instead of String::toCharArray, mind the indices
if(t[i][1] == '1') {
sb.insert(0, letter); // prepend a letter
} else {
sb.append(letter); // append a letter
}
}
}
Edit: I have tested the solution with the simplest way possible using a difference of System.currentTimeMillis() on my laptop:
Original solution: 7.658, 6.899 and 7.046 seconds
2nd solution: 3.288, 3.691 and 3.158 seconds
3rd solution: 2.717, 2.966 and 2.717 seconds
Conclusion: I see no way to improve the algorithm itself in terms of the computation complexity, however, using the correct ways to treat Strings helps to reduce the time complexity 2-3 times (in my case).
General advice: What you can instantiate and define before the loop, do it before the loop.
Is there a way to reverse the string in less than O(n) time? Or is something else slowing the code down?
No there is no way to reverse a string in less than O(n) time: A program that produces an output of size n necessarily takes o(n) time at the minimum.
Your code has lots of unnecessary operations that slow the program down. The program produces 50000 letters x, followed by one letter a, followed by another 50000 letters x. Here is a much faster (and easier to understand) implementation of the same program.
class Faster {
public static void main(String[] args) {
String hundredXs = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
for (int i = 0; i < 500; i++)
System.out.print(hundredXs);
System.out.print("a");
for (int i = 0; i < 500; i++)
System.out.print(hundredXs);
System.out.println();
}
}

Efficient way for finding Shortest Palindrome from Arraylist of strings

I am trying to solve this problem in java. I have an arraylist of palindromic strings. I have to find the shortest palindrome string out of the given array list. I have solved the question but was looking at getting feedback on my code and also how I can try to make the code more efficient/better.
Here is the code what I have tried.
In this case, size would be 3, since that is the length of the smallest palindromic string.
import java.util.ArrayList;
class ShortestPalindrome {
public static int isShortestPalindrome(ArrayList<String> list) {
int smallest = list.get(0).length();
boolean ret = false;
for (String element : list) {
ret = isPalindrome(element);
if (ret) {
if (element.length() < smallest)
smallest = element.length();
}
}
return smallest;
}
private static boolean isPalindrome(String input) {
String str = "";
boolean result = false;
if (input.length() == 1 || input.length() == 0)
return true;
if (input.charAt(0) != input.charAt(input.length() - 1))
return false;
StringBuilder sb = new StringBuilder(input.toLowerCase());
str = sb.reverse().toString();
if (input.equals(str)) {
result = true;
}
return result;
}
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<String>();
array.add("malayam");
array.add("aba");
array.add("abcdeyugugi");
array.add("nitin");
int size = isShortestPalindrome(array);
System.out.println("Shortest length of string in list:" + size);
}
}
I have a couple of comments regarding your code:
In general - if you break your problem to smaller parts, there are efficient solutions all around.
As #AKSW mentioned in his comment, if - in any case - we have to check each string's length, it's better to do it in the beginning - so we don't run the relatively expensive method isPalindrome() with irrelevant strings.(Just notice I override the given list with the sorted one, even though initializing a new sorted list is trivial)
The main improvement that I made is in the isPalindrome() method:
Reversing a string of length n takes n time and additional n space. Comparing the two takes also n time.Overall: 2n time, n space
Comparing each two matching characters (from the beginning and from the end) takes 2 additional space (for the integers) and approximately n/2 time.Overall: n/2 time, 2 space
Obviously when using limits for complexity calculations, the time complexities are both the same - O(n) - but the second solution is still 4 times cheaper and cost a negligible amount of space.
Therefore I believe this is the most efficient way to achieve your test:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
class ShortestPalindrome {
public static int isShortestPalindrome(ArrayList<String> list) {
// Sorts the given ArrayList by length
Collections.sort(list, Comparator.comparingInt(String::length));
for (String element : list) {
if(isPalindrome(element)) {
return element.length();
}
}
return -1; // If there is no palindrome in the given array
}
private static boolean isPalindrome(String input) {
String lowerCased = input.toLowerCase();
int pre = 0;
int end = lowerCased.length() - 1;
while (end > pre) {
if (lowerCased.charAt(pre) != lowerCased.charAt(end))
return false;
pre ++;
end --;
}
return true;
}
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<>(Arrays.asList("malayam", "aba", "abcdeyugugi", "nitin"));
int size = isShortestPalindrome(array);
System.out.println("Shortest length of string in list: " + size);
}
}
Edit: I've tested this algorithm with the following list. Sorting the list before checking for palindromes reduces run time in 50%.
"malayam", "aba", "abcdeyugugi", "nitin", "sadjsaudifjksdfjds", "sadjsaudifjksdfjdssadjsaudifjksdfjds", "sadjsaudifjksdfjdssadjsaudifjksdfjdssadjsaudifjksdfjds", "a"
The simplest improvement to your code is to only check whether a string is a palindrome, if it's length is smaller than smallest.
Btw the initialization int smallest = list.get(0).length(); is not correct, imagine the first element not being a palindrome and being of smallest size of all strings. You should do int smallest = Integer.MAX_VALUE;
Also the check
if (input.charAt(0) != input.charAt(input.length() - 1))
return false;
is incorrect, as you don't convert the characters to lower case (as you do later), thus "ajA" would not be a palindrome.
There are further improvements of your code possible:
You could replace the palindrome checking by copying and reversing with this:
for (int i = 0; i < input.length() / 2; ++i)
if (Character.toLowerCase(input.charAt(i)) != Character.toLowerCase(input.charAt(input.length() - 1 - i)))
return false;
Here there is no copy necessary and in the average case it might be faster (as it can terminate early).
Also, like AKSW mentioned, it might be faster to sort the strings by length and then you can terminate early, once you found a palindrome.
Below simple code should work for you.
First check for length and then check is it actually palindrome or not.
If yes, then just store it in smallest
public static int isShortestPalindrome(ArrayList<String> list) {
Integer smallest = null;
for(String s:list){
if ( (smallest == null || s.length()< smallest) && new StringBuilder(s).reverse().toString().equalsIgnoreCase(s) ){
smallest = s.length();
}
}
return smallest == null ? 0 :smallest;
}
Here is a stream version:
OptionalInt minimalLenOfPalindrome
= list.paralellStream()
.filter(st -> {
StringBuilder sb = new StringBuilder(st);
String reversedSt = sb.reverse().toString();
return st.equalsIgnoreCase(reversedSt);
})
.mapToInt(String::length)
.min();
Thanks for #yassin answer I am changing above code with:
public class SOFlow {
private static boolean isPalindrome(String input) {
for (int i = 0; i < input.length() / 2; ++i) {
if (Character.toLowerCase(input.charAt(i)) != Character.toLowerCase(input.charAt(input.length() - 1 - i))) {
return false;
}
}
return true;
}
public static void main(String args[]) {
List<String> list = new ArrayList<>();
list.add("cAcc");
list.add("a;;;;a");
list.add("aJA");
list.add("vrrtrrr");
list.add("cAccccccccc");
OptionalInt minimalLenOfPalindrome
= list.parallelStream()
.filter(SOFlow::isPalindrome)
.mapToInt(String::length)
.min();
System.out.println(minimalLenOfPalindrome);
}
}
Using Java8 streams, and considering sorting first, for the same reasons as others:
boolean isPalindrome (String input) {
StringBuilder sb = new StringBuilder(input.toLowerCase());
return sb == sb.reverse();
}
public static int isShortestPalindrome(ArrayList<String> list) {
return (list.stream().sorted ((s1, s2) -> {
return s1.length () - s2.length ();
})
.filter (s-> isPalindrome (s))
.findFirst ()
.map (s -> s.length ())
.orElse (-1));
}
If you have many Strings of equal, minimal length, of very big length, only non palindromic in the very middle, you might spend much time in isPalindrome and prefer something like isPalindrome1 over isPalindrome.
If we assume a Million Strings of equally distributed length from 1000 to 2000 characters, we would end up with concentration on in avg. 1000 Strings. If most of them are equal except on few characters, close to the middle, then fine tuning that comparison might be relevant. But finding a palindrome early terminates our search, so the percentage of palindromes is of heavy influence on the performance too.
private static boolean isPalindrome1 (String s) {
String input = s.toLowerCase ();
int len = input.length ();
for (int i = 0, j = len -1; i < len/2 && j > len/2; ++i, --j)
if (input.charAt(i) != input.charAt (j))
return false;
return true;
}
The Result of the stream sorting and filtering is an Option, which is great opportunity to signal, that nothing was found. I sticked to your interface of returning int and return -1 if nothing is found, which has to be properly evaluated, of course, by the caller.

String Filtering .. Any one..Contains only either of the selected characters or both

I have a array from 0 to 10000.
problem
And i need to filter only the numbers containing either 3 or 4 or both and none other than that...
eg., 3,4,33,44,333,444,343,434,334
I tried the do while technique but I have made some mistake in the code..\
I'm not getting the output still..:(
the improved code is
import java.util.*;
import static java.lang.System.*;
public class numb {
public static void main(String[] args) {
int num,c,cum;
int i;
Scanner in = new Scanner(System.in);
out.println(3/10);
out.println("How many elements u need to put in this array?");
num=in.nextInt();
int[] ray1 = new int[num];
List<String> l1 = new LinkedList<String>();
for (c=0;c<num;c++)
{
ray1[c]=c;
}
for(i=1;i<num;i++)
{
boolean baabu=true;
do {
cum=ray1[i];
int lastdig = cum%10;
if(lastdig!=3||lastdig!=4)
{
baabu=false;
}
cum=cum/10;
}
while(cum>0&&baabu);
if(baabu)
{
String ad = String.valueOf(ray1[i]);
l1.add(ad);
}
}
printme(l1);
}
public static void print (int[] array)
{
for(int xc:array)
out.println(xc);
}
public static void printme (List<String> l1)
{
for(String yc:l1)
out.println(yc);
}
}
Check if the last digit is a 3 or a 4: if it is, divide the number by 10 and check the next digit; otherwise, discard the number.
boolean add = true;
do {
int lastDigit = num % 10;
if (lastDigit != 3 && lastDigit != 4) {
add = false;
}
num /= 10;
} while (add && num > 0);
if (add) {
// Add to list.
}
Think of the string as a list of characters, eg 3435 is 3,4,3,5 and 39 is 3,9. You need to iterate over the characters in the string and check that each one is a 3 or 4. Your code checks for specific combinations of 3 and 4 but cannot handle all possible enumerations.
Since you're treating the numbers as Strings already, the simplest way to find {3,4} strings is by matching the Regex "^[34]+$"
Pattern threeFourStrings = Pattern.compile("^[34]+$");
...
if (threeFourStrings.matcher(ad).matches()) {
l1.add(ad);
}
However, since you want all {3,4} strings up to 10000 (i.e. up to 4 digits), it would be far more efficient to construct them. You might start looking here: How to get all possible n-digit numbers that can be formed using given digits?
(Of course since you only have 2 digits, "3" and "4", it might be even more efficient to just list the binary numbers from 0 to 1111 and transpose "0" to "3" and "1" to "4".)
Use this method for every int in the array (it worked for me):
public static String myMethod(int num){
String str = String.valueOf(num);
String ns ="";
for (int i=0; i<str.length(); i++) {
if (str.substring(i, i+1).equals("3"))
{
ns += 3;
}
if (str.substring(i, i+1).equals("4"))
{
ns += 4;
}
}
return ns;
}

Printing a number with appropriate commas recursively

A sample input of 12345 should return 12,345. I have it figured out i think. Only problem is the string I get is reversed (543,21). Now i know there's ways to reverse a string pretty easily but that's more complexity to the running time so I was wondering if there was a straightforward way to do it within the auxiliary itself?
public void print(int n){
String number = Integer.toString(n);
StringBuilder answer = new StringBuilder();
if(number.length() > 3){ //Only worry about adding commas if its more than three digits
printAux(number, answer, 1, number.length()-1);
System.out.println(answer);
}
}
private void printAux(String s, StringBuilder answer, int count, int index){
if(index < 0){
return;
}
else{
//If the counter is at the 4th index meaning it has passed three digits
if(count%3 == 1 && count > 3){
answer.append(",");
index = index + 1;
count = 0;
}
else{
answer.append(s.charAt(index));
}
printAux(s, answer, count + 1, index - 1);
}
}
Something simpler
public static void print(String s) {
out.print(s.charAt(0));
if (s.length() == 1) out.print("\n");
else {
if (((s.length()-1) % 3) == 0) out.print(",");
print(s.substring(1));
}
}
Explanation:
always print the 1st character
if there is no more character, print CR
if there is at least one character to process, check if the length of what to process is a multiple of 3, if yes print a ","
and call recursively print with the string minus the 1st character
You can can use StringBuilder.reverse() to reverse a String in one line like
String str = "abc";
str = new StringBuilder(str).reverse().toString();
But you could also use printf1. Something like,
public static void print(int n) {
System.out.printf("%,d%n", n);
}
public static void main(String[] args) {
int num = 123456789;
print(num);
}
Output is (as requested)
123,456,789
1See also The Java Tutorials - Formatting Numeric Print Output for more options.
You can use the following DecimalFormat to get the job done.
String number = "1000500000.574";
double amount = Double.parseDouble(number);
DecimalFormat formatter = new DecimalFormat("#,###.00");
System.out.println(formatter.format(amount));

Moving from one base to another java?

I'm trying to create a program that reads in two bases from stdin and checks to see what's the smallest number in which both have repeating digits in it. It seems to be working fine for small bases but when I use larger bases I seem to be getting the wrong answer. e.g. giving it 3 and 50 it will find 22 as the smallest number where they both have repeated digits but i'm pretty sure 22 in base 50 is a single number.
What's the logic here that I'm missing? I'm stumped. Anything to point me in the right direction would be appreciated :)
My conversion method, this works for smaller bases but not larger it seems.
public static String converties(int number, int base)
{
int remainder;
ArrayList<Integer>remainders = new ArrayList<Integer>();
while (number != 0)
{
remainder = number%base;
remainders.add(remainder);
number = number/base;
}
String result = "";
for (int i = 0; i < remainders.size(); i++)
{
result+=Integer.toString(remainders.get(i));
}
result = reverseString(result);
return result;
}
public static String reverseString(String result)
{
String newResult = "";
for (int i = result.length()-1; i >= 0; i--)
{
newResult+=result.charAt(i);
}
return newResult;
}
public static boolean areThereRepeats(String value)
{
ArrayList<Character> splitString = new ArrayList<Character>();
for (char c : value.toCharArray())
{
//if it already contains value then theres repeated digit
if (splitString.contains(c))
{
return true;
}
splitString.add(c);
}
return false;
}
The problem is in this function:
public static boolean areThereRepeats(String value){
ArrayList<Character> splitString = new ArrayList<Character>();
for (char c : value.toCharArray()){
//if it already contains value then theres repeated digit
if (splitString.contains(c)){
return true;//Note that returning here only checks the first value that matches
}
splitString.add(c);
}
return false;
}
When you check to see if splitString.contains(c) it will return true if the array is length one. You aren't doing anything to check that the char c you're checking isn't comparing against itself.
Also note that Maraca has a point: the data structure you're choosing to utilize to record your remainders is flawed. areThereRepeats will work fine for checking if you assume that each new character represents a new remainder (or more specifically, the index into the base you're checking of the remainder you found). But why marshal all of that into a string in the first place? Why not pass the ArrayList to areThereRepeats?
public static boolean converties(int number, int base){
int remainder;
ArrayList<Integer>remainders = new ArrayList<Integer>();
while (number != 0){
remainder = number%base;
remainders.add(remainder);//Saves the index of the remainder in the current base, using an integer base-10 representation
number = number/base;
}
return areThereRepeats(remainders);
}
//Recursion ain't efficient, but...
public static boolean areThereRepeats(ArrayList<Integer> remainders){
if (remainders.size() <= 1) {
return false;
}
rSublist = remainders.sublist(1, remainders.size())
if (rSublist.contains(remainders.get(0)) {
return true;
}
return areThereRepeats(rSublist);
}
result+=Integer.toString(remainders.get(i));
In this line you add the remainder in base 10, so it will only work correctly if you find a match with base <= 10. Btw. It could be done very easily with BigInteger (if you don't want to do it yourself).
Otherwise:
result += (char)(remainders.get(i) < 10 ? ('0' + remainders.get(i)) : ('A' + remainders.get(i) - 10));
This will work up to base 36.
Or just use result += (char)remainders.get(i); it will work up to base 256, but it won't be readable.
And I agree with Nathaniel Ford, it would be better to pass the ArrayLists. If you still want to get the String in the standard way you can make another function to which you pass the ArrayList and transform it with the 1st method shown here.

Categories

Resources