I am new to Java and am trying to create a method that will allow me to remove duplicate characters in a string and create a new string with all the consecutive occurrences of the same character turned into a single character. For example, string fffggghhh would return as fgh. I have provided my code below but I am receiving an index out of range error with the length of the string that I input. For example when testing this method and entering AA as my string, I receive an index out of range 2 error.
public String DuplicatesTEST(String s) {
StringBuilder result = new StringBuilder();
for (int i = 1; i <= s.length(); i++) {
char curr = s.charAt(i);
char prev = s.charAt(0);
if (curr != prev) {
result.append(prev);
} else if (curr == prev)
prev = curr;
}
return result.toString();
}
Behavior of Data structure Set is not containing duplicates, so use Set to remove duplicates.
Try something like this:
public String DuplicatesTEST(String s) {
Set<Character> set = new HashSet<>();
StringBuilder result = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
set.add(s.charAt(i));
}
set.forEach(result::append);
return result.toString();
}
for fffggghhhff input this return fgh.
If you want to remove duplicates with a block above solution not help then I did small change to your implementation:
public String DuplicatesTEST(String s) {
StringBuilder result = new StringBuilder();
if (s != null && !s.isEmpty()) {
char first = s.charAt(0);
for (int i = 1; i < s.length(); i++) {
if (first != s.charAt(i)) {
result.append(first);
first = s.charAt(i);
}
}
result.append(first);
}
return result.toString();
}
for fffggghhhff input this return fghf.
You can transform your method to recursive and use regexp. This greatly simplifies your code.
Try it online!
public static String removeDuplicates(String str) {
if (str.equals(str = str.replaceAll("(.)\\1", "$1")))
return str;
else
return removeDuplicates(str);
}
public static void main(String[] args) {
System.out.println(removeDuplicates("aaaaaaa")); // a
System.out.println(removeDuplicates("fffggghhh")); // fgh
System.out.println(removeDuplicates("fffggghhhff")); // fghf
}
Explanation:
regexp (.)\\1 - any character followed by the same character;
regexp $1 - replace with a character from the 1st group, i.e. the same character;
str = str.replaceAll(...) - removes all duplicates and replaces current string;
str.equals(...) - checks equality of the current string with itself, but without duplicates.
See also: Remove a pair of chars in a string next to each other
Strings are 0 based Index.
As per your input, If duplicate characters are consecutive orders then you can store first character from your input string into result StringBuilder.
Also, in your code, prev always stars at 0 which is wrong. You need to keep increment it too.
Here is is fixed version of your code:
public static String DuplicatesTEST(String s) {
StringBuilder result = new StringBuilder();
result.append(s.charAt(0));
for (int i = 1; i < s.length(); i++) {
char prev = s.charAt(i);
char curr = s.charAt(i - 1);
if (curr != prev || i == 0) {
result.append(prev);
}
else if (curr == prev) {
prev = curr;
}
}
return result.toString();
}
Related
I am having an issue with a particular leetcode problem called Valid Palindrome. My code works for all test cases except the last test case 479/480.
In this test case a 106890 length string is passed in but my code takes too long to solve it.
I decided to try take a different approach and use the StringBuilder class to reverse the string and then simply use reversedString.equals(originalString) to compare whether they are a palindrome. This approach solves the question and passes all testcases
Why doesn't my two pointer approach work? Why does it fail on the last test case?
Here is my solution (Two Pointer)
class Solution {
public static boolean isPalindrome(String s) {
String fixedString = "";
for (char c : s.toCharArray()) {
if (Character.isDigit(c) || Character.isLetter(c)) {
fixedString += c;
}
}
fixedString = fixedString.toLowerCase();
int i = 0;
int j = fixedString.length() - 1;
System.out.println(fixedString.toCharArray());
while (i <= j) {
if (fixedString.toCharArray()[i] != fixedString.toCharArray()[j]) {
return false;
}
i += 1;
j -= 1;
}
return true;
}
}
Here is my second solution using StringBuilder.
public class Valid_Palindrome {
public static void main(String args[]){
System.out.println(isPalindrome("A man, a plan, a canal: Panama"));
}
public static boolean isPalindrome(String s) {
String fixedString = "";
for(char c : s.toCharArray()){
if(Character.isDigit(c) || Character.isLetter(c)){
fixedString += c;
}
}
fixedString = fixedString.toLowerCase();
StringBuilder sb = new StringBuilder(fixedString);
sb = sb.reverse();
System.out.println(sb);
return sb.toString().equals(fixedString);
}
}
Technically speaking, isn't the second solution supposed to be much slower since it is using StringBuilder?
How do I optimize my first solution?
Here is the input string that is passed in my leetcode.
Don't build or reverse or do anything with the string, except iterate over half its characters.
In pseudo code:
Loop over the first half of the characters
For the ith character, compare it with the (length - i - 1)th character
If different, return false
If loop ends, return true
It is generally slow to perform string concatenation in a loop. Use a StringBuilder instead in the first loop to create the filtered string.
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (Character.isLetterOrDigit(c))
sb.append(Character.toLowerCase(c));
}
for (int i = 0, j = sb.length() - 1; i < j; i++, j--)
if (sb.charAt(i) != sb.charAt(j))
return false;
return true;
There are a couple of statements in your code that are probably slowing it down.
fixedString += c;
This creates a new StringBuilder object. The contents of fixedString are copied to it. Then the character (c) is appended. Then the StringBuilder is converted to a String and that String is assigned to variable fixedString.
if (fixedString.toCharArray()[i] != fixedString.toCharArray()[j])
Method toCharArray creates a new char[] and copies the contents of the String to it.
I suggest that you create the char[] once only and work with it. Of-course you need to remove the non-letters and non-digits from the original string as well as convert to lower case.
Here is my rewrite of your [two pointer] solution.
(Note that I assume that a null or empty string is not a palindrome.)
public static boolean isPalindrome(String s) {
if (s != null && !s.isEmpty()) {
char[] chars = s.toCharArray();
char[] temp = new char[chars.length];
int count = 0;
for (char c : chars) {
if (Character.isDigit(c) || Character.isLetter(c)) {
temp[count++] = Character.toLowerCase(c);
}
}
char[] letters = new char[count];
System.arraycopy(temp, 0, letters, 0, count);
int i = 0;
int j = count - 1;
System.out.println(letters);
while (i < j) {
if (letters[i] != letters[j]) {
return false;
}
i++;
j--;
}
return true;
}
return false;
}
Like in question, exercise if it is possible to create a palindromic string of minimum length 3 characters by removing 1 or 2 characters. For example string "abjchba", we can remove letters "jc" and will get palindromic, in this case program should return removed letters so "jc". I know that we can mke palindromic by removing also "ch" but in exercise is that we should remove characters that appear earlier in string. Program should always attempt to create the longest palindromic substring. I wrote methods to reverse String and method to check that string is palindromic:
private static String reverse(String string) {
return new StringBuilder(string).reverse().toString();
}
private static boolean isPalin(String string) {
return string.equals(reverse(string));
}
I also made method to create Palindromic to return symbols we should remove to make palindromic, but beacuse i'm working on sb 'temp' I got exception . Have anyone idea how to fix it and finish exercise?
private static String createPalindrome(String str) {
StringBuilder result = new StringBuilder();
StringBuilder temp = new StringBuilder(str);
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == str.charAt(str.length() - 1 - i)){
continue;
}else {
result.append(str.charAt(i));
temp.deleteCharAt(i);
if (isPalin(temp.toString())){
return result.toString();
}
}
}
return "not possible";
}
Method 1:
Find longest palindromic subsequence(LPS)
Given string: "abjchba"
Longest Palindrome Subsequence: "abhba". Others like "abjba" and "abcba" also are LPS but you want to remove chars that appear earlier so that "abhba".
If (input string length - length of LPS) > 2, return "not possible".
Remove letters from the input string that are not in the LPS.
Start matching string with LPS. 'j' and 'c' won't match. Add them to result and return.
Method 2:
Find longest common subsequence (LCS) between input string and its reverse.
String: "abjchba"
Reverse: "abhcjba"
LCS: Take "abhba" in our case
If (input string length - length of LCS) > 2, return "not possible".
Step 2 will be the same as that of in Method 1 above.
As you are trying for at most 2 deletions, I am thinking if we can do better with time complexity.
I think the simplest way to fix your code is to use recursion. When you find a char that does not match, remove it and call recursively.
// Helper function to remove a character from a string
public static String removeAt(String s, int i)
{
return s.substring(0, i) + s.substring(i + 1);
}
private static String createPalindromeRecursive(String str) {
// Only need to check half the string
for (int i = 0, j = str.length() - 1; i < j; i++, j--) {
// if (something) continue; else {} <- the else is not needed
// because the continue skips to the end of the loop
// or you can negate the condition and don't use continue
if (str.charAt(i) == str.charAt(j)){
continue;
}
String temp = createPalindrome(removeAt(str, i));
// Success. Return the new string
if (null != temp) return temp;
else return null;
}
return str;
}
private static String createPalindrome(String str) {
String palindrome = createPalindromeRecursive(str);
if (palindrome == null || palindrome.length() < str.length() - 2) {
return "not possible";
}
else {
return palindrome;
}
}
using System;
using System.Text;
class MainClass
{
public static String removeAt(String s, int i)
{
return s.Substring(0, i) + s.Substring(i + 1);
}
private static String createPalindromeRecursive(String str)
{
for (int i = 0, j = str.Length - 1; i < j; i++, j--)
{
if (str[i] == str[j])
{
continue;
}
String temp = PalindromeCreator(removeAt(str, i));
if (null != temp) return temp;
else return null;
}
return str;
}
public static string PalindromeCreator(string str)
{
String palindrome = createPalindromeRecursive(str);
StringBuilder result = new StringBuilder();
StringBuilder temp = new StringBuilder(str);
if (palindrome == null || palindrome.Length < str.Length - 2)
{
return "not possible";
}
else
{
for (int i = 0; i < str.Length; i++)
{
if (str[i] == str[(str.Length - 1 - i)])
{
continue;
}
else
{
result.Append(str[i]);
temp.Remove(i, str.Length);
return result.ToString();
}
}
return palindrome;
}
}
static void Main()
{
// keep this function call here
Console.WriteLine(PalindromeCreator(Console.ReadLine()));
}
}
here it is giving error----required variable ,found value
my code
for eg aabacc when we got any pair like aa remove it from string and the final answer is (ba).
public class Solution {
// Complete the superReducedString function below.
static String superReducedString(String s) {
String sn;
int j=0;
for(int i=0;i<s.length()-1;i++)
{
if(s.charAt(i)!=s.charAt(i+1))
{
sn.charAt(j)=s.charAt(i);
j++;
}
}
return sn;
}
Since String is immutable in Java - String manipulation always generates a new String leaving the previous Strings in String Pool. StringBuffer and StringBuilder are mutable objects and provide methods for String manipulation
Sample working method using StringBuilder is provided below:
static String superReducedString(String s) {
StringBuilder myName = new StringBuilder(s);
int j=0;
for(int i=0;i<s.length()-1;i++) {
if(s.charAt(i)!=s.charAt(i+1)) {
myName.setCharAt(j, s.charAt(i));
j++;
}
}
return myName.toString();
}
You can not do such assignment like sn.charAt(j)=s.charAt(i); since charAt() is function that returns the result, but not a variable. You could use StringBuilder here:
static String superReducedString(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (s.length() == i+1 || s.charAt(i) != s.charAt(i + 1)) {
sb.append(s.charAt(i));
} else {
i++;
}
}
return sb.toString();
}
s.length() == i+1 checks if it's the last char. In case aabaccr the result will be as expected bar
Just another solution, in case the other answers don't work for you:
static String superReducedString(String s) {
char[] chars = s.toCharArray();
String lastChar = "";
ArrayList<String> newString = new ArrayList<>();
for (char aChar : chars) {
String currentChar = String.valueOf(aChar);
if (lastChar.equals(currentChar))
newString.remove(newString.size() - 1);
else {
newString.add(currentChar);
lastChar = currentChar;
}
}
AtomicReference<String> returnString = new AtomicReference<>("");
newString.forEach(character-> returnString.set(returnString + character));
return returnString.get();
}
The answer is quite simple.
you cannot delete anything from a String but you can move them to another String as you want.
public class Solution {
public static void main(String[] args) {
String s = "abbccd", s1 = "";
if(s.charAt(1) != s.charAt(0))
s1 += s.charAt(0);
if(s.charAt(s.length()-1) != s.charAt(s.length()-2))
s1 += s.charAt(s.length()-1);
for (int i = 1; i < s.length() - 1; i++) {
if (s.charAt(i) != s.charAt(i - 1) && s.charAt(i) != s.charAt(i + 1))
s1 += s.charAt(i);
}
System.out.println(s1);
}
}
You create another String.
Then in a for loop iterating from 1 (NOT 0) to s.length()-1 (NOT s.length()), you check if the s.charAt(i) (current character) is equal to the preceding or following one. If it's not equal to any of them, you add it to the second String and then you print it. We are checking both sides so that's why the loop is from 1 to s.length()-1, to avoid out of bounds exceptions.
EDIT: to check the first and last character.
The purpose of this method is replace all but the first and last letters of each word with "_". I'm a complete novice when it comes to coding, so I'm certain my code is fairly incorrect. I think where my code starts functioning improperly is with the while loop.
EDIT: How do I make this method without using arrays or extra methods, like the split method?
public static String blankWords(String s1) {
StringBuilder sb = new StringBuilder();
if(s1.length() > 2) {
sb.append(s1.charAt(0));
for(int x = 1; x < s1.length() - 1; x = x + 1) {
char y = ' ';
while(y != s1.charAt(x)) {
sb.append("_");
x = x + 1;
}
}
sb.append(s1.charAt(s1.length() - 1));
return sb.toString();
}
return s1;
}
What my code is outputting:
HW2.blankWords("This is a Test.")
java.lang.StringIndexOutOfBoundsException: String index out of range: 15
at java.lang.String.charAt(Unknown Source)
at HW2.blankWords(HW2.java:73)
What my code should output:
HW2.blankWords("This is a Test.")
"T__s is a T__t."
Here is a pretty simple solution:
class Scratch {
public static void main(String[] args) {
System.out.println(blankWords("My name is sam orozco"));
}
public static String delim = "_";
public static String blankWords(String s1) {
// this split arg on one or more space
String[] words = s1.split("\\s+");
StringBuilder response = new StringBuilder();
for (String val : words) {
val = convertWord(val);
response.append(val).append(" ");
}
return response.toString().trim();
}
public static String convertWord(String val) {
int len = val.length();
StringBuilder bldr = new StringBuilder();
int index = 0;
for (char ch : val.toCharArray()) {
if (index == 0 || index == len - 1) {
bldr.append(ch);
} else {
bldr.append(delim);
}
index++;
}
return bldr.toString();
}
}
You can do this using a StringTokenizer that will extract words based on a list of delimiters. Since you want to keep those delimiters in the output, you'll instruct the tokenizer to return them as tokens:
String blankWords(String s) {
// build a tokenizer for your string, listing all special chars as delimiters. The last argument says that delimiters are going to be returned as tokens themselves (so we can include them in the output string)
StringTokenizer tokenizer = new StringTokenizer(s, " .,;:?!()[]{}", true);
// a helper class to build the output string; think of it as just a more efficient concat utility
StringBuilder sb = new StringBuilder();
while (tokenizer.hasMoreTokens()) {
String blankWord = blank(tokenizer.nextToken());
sb.append(blankWord);
}
return sb.toString();
}
/**
* Replaces all but the first and last characters in a string with '_'
*/
private String blank(String word) {
// strings of up to two chars will be returned as such
// delimiters will always fall into this category, as they are always single characters
if (word.length() <= 2) {
return word;
}
// no need to iterate through all chars, we'll just get the array
final char[] chars = word.toCharArray();
// fill the array of chars with '_', starting with position 1 (the second char) up to the last char (exclusive, i.e. last-but-one)
Arrays.fill(chars, 1, chars.length - 1, '_');
// build the resulting word based on the modified array of chars
return new String(chars);
}
Here is the contents of a test that validates this implementation, using TestNG:
#Test(dataProvider = "texts")
public void testBlankWords(String input, String expectedOutput) {
assertEquals(blankWords(input), expectedOutput);
}
#DataProvider
public Object[][] texts() {
return new Object[][] {
{"This is a test.", "T__s is a t__t."},
{"This one, again, is (yet another) test!", "T__s o_e, a___n, is (y_t a_____r) t__t!"}
};
}
The main drawback of this implementation is that StringTokenizer requires you to list all the delimiters by hand. With a more advanced implementation, you can consider a delimiter any character that returns false for Character.isAlphabetic(c) or however you decide to define your non-word chars.
P.S.
This could be a "more advanced implementation", as I mentioned above:
static String blankWords(String text) {
final char[] textChars = text.toCharArray();
int wordStart = -1; // keep track of the current word start position, -1 means no current word
for (int i = 0; i < textChars.length; i++) {
if (!Character.isAlphabetic(textChars[i])) {
if (wordStart >= 0) {
for (int j = wordStart + 1; j < i - 1; j++) {
textChars[j] = '_';
}
}
wordStart = -1; // reset the current word to none
} else if (wordStart == -1) {
wordStart = i; // alphabetic characters start a new word, when there's none started already
} else if (i == textChars.length - 1) { // if the last character is aplhabetic
for (int j = wordStart + 1; j < i; j++) {
textChars[j] = '_';
}
}
}
return new String(textChars);
}
No while loop necessary!
Look ahead by 1 character to see if it's a space, or if the current character is a space, in that case you append it. Otherwise you make sure to add the next character (skipNext false).
Always add the last character
public static String blankWords(String s1) {
StringBuilder sb = new StringBuilder();
if(s1.length() > 2) {
Boolean skipNext = false;
for(int x = 0; x < s1.length() - 1; x = x + 1) {
if(s1.charAt(x) == ' ' || s1.charAt(x + 1) == ' ') {
sb.append(s1.charAt(x));
skipNext = false;
}
else {
if(skipNext) {
sb.append('_');
}
else {
sb.append(s1.charAt(x));
skipNext = true;
}
}
}
sb.append(s1.charAt(s1.length() - 1));
return sb.toString();
}
return s1;
}
For the more advanced programmer, use regular expression.
public static String blankWords(String s1) {
return s1.replaceAll("\\B\\w\\B", "_");
}
This correctly keeps the final t, i.e. blankWords("This is a Test.") returns "T__s is a T__t.".
Please help me to identify my mistakes in this code. I am new to Java. Excuse me if I have done any mistake. This is one of codingbat java questions. I am getting Timed Out error message for some inputs like "xxxyakyyyakzzz". For some inputs like "yakpak" and "pakyak" this code is working fine.
Question:
Suppose the string "yak" is unlucky. Given a string, return a version where all the "yak" are removed, but the "a" can be any char. The "yak" strings will not overlap.
public String stringYak(String str) {
String result = "";
int yakIndex = str.indexOf("yak");
if (yakIndex == -1)
return str; //there is no yak
//there is at least one yak
//if there are yaks store their indexes in the arraylist
ArrayList<Integer> yakArray = new ArrayList<Integer>();
int length = str.length();
yakIndex = 0;
while (yakIndex < length - 3) {
yakIndex = str.indexOf("yak", yakIndex);
yakArray.add(yakIndex);
yakIndex += 3;
}//all the yak indexes are stored in the arraylist
//iterate through the arraylist. skip the yaks and get non-yak substrings
for(int i = 0; i < length; i++) {
if (yakArray.contains(i))
i = i + 2;
else
result = result + str.charAt(i);
}
return result;
}
Shouldn't you be looking for any three character sequence starting with a 'y' and ending with a 'k'? Like so?
public static String stringYak(String str) {
char[] chars = (str != null) ? str.toCharArray()
: new char[] {};
StringBuilder sb = new StringBuilder();
for (int i = 0; i < chars.length; i++) {
if (chars[i] == 'y' && chars[i + 2] == 'k') { // if we have 'y' and two away is 'k'
// then it's unlucky...
i += 2;
continue; //skip the statement sb.append
} //do not append any pattern like y1k or yak etc
sb.append(chars[i]);
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(stringYak("1yik2yak3yuk4")); // Remove the "unlucky" strings
// The result will be 1234.
}
It looks like your programming assignment. You need to use regular expressions.
Look at http://www.vogella.com/articles/JavaRegularExpressions/article.html#regex for more information.
Remember, that you can not use contains. Your code maybe something like
result = str.removeall("y\wk")
you can try this
public static String stringYak(String str) {
for (int i = 0; i < str.length(); i++) {
if(str.charAt(i)=='y'){
str=str.replace("yak", "");
}
}
return str;
}