Remove adjacent duplicates from a String - java

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.

Related

Remove a Character From String in Java

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.

How to efficiently remove consecutive same characters in a string

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

Java - breaking out of a incursion method and returning the value

Good morning all,
Today is my first time trying to make a recursion method. Just when I thought it would work I got the error; missing return statement. This confuses me because it literally has a return value ( the total string from the Stringbuilder ) after n < 1
This is what I got:
import java.util.Scanner;
public class P5_4MethodRepeatString {
public void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.println("Enter a string followed by the times you want
it repeated:");
String input = sc.nextLine();
int count = sc.nextInt();
String total = repeat(input, count);
System.out.println(total);
}
public static String repeat(String str, int n) {
StringBuilder full = new StringBuilder();
if(n < 1) {return full.toString();}
repeat(str, n - 1);
for(int i = 0; i < n; i++){
full.append(str);
}
}
}
if(n < 1) {return full.toString();} but if n >= 1 you don't return anything. It should be return repeat(str, n - 1); but then you need to move the for... part
The immediate cause of your problem is that not all conditional flows in your repeat() method have a return value. But I am not sure if even making that change would result in working code. Consider the following refactor:
public void repeat(StringBuilder result, String str, int n) {
if (n == 0) return;
result.append(str);
repeat(result, str, n - 1);
return;
}
Here we are passing in a StringBuilder object which we want to contain our repeated string results. We also pass the string to be repeated, and the number of remaining turns. The base case occurs where there are no more turns, in which case we simply return from the recursive method. Otherwise, we add one copy of the string and make a recursive call.
Your original recursive attempt had the following for loop:
for (int i=0; i < n; i++) {
full.append(str);
}
This does not look recursive, and looping to repeatedly add the string defeats the point of doing recursion (though I would probably use a loop in real life if I had to do this).

Time complexity of these 2 program

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();
}

Replace '1' with 'Q' and 'Q' with '1'. Char replacement in string. Java

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

Categories