I am new to complexity analysis. The task is that given a non-empty string like "Code" return a string like "CCoCodCode". I have these 2 programs which are doing the same thing.
Program 1:
public String stringSplosion(String str) {
String result = "";
for (int i=0; i<str.length(); i++) {
for(int j=0;j<=i;j++)
result += str.charAt(j);
}
return result;
}
So, above one is pretty simple and this program has O(n^2) complexity.
Program 2:
public String stringSplosion(String str) {
String result = "";
// On each iteration, add the substring of the chars 0..i
for (int i=0; i<str.length(); i++) {
result = result + str.substring(0, i+1);
}
return result;
}
From a different StackOverflow question, it seems str.substring() has a time complexity of O(n). In that case, program 2 also has O(n^2) time complexity.
Is my analysis correct or I am missing something?
In fact, both have the same complexity - O(n^3). That's because you're using += for concatenating the answer! there's a hidden loop there that you didn't account for, and a classic example of Schlemiel the Painter's Algorithm. You should use StringBuilder instead, it's the right way to build a string as you go.
Forgive the indentation but this is essentially a solution that satisfies the tests.
public String stringSplosion(String str) {
// Empty String test
if (str.length() == 0)
return str;
StringBuilder sb = new StringBuilder();
for (int i=0; i<=str.length() ; i++) {
sb.append(str.substring(0,i));
}
return sb.toString();
}
Related
I'm trying to concatenate a string with itself and remove all capital letters from the resultant string.
Here is my code:
public String removeCapitals(String A) {
StringBuilder B = new StringBuilder(A+A);
int n = B.length();
for(int i=0; i<n; i++){
if(B.charAt(i)>='A' && B.charAt(i)<='Z'){
B.deleteCharAt(i);
}
}
return B.toString();
}
I'm getting Exception saying:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 6
at java.lang.AbstractStringBuilder.charAt(AbstractStringBuilder.java:237)
at java.lang.StringBuilder.charAt(StringBuilder.java:76)
at Solution.removeCapitals(Solution.java:10)
at Main.main(Main.java:190)
Can someone help me to understand the issue.
If at least one removal succeeds, at some point your code will attempt to access an invalid index that exceeds the length of a StringBuilder.
It happens because the variable n remain unchanged. You should change the condition to be bound to the current size of StringBuilder and decrement the index at each removal, or iterate backwards (as shown in another answer).
Also condition B.charAt(i)>='A' && B.charAt(i)<='Z' can be replaced with:
Character.isUpperCase(b.charAt(i))
Which is more descriptive.
That's how it might look like:
public static String removeCapitals(String a) {
StringBuilder b = new StringBuilder(a + a);
for (int i = 0; i < b.length(); i++) {
if (Character.isUpperCase(b.charAt(i))) {
b.deleteCharAt(i); // which can be combined with the next line `b.deleteCharAt(i--);` - previous value of `i` would be used in the call `deleteCharAt()` and variable `i` will hold a value decremented by 1
i--;
}
}
return b.toString();
}
Method deleteCharAt() runs in a linear time, because it shifts all subsequent characters in the underlying array bytes. Each upper-case letter will trigger these shifts and in the worst case scenario, it would result in the quadratic overall time complexity O(n ^ 2).
You make your method more performant and much more concise without using loops and StringBuilder. This code will run in a linear time O(n).
public static String removeCapitals(String a) {
return a.replaceAll("\\p{Upper}", "").repeat(2);
}
When you delete a character you change the length of the StringBuilder. But n still has the original length. So you will eventually exceed the size of the StringBuilder. So start from the end and move backwards. That way, any deletions will come after (based on relative indices) the next position so the index will be within the modified StringBuilder size. In addition, deleting from the end is more efficient since there is less copying to do in the StringBuilder.
public String removeCapitals(String A) {
StringBuilder B = new StringBuilder(A+A);
int n = B.length();
for(int i=n-1; i>=0; i--){
if(B.charAt(i)>='A' && B.charAt(i)<='Z'){
B.deleteCharAt(i);
}
}
return B.toString();
}
If just remove Capital characters from a string. Alternative solution just create another method replaceAll() + regex
private static String removeCapitals(String A){
if (!A.isEmpty() && !A.equals("")) {
String B = A + A;
String newStr = B.replaceAll("([A-Z])", "");
return newStr;
} else {
return null;
}
}
Shorter solution to your task.
String a = "ABcdEF";
String b = "";
for (int i = 0; i < a.length(); i++) {
if(a.toLowerCase().charAt(i) == a.charAt(i))
b+=a.charAt(i);
}
System.out.println(b);
By changing to .toUpperCase() you'll get rid of the lower case ones.
I wrote a method to reduce a sequence of the same characters to a single character as follows. It seems its logic is correct while there is a room for improvement in terms of performance, according to my tutor. Could anyone shed some light on this?
Comments of aspects other than performance is also really appreciated.
public class RemoveRepetitions {
public static String remove(String input) {
String ret = "";
String last = "";
String[] stringArray = input.split("");
for(int j=0; j < stringArray.length; j++) {
if (! last.equals(stringArray[j]) ) {
ret += stringArray[j];
}
last = stringArray[j];
}
return ret;
}
public static void main(String[] args) {
System.out.println(RemoveRepetitions.remove("foobaarrbuzz"));
}
}
We can improve the performance by using StringBuilder instead of using string as string operations are costlier. Also, the split function is also not required (it will make the program slower as well).
Here is a way to solve this:
public static String remove(String input)
{
StringBuilder answer = new StringBuilder("");
int N = input.length();
int i = 0;
while (i < N)
{
char c = input.charAt(i);
answer.append( c );
while (i<N && input.charAt(i)==c)
++i;
}
return answer.toString();
}
The idea is to iterate over all characters of the input string and keep appending every new character to the answer and skip all the same consecutive characters.
Possible change which you could think of in your code is:
Time Complexity: Your code is achieving output in O(n) time complexity, which might be the best possible way.
Space Complexity: Your code is using extra memory space which arises due to splitting.
Question to ask: Can you achieve this output, without using the extra space for character array that you get after splitting the string? (as character by character traversal is possible directly on string).
I can provide you the code here but, it would be great if you could try it on your own, once you are done with your attempts
you can lookup for the best solution here (you are almost there)
https://www.geeksforgeeks.org/remove-consecutive-duplicates-string/
Good luck!
As mentioned before, it is much better to access the characters in the string using method String::charAt or at least by iterating a char array retrieved with String::toCharArray instead of splitting the input string into String array.
However, Java strings may contain characters exceeding basic multilingual plane of Unicode (e.g. emojis πππ, Chinese or Japanese characters etc.) and therefore String::codePointAt should be used. Respectively, Character.charCount should be used to calculate appropriate offset while iterating the input string.
Also the input string should be checked if it's null or empty, so the resulting code may look like this:
public static String dedup(String str) {
if (null == str || str.isEmpty()) {
return str;
}
int prev = -1;
int n = str.length();
System.out.println("length = " + n + " of [" + str + "], real length: " + str.codePointCount(0, n));
StringBuilder sb = new StringBuilder(n);
for (int i = 0; i < n; ) {
int cp = str.codePointAt(i);
if (i == 0 || cp != prev) {
sb.appendCodePoint(cp);
}
prev = cp;
i += Character.charCount(cp); // for emojis it returns 2
}
return sb.toString();
}
A version with String::charAt may look like this:
public static String dedup2(String str) {
if (null == str || str.isEmpty()) {
return str;
}
int n = str.length();
StringBuilder sb = new StringBuilder(n);
sb.append(str.charAt(0));
for (int i = 1; i < n; i++) {
if (str.charAt(i) != str.charAt(i - 1)) {
sb.append(str.charAt(i));
}
}
return sb.toString();
}
The following test proves that charAt fails to deduplicate repeated emojis:
System.out.println("codePoint: " + dedup ("πππππππ hello"));
System.out.println("charAt: " + dedup2("πππππππ hello"));
Output:
length = 20 of [πππππππ hello], real length: 13
codePoint: ππππ helo
charAt: πππππππ helo
I'm a beginner with java and one concept is rather unclear to me. I need to create a method that creates a new string by replicating another string. For example, if the String1 is "java" and it is specified that it needs to be repeated 4 times and each of them needs to be separated with a comma, the new string would look like this: java, java, java, java
However, the method should not print it, but only create a new string that is then printed in the main program. This is a problem for me, because I have trouble understanding, how can I use a loop to create something without printing it. I think that the following code would print it correctly:
public static void replicate(String str, int times) {
for (int i = 0; i < times; i++) {
System.out.print(str);
if (i < times -1) {
System.out.print(", ");
}
}
}
How could I transform it so that I could use the method to create a new string without printing it? I am assuming this is something super simple, but I just don't know at all how to do this, because every guide just uses examples of printing in these kinds of situations.
This is much better with Collections and join
import java.util.*;
public class Main
{
public static void main(String[] args)
{
String newstr=String.join(",", Collections.nCopies(3, "java"));
System.out.println(newstr);
}
}
Working fiddle-https://repl.it/repls/OfficialInvolvedObject
This is easy to do using the StringBuilder class, which can be used like this:
public static String replicate(String str, int times) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < times; i++) {
builder.append(str);
if (i < times - 1) {
builder.append(", ");
}
}
return builder.toString();
}
The following code should be useful for academic purposes in which you cannot use all of Java's libraries. You want to use a variable to store the repeated text and then return that variable to the main method for it to be printed. For non academic purposes or in cases when efficiency is a priority the java.lang.StringBuilder object should be used.
public static String replicate(String str, int times) {
String newString = "";
for (int i = 0; i < times; i++) {
newString = newString + str;
if(i<times-1){
newString = newString + ", ";
}
}
return newString;
}
One option is to use a StringBuilder, as exemplified by Emily's answer. Since the same delimiter is used each time another option is to use java.util.StringJoiner. Here's an example:
public static String replicate(String str, int times) {
StringJoiner joiner = new StringJoiner(", ");
for (int i = 0; i < times; i++) {
joiner.add(str);
}
return joiner.toString();
}
I am assuming this is something super simple
You are right. While the other answers show various helper classes, you can do it with re-assigning a single String variable already:
public static String replicate(String str, int times) {
String result="";
for (int i = 0; i < times; i++) {
result += str; //System.out.print(str);
if (i < times -1) {
result += ", "; //System.out.print(", ");
}
}
return result;
}
Or, if you do not worry about having to provide 0 copies:
public static String replicate(String str, int times) {
String result=str; // one copy, without comma
for (int i = 1; i < times; i++) {
result += ", " + str; // all the other copies have their commas
}
return result;
}
This latter "trick" can be used with StringBuilder too of course:
public static String replicate(String str, int times) {
StringBuilder result = new StringBuilder(str);
for (int i = 1; i < times; i++) {
result.append(", ");
result.append(str);
}
return result.toString();
}
Btw, it may be worth mentioning what there story about StringBuilder is: when Java sees something=string1+string2, internally it writes StringBuilder temp=new StringBuilder(string1); temp.append(string2); something=temp.toString() (of course it is an unnamed, internal variable, not "temp"). So a brand new StringBuilder object is created, appended, converted back to a brand new String and thrown away every time. That is why the suggested method is to have one, dedicated StringBuilder, use it many times, and get the result from it only once, at the end.
I need to write a function that receives a String and removes adjacent duplicates.
Example:
Input -> "aabbaabbcccaaa"
Output -> "ababca"
I tried to solve it as follows:
public String remdups(String input) {
String response = "";
char temp;
int i, length = input.length();
for(i = 0; i < length; i++) {
temp = input.charAt(i);
response += temp;
while(i < length && input.charAt(i) == temp) i++;
}
return response;
}
But it seems that time complexity is not as expected, how could I improve perfomance or what would be a better approach?
I know it's a really simple problem, but I can't find a way to improve or another way to do it.
To me your code looks already good from a complexity point of view. It is only going through the String once. The optimisations you could do are on the response String by using a StringBuilder, and maybe simplifying the loop a bit just for readability (no need for 2 nested loops, and incrementing the i counter from 2 places could introduce mistakes).
public String remdups(String input) {
StringBuilder response = new StringBuilder(input.length());
char temp;
for (int i = 0; i < input.length(); i++) {
char next = input.charAt(i);
if (temp != next) {
temp = next;
response.append(temp);
}
}
return response.toString();
}
Why not try something with regex? Like so:
public static void main(String[] args) {
String str = "aabbaabbcccaaa";
System.out.println(str.replaceAll("(.)\\1+","$1"));
}
Output:
ababca
Edit:
For future reference, this approach turns out to be abysmally slow. I benchmarked it with JMH and itβs approximately 4x slower than the non-regex solution for short strings, and only gets worse for longer (~10k character) strings.
Hi I need some help with my internship test task. I've read it a couple of times and I can't even say that I'm surely know what I need to do. So the task is:
You must generate a string of random length consisting of random ASCII characters. After that you need to replace all entries of '1' with 'Q' and 'Q' with '1' in it. To complete the task you are allowed to change one substring with another as many times as you want. For example as a result of changing "ww" -> "wert" the string "wwwwwQ" will become "wertwertwQ". Write a program which does required changes in the most optimal way (doing minimum amount of replacements).
I've already implemented string generation and i simply don't know what to do next. As said in the header, i need to do this using Java. Could you please offer me some way to solve this task?
As i said what i've already done is genString() which generates a char array for me and a replace() method which does what intended but it not uses substrings so it seems that this task should be done another way.
public static char[] genString()
{
int n = rand.nextInt(50) + 1;
char[] arr = new char[n];
for (int i = 0; i<n; i++)
{
arr[i] = (char)(rand.nextInt(95) + 33);
}
return arr;
}
public static void replace(char[] arr)
{
for (int i = 0; i < arr.length; i++)
{
arr[i] = (arr[i] == 'Q') ? '1'
: (arr[i] == '1' ) ? 'Q'
: arr[i];
}
}
What i actually don't understand is that how the substrings could be used there. I don't understand how going from "wwwwwQ" to "wertwertwQ" -like replacements will help me replace the 'Q' in it
To make the fewest replacements, I would use a stream-based approach:
StringBuilder sb = new StringBuilder(str);
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == 'Q') {
sb.setCharAt(i, '1');
} else if (str.charAt(i) == '1') {
sb.setCharAt(i, 'Q');
}
}
return sb.toString();
Using a StringBuilder make manipulating the string a lot more efficient, because a new string doesn't have to be created every time you change a letter.
well, try this code:
public String Replace(String theString, String first, String Second)
{
String result ="";
result=theString.replace(first, Second);
return result;
}
Example of using:
System.out.println(Replace("wwwwwQ", "ww", "wert"));// result will be: wertwertwQ
I hope it will help you, best regards