Remove Duplicates: Space Complexity for this Code - java

Given a string without spaces, the task is to remove duplicates from it.
Note: The original order of characters must be kept the same.
Example:
Input: S = "zvvo"
Output: "zvo"
Explanation: only keep the first
occurrence
I've written the following code, and I want to know its Space complexity.
public class Solution {
public String removeDups(String s) {
StringBuilder sb = new StringBuilder();
for(int i = 0; i< s.length(); i++){
String ch = Character.toString(s.charAt(i));
if (sb.indexOf(ch) == -1)
sb.append(ch);
}
return sb.toString();
}
}

Space complexity of your solution is O(n) since it requires allocating in memory an instance of StringBuilder which would contain up to n characters, and transforming it into a String of the same length.
These memory costs are fine, but time complexity of your solution can be improved. Presented code runs in O(n^2) because method indexOf() performs iteration over the characters in your StringBuilder instance under the hood, and it's being invoked for every character in the given string.
To reduce the time complexity you can maintain a HashSet, and offer each character to the Set, if the character gets rejected it would mean that it was encountered before.
Here's how it might be implemented:
public String removeDups(String s) {
Set<Character> seen = new HashSet<>();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (seen.add(ch)) sb.append(ch);
}
return sb.toString();
}
Although we are using some additional memory, space complexity remains the same O(n) (because constants are not taken into consideration while evaluating the algorithm's complexity, 2*n and 3*n would result in O(n)). And time complexity would be linear O(n).

Related

Why is the space complexity for this algorithm to check if an array has all unique characters O(n)?

In the book "Cracking the Coding Interview", first exercise says "Implement an algorithm to determine if a string has all unique characters (not using additional data structures)". And the solution:
public static boolean isUniqueChars(String str) {
boolean [] char_set = new boolean[256];
for (int i = 0; i < str.length(); i++) {
int val = str.charAt(i);
if (char_set[val]) return false;
char_set[val] = true;
}
return true;
}
Then they say "Time complexity is O(n), where n is the length of the string, and space complexity is O(n)".
I don't see why space complexity is O(n). The array char_set has a constant length, independent on how long the given str is. To me, space complexity is O(1).
Its space complexity is O(1) (\Theta(1)) as it keeps 256 (constant) bits more than the size of the input array. Also, the time complexity is O(1) as there are 256 chars to be checked in the input string and the duplicate will be detected at most at 256th character of the string.

Why does using arrays instead of string give less memory consumption and time executing?

Given the string in the form of char array. Modify it the way that all the exclamation point symbols '!' are shifted to the start of the array, and all ohters are in the same order. Please write a method with a single argument of type char[]. Focus on either memory and time consumption of alghoritm.
Feedback that i've received: it was possible to use working with arrays instead of strings. Where can i find info about memory?
public static String formatString(char[] chars) {
StringBuilder exclamationSymbols = new StringBuilder();
StringBuilder otherSymbols = new StringBuilder();
for (char c : chars) {
if (c == '!') {
exclamationSymbols.append(c);
} else {
otherSymbols.append(c);
}
}
return (exclamationSymbols.toString() + otherSymbols.toString());
}
You can do this faster using a char[] than a StringBuilder because:
a StringBuilder is just a wrapper around a char[], so there's no way it can be faster. The indirection means it will be slower.
you know exactly how long the result will be, so you can allocate the minimum-sized char[] that you'll need. With a StringBuilder, you can pre-size it, but with two StringBuilders you can't exactly, so you either have to over-allocate the length (e.g. make both the same length as chars) or rely on StringBuilder resizing itself internally (which will be slower than not; and it uses moer memory).
My idea would be to use two integer pointers to point to the next position that you'll write a char to in the string: one starts at the start of the array, the other starts at the end; as you work your way through the input, the two pointers will move closer together.
Once you've processed the entire input, the portion of the result array corresponding to the "end pointer" will be backwards, so reverse it.
You can do it like this:
char[] newChars = new char[chars.length];
int left = 0;
int right = chars.length;
for (char c : chars) {
if (c == '!') {
newChars[left++] = c;
} else {
newChars[--right] = c;
}
}
// Reverse the "otherSymbols".
for (int i = right, j = newChars.length - 1; i < j; ++i, --j) {
char tmp = newChars[i];
newChars[i] = newChars[j];
newChars[j] = tmp;
}
return new String(newChars);
Ideone demo

Space complexity of Partition Label Problem

A string S of lowercase letters is given. We want to partition this string into as many parts as possible so that each letter appears in at most one part, and return a list of integers representing size of each part.
Input: S = "ababcbacadefegdehijhklij"
Output: [9,7,8]
Explanation:
The partition is "ababcbaca", "defegde", "hijhklij".
This is a partition so that each letter appears in at most one part.
A partition like "ababcbacadefegde", "hijhklij" is incorrect, because it splits S into less parts.
Below is my Code for the above problem:
class Solution {
public List<Integer> partitionLabels(String S) {
char[] st = S.toCharArray();
int k=0,c=0;
List<Integer> res = new ArrayList<Integer> ();
Set<Integer> visited = new HashSet<Integer> ();
for(int i=0 ; i<st.length ; i++)
{
int idx = S.lastIndexOf(st[i]);
if(visited.add(i) && idx>i && idx>k)
{
k = Math.max(k,idx);
visited.add(k);
}
else if(i == k)
{
res.add(i-c+1);
c=i+1;
k++;
}
}
return res;
}
}
The above code works and the time complexity of the above code in O(n) since it visits each element once.
But what is the space complexity? Since I am using a Char array whose size is the same as the the String S and a Set whose Max size can be the size of the String S, is it also O(n)?
As you only use one dimensional arrays and lists your space complexity is O(n).
But your time complexity is O(n²) because S.lastIndexOf(st[i]); is O(n).
If you wanted also time to be O(n) you have to pre-process the string once (= O(n)) to determine the last occurence of each character, f.i. with a map to keep the retrieving time O(1).

Java simple application Complexity

I had to resolve a problem for an interview, and i didn't understood something.
The problem was, for a string like "we like hiking" to reverse the string like this: "ew ekil gnikih", this means, to explode the string like: str.split(" ") and then, each word to reverse and to add to the new string.
The problem was at the end of the problem, because was asking something about Complexity, like:
" - expected worst-case time complexity is O(N)"
- expected wors-case space complexity is O(N) (not counting the storage required for input arguments)
I resolved the problem like this:
public static String solution(String S){
if(S.length()>1 && S.length()<200000){
String toReturn = "";
String[] splitted = null;
if(S.contains(" ")){
splitted = S.split(" ");
}
else{
splitted = new String[]{S};
}
for(int i=0;i<=splitted.length-1;i++){
String wordToChange = splitted[i];
String reversedWord = "";
for(int j=wordToChange.length()-1;j>=0;j--)
reversedWord+=wordToChange.charAt(j);
toReturn+=" " + reversedWord;
}
return toReturn.trim();
}
return null;
}
What i should do about the complexity request?
Thanks!
About time complexity:
Worst-case O(N) means that you should have a worst case scenario in which you compute N iteration in order to get the result, in your case you split the String into an array of Strings [operation that probably takes O(N), but it depends on the method split()], then for each String in the array you start from the end of the String and invert the characters.
So your worst case is O(N) + O(N)... = O(N)
About space complexity:
Each String is an object and you reserve space in memory for each String you create, here you have an array of Strings, means that you create an object for each word or letter you split from the original string.
You worst case is a string like "I a m t h e o n e" in which you create a String for each character -> O(N)
Hope this clarifies your doubts
I think this is not the correct answer for at least the time complexity.
In my opinion the splitting of a String will most likely be a complexity of O(n). And it'll just add to next complexity, therefore we can forget about it.
Now you're creating an empty String and concatenating a char on top of each String. Here you're assuming this operation is as cheap like inserting a value into an array (O(1)).
In this case you should assume, that you're creating a new String each time with the member method concat(). If you're have the chance of looking into the implementation of String.concat(String) you'll see that this method is creating another char[] in the length of string1.length + string2.length and is copying both string1 and string2 into it. So this method alone has a complexity of O(n).
Because you're concatenating n chars for one word of n length, it has the complexity of O(n * (n / 2)) = O(n^2) for one word. Assuming the worst case of just one word, you have the complexity class of O(n^2).
I would concur with the space complexity class of O(n) for your code. But this is just under the assumption of the intelligence of the JVM reusing you're unused space.
Your code should work, but I think it is really not good code for this problem. You can improve it with a StringBuilder or direct operations on a char[].
The StringBuilder is a good idea, because it acts similar to a Vector or a ArrayList. I has a much bigger char[] working in its background than a String. If it's capacity is exceeded (by using the append() method), it will create a much bigger array, something like 2 time the size of the previous backing array. This might only be a time complexity of O(nlogn). But you can tweak it furthermore by initializing the StringBuilder with a capacity of the same size as your initial String. This means the backing char[] will not be copied at all, because you'll not exceed it's capacity.
Here is an example with the use of a char[]:
private static void reverseCharArray(char[] result, int start, int end) {
int halfDifference = (end - start) / 2;
for (int i = 0; i < halfDifference; i++) {
char buffer = result[start + i];
result[start + i] = result[end - i - 1];
result[end - i - 1] = buffer;
}
}
public static String reverseWordInString(String string) {
char[] stringAsCharArray = string.toCharArray();
int indexStart = 0;
for (int indexEnd = 0; indexEnd < stringAsCharArray.length; indexEnd++) {
if (stringAsCharArray[indexEnd] != ' ') {
continue;
}
StringOperations.reverseCharArray(stringAsCharArray, indexStart, indexEnd);
indexStart = indexEnd + 1;
}
StringOperations.reverseCharArray(stringAsCharArray, indexStart, stringAsCharArray.length);
return String.valueOf(stringAsCharArray);
}

Is the time complexity of this code O(N^2)

This is a solution of mine for one of the problems in leetcode. Through my deductions, I concluded it to have an overall O(N^2) time complexity. However, I would like to get a confirmation on this just so that I don't continue making the same mistakes when it comes to judging the time/space complexity of an algorithm.
Oh, and the problem goes as follows:
Given an input string, reverse the string word by word.
e.g. "I am you" == "you am I"
The code is as follows:-
public String reverseWords(String s) {
//This solution is in assumption that I am restricted to a one-pass algorithm.
//This can also be done through a two-pass algorithm -- i.e. split the string and etc.
if(null == s)
return "";
//remove leading and trailing spaces
s = s.trim();
int lengthOfString = s.length();
StringBuilder sb = new StringBuilder();
//Keeps track of the number of characters that have passed.
int passedChars = 0;
int i = lengthOfString-1;
for(; i >= 0; i--){
if(s.charAt(i) == ' '){
//Appends the startOfWord and endOfWord according to passedChars.
sb.append(s.substring(i+1, (i+1+passedChars))).append(" ");
//Ignore additional space chars.
while(s.charAt(i-1) == ' '){
i--;
}
passedChars = 0;
}else{
passedChars++;
}
}
//Handle last reversed word that have been left out.
sb.append(s.substring(i+1, (i+1+passedChars)));
//return reversedString;
return sb.toString();
}
My reasoning for this being an O(N^2) algorithm:-
The loop = O(n)
StringBuilder.append = O(1)
Substring method = O(n) [as of Java 7]
On that note, if anyone else has a better solution than this, please feel free to share it! :)
I was aiming for a one-pass solution and therefore, opted out of splitting the string before the loop.
Appreciate the help!
EDIT: I meant to ask about the time complexity of the portion of the code that contains the loop. I apologize in advance if the question was misleading/confusing. The whole chunk of code is meant for clarification purposes. :)
Time complexity is O(n).
Each insertion (append(x)) to a StringBuilder is done in O(|x|), where |x| is the size of the input string you are appending. (independent of the state of the builder, on average).
Your algorithm iterates the entire string, and use String#substring() for each word in it. Since the words do not overlap, it means you do a substring() for each word one time, and append it to the builder (also once) - giving you 2|x| for each word x.
Summing it up, gives you
T(S) = |S| + sum{2|x| for each word x}
But since sum{|x| for each word x} <= |S|, this gives you total of:
T(S) = |S| + 2sum{|x| for each word x} = |S| + 2|S| = 3|S|
Since |S| is the size of the input (n), this is O(n)
Note that the important part is in jdk7, the substring() method is linear in the size of the output string, not the original one (you copy only the relevant part, not all of the string).
Here is an alternative solution which I believe may perform a little better.
public String reverseWords(String s) {
String[] array = s.split(" ");
int len = array.length;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) {
sb.append(" ").append(array[len - i - 1]);
}
return sb.toString().trim();
}
Amit has already given you detailed explanation on the complexity computation, I would like to give you a simpler version.
In general, if we have nested loops, we consider the complexity to be O(N^2). But this is not the case always, as you have to do some activity n times for each nth part of input. E.g., if you have input of size 3, you have to do some action 3 times on each of the element. Then, you can say that your algorithm has O(n^2) complexity.
Since you are traversing and processing each part of your input string only once (even though you are using nested loops), complexity should be on the order of O(n). For proof, Amit has done quite a job.
Although, I would have used below code to reverse the order of words
String delim = " ";
String [] words = s.split(delim);
int wordCount = words.length;
for(int i = 0; i < wordCount / 2; i++) {
String temp = words[i];
words[i] = words[wordCount - i - 1];
words[wordCount - i - 1] = temp;
}
String result = Arrays.toString(words).replace(", ", delim).replaceAll("[\\[\\]]", "");

Categories