Below is a solution I came up with (in C++) for an algorithm that is supposed to find the first character in a string that only appears once in the input string (the input string is guaranteed to be made up of only lower case characters from English alphabet). The catch is that the algorithm must return the index of that character from the original string, or negative one (-1) if there is no such character.
The solution below is O(n) in time and O(1) in space (const space because the list and the map can never have more than 26 entries).
- I'm not convinced that my use of data structures is precise/clean.
- I'm not sure if the code is very readable as is. Would it be more effective to write small functions that say what they do to replace things like:
map_char_to_tracking[s[i]].char_count_in_string == 1
[so the reader can read at a higher level of abstraction without having to know the details of the data structures]?
Any improvements would be much appreciated.
#include <iostream>
#include <list>
#include <unordered_map>
typedef struct
{
int char_index_in_original_string;
std::list<char>::iterator char_position_in_list_itr;
int char_count_in_string;
}
Tracking;
// O(n) time | O(1) space, where n is the number of characters in the input string.
int FirstOccurrenceOfUniqueChar(const std::string& s)
{
std::list<char> unique_chars;
std::unordered_map<char, Tracking> map_char_to_tracking;
for (int i = 0; i < s.size(); ++i)
{
if (map_char_to_tracking[s[i]].char_count_in_string == 1)
{
// char has appeared once in string already, so remove it from unique chars list
++map_char_to_tracking[s[i]].char_count_in_string;
unique_chars.erase(map_char_to_tracking[s[i]].char_position_in_list_itr);
}
else if (map_char_to_tracking[s[i]].char_count_in_string == 0)
{
// char hasn't appeared in string yet, so add it to unique chars list and map.
unique_chars.push_back(s[i]);
map_char_to_tracking[s[i]] = Tracking{ i, --unique_chars.end(), 1 };
}
}
if (unique_chars.empty())
{
return -1;
}
return map_char_to_tracking[unique_chars.front()].char_index_in_original_string;
}
int main()
{
std::string s1{ "abcdeabcd" };
std::string s2{ "v" };
std::string s3{ "" };
std::string s4{ "aabbcc" };
std::cout << FirstOccurrenceOfUniqueChar(s1) << std::endl; // expect 4
std::cout << FirstOccurrenceOfUniqueChar(s2) << std::endl; // expect 0
std::cout << FirstOccurrenceOfUniqueChar(s3) << std::endl; // expect -1
std::cout << FirstOccurrenceOfUniqueChar(s4) << std::endl; // expect -1
return 0;
}
below is O(n) in space…
yes…and time
If every char in the string is duplicated, you get up to alphabet size calls tounique_chars.erase()
. O(1), but maybe notable. By the same token, additional space is O(1)… \$\endgroup\$