I am attending a coding competitions in February and I'm looking at last years problem set. The task I am trying to do is this. As input i am given an integer N(maximum 10,000,000), and a string S of length N. The string consists of only the letters 'x', 'y' and 'z'.
These letter are all 'commands'. You start out with a word consisting of only the letter 'A'. If you get a command 'x' you will add an 'A' to the back of you word.
Command'y' will add a 'B' at the front of the word. And 'y' will flip the word. So an input of N = 3, and S = "xyz", will make the word be "AAB". x: add 'A' ,y: add 'B' and z: flip the entire word.
This entire thing has to be done in under 2 seconds, which seems to be a problem for me to achieve.
I hope all that was understandable...
Well, the solution explanation said that a double-ended queue would be the most efficient way to do this, but it I can't get it any lower than a little more than 10 seconds execution time. Could someone please help me find a way to optimize this code.
using namespace std;
int main() {
int num = 10000000;
string commands = "";
bool reversed = false;
deque<char>word = { 'A' };
// just for generating the input. The real program would not need this
for (int i = 0; i < num / 5; i++) {
commands += "xyzxy'";
}
//cin >> num >> commands;
for (int i = 0; i < num; i++) {
if (commands.at(i) == 'x') { //If the command is 'x' add an A at the back
if (!reversed)
word.push_back('A');
else // if it the word is reversed, reverse the command
word.push_front('A');
}
else if (commands.at(i) == 'y') { //If the command is 'y', add a 'B' at the front
if (!reversed)
word.push_front('B');
else // if it the word is reversed, reverse the command
word.push_back('B');
}
else if (commands.at(i) == 'z') { // If the command is 'z' set the status to reversed/!reversed
reversed = !reversed;
}
}
if (reversed)
reverse(word.begin(), word.end());
for (int i = 0; i < word.size(); i++) { // print out the answer
cout << word.at(i);
}
system("pause");
return 0;
}
Thank you!
Usually we expect that a server executes 10^6 commands per second. Bearing in mind that N=10,000,000 and time limit is 2 seconds, O(n) complexity is required.
You can achieve that using the following technique:
Use a double linked list.
Use a boolean variable, for example flag, that gives true if result string starts at the front of linked list and false if the string starts at the back of the linked list.
For each character of input string push_back if given character is x, push_front if given character is y and change the value of flag.
When reading input finishes, print the string respectively to flag value.
Complexity: O(n)
The trick has two parts.
Use std::deque, which is optimized for insertion at both ends of the container.
Do not flip the word for the 'z' input. Instead, keep track of how many times 'z' was read, and change the code that adds A and B so that if the number of times is odd, the character is added to the other end of the word.
At the conclusion of the input, flip the string just once, if the final count is odd.
The main trick is to avoid wasting time flipping the string over and over again. You only need to flip it at most once.
Observation: push_back on vector is faster than both push_back and push_front on deque.
If you use two vectors (front and back) and treat the front as if it is reversed, then you can also eliminate all of the 'if (reverse)...' stuff by using push_back on the back vector for the x, push_back on the front vector for the y, and swapping the vectors when you hit a z.
Then when outputting the result, do a reverse iteration through the front vector and a forward iteration through the back vector.
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <string>
#include <chrono>
int main(int argc, char* argv[])
{
auto fileIn = std::ifstream{"flip.in"};
int commandSize;
std::string commands;
fileIn >> commandSize >> commands;
std::vector<char> wordFront;
std::vector<char> wordBack;
wordFront.push_back('A');
auto start = std::chrono::high_resolution_clock::now();
for (auto v : commands)
{
switch(v)
{
case 'x':
wordBack.push_back('A');
break;
case 'y':
wordFront.push_back('B');
break;
case 'z':
std::swap(wordFront, wordBack);
break;
}
}
auto finish = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = finish - start;
std::cout << "Elapsed time (secs): " << elapsed.count() << std::endl;
auto fileOut = std::ofstream{"flip.out"};
std::for_each(crbegin(wordFront), crend(wordFront), [&fileOut](auto v){ fileOut << v; });
std::for_each(cbegin(wordBack), cend(wordBack), [&fileOut](auto v){ fileOut << v; });
}
It didn't make a positive difference on my machine, but you could also reserve some space on the front and back vectors before you start building the word.
Related
This question already has answers here:
Converting integer into array of digits [closed]
(4 answers)
Closed 2 years ago.
I know there are similar questions on Stack overflow about this. I already checked them. Here are two points:
The number will be an input from the user, so I won't know how many digits the number may actually contain
I DON'T want to directly print the digits, I need to do some further process on every digit, so I need a way to save or assign every single digit.
So fore example, if the user enters 1027, I'll need 1, 0, 2, 7 returned, and not printed. That's where my problem starts.
Here's how I'd write it if it could be printed:
int x;
cin>>x;
while(x != 0)
{
cout<<x%10<<endl;
x/=10;
}
Any hints or help is in advance appreciated.
It depends what order you need it in. If you need least-significant digit (rightmost) to most-significant (leftmost), then your solution is almost there
int x = ...
while(x != 0)
{
int current = x % 10; // get rightmost digit
x /= 10;
// process 'current', or store in a container for later processing
}
If you need most-significant (leftmost) to least-significant (rightmost), then you can do this recursively:
void for_each_digit(int input)
{
// recursive base case
if (input == 0) { return; };
int current = input % 10
for_each_digit(input / 10); // recurse *first*, then process
// process 'current', add to container, etc
}
// ...
int x = ...
for_each_digit(x);
Edit: I apparently missed the part about returning a sequence of digits.
Either approach works. If you go right-to-left, you will need to reverse the container first. If you go recursive, you will need to append each value to the container.
Use a std::string:
std::string input;
std::cin >> input;
Now input[i] is the i-th digit. input.size() is the number of digits.
Well you can use vector. It can take variable length input. You need not declare the size beforehand. Learn more about vector here: vector
#include<iostream>
#include<vector>
#include <algorithm> // std::reverse
using namespace std;
int main(void)
{
vector<int>digits;
int x;
cin >> x;
while(x)
{
digits.push_back(x % 10);
x = x / 10;
}
// reversing the order of the elements inside vector "digits" as they are collected from last to first and we want them from first to last.
reverse(digits.begin(), digits.end());
// Now the vector "digits" contains the digits of the given number. You can access the elements of a vector using their indices in the same way you access the elements of an array.
for(int i = 0; i < digits.size(); i++) cout << digits[i] << " ";
return 0;
}
You may try std::vector<int> to store unknown number of integers as shown:
#include <iostream>
#include <vector>
int main(void) {
std::vector<int> digits;
std::string s;
std::cout << "Enter the number: ";
std::cin >> s;
size_t len = s.length();
for (size_t i = 0; i < len; i++) {
digits.push_back(s[i] - '0');
}
// Comment next 3 code to stop getting output
for (size_t i = 0; i < len; i++)
std::cout << digits[i] << ' ';
std::cout << std::endl;
return 0;
}
Note: This approach doesn't performs any mathematical operations (i.e. operation of division and getting remainders). It simply stores each integer in a vector using a for loop.
Below is my full code:
#include<iostream>
#include<string.h>
int main()
{
char x[100];
int i, pos=0, l;
std::cout<<"Enter a string: ";
gets(x);
l=strlen(x);
for(i=0;i<l;i++)
{
if(x[i]==' ')
pos+=1;
x[i]=x[i+pos];
if(x[i]=='\0')
break;
}
std::cout<<"The final string is: "<<x;
fflush(stdin);
getchar();
return 0;
}
But the output is not coming as expected...
Would be very happy if someone pointed out the mistake...
I would simplify your code to something like this:
#include<iostream>
int main()
{
char x[100];
std::cout<<"Enter a string: ";
gets(x);
std::cout << "The final string is: ";
for(int i = 0; i < 100; i++)
{
if(x[i] == '\0') break;
while(x[i] == ' ') i++;
std::cout << x[i];
}
getchar();
return 0;
}
Final check for break should be:
if(X[i+pos] == '\0')
break;
The issue is that you keep checking the place you want to copy to for ' ', not the place you want to copy from.
So it should be while(x[i+pos]==' ') instead of if(x[i]==' ') (while instead of if to be able to handle multiple spaces in a row)
You can easily find these kind of errors by stepping through your code with a debugger and checking the values of all relevant variables to see if they match your expectations.
If your goal is to change the string, then the strategy could be
1) Find the first space character. If no space character was found, we can end processing. If a space is found, then this is the first position to change and the first position to check (these are distinct positions, even though they start out at the same place).
2) Process the string using these two indices, one pointing to the position to check, and the other pointing to the position to change.
3) If the position to check is not a space, copy it to the position to change and increment the position to change.
4) Increment the position to check. Go to step 3) until the end of string is encountered (position to check is at the end-of-string).
5) Null terminate the string at the position to change. Done.
Here is an implementation of the above:
#include <iostream>
#include <cstring>
int main()
{
char str[] = "Hello, my name is John";
size_t len = strlen(str);
size_t change_position = 0;
// find the first space character
for (change_position = 0; change_position < len; ++change_position)
{
if (str[change_position] == ' ')
break; // found it
}
if (change_position != len) // if we found a space character
{
// starting from the first space
for (size_t check_position = change_position; check_position < len; ++check_position)
{
// if the check position is not a space
if (str[check_position] != ' ')
{
// copy it to the change position
str[change_position] = str[check_position];
// update change position
++change_position;
}
}
}
// finish up by null-terminating the string
str[change_position] = '\0';
std::cout << str;
}
Live Example
For my first part of the question, We have given a long string of input and we have to count the occurrence for it.
For eg.
Input = AXBHAAGHXAXBH
Find = AXBH
Output = 2
This can be achieved by using the string.find("term") loop. Eg.
#include <string>
#include <iostream>
int main()
{
int occurrences = 0;
std::string::size_type pos = 0;
std::string inputz = "AXBHAAGHXAXBH";
std::string target = "AXBH";
while ((pos = inputz.find(target, pos )) != std::string::npos) {
++ occurrences;
pos += target.length();
}
std::cout << occurrences << std::endl;
}
However, I am not sure how to do the second part where, it needs to take into account the random structure:
Random structure refers to any orientation of our find. Important note: The find occurrences are always grouped together but can have different structure.
I do not want to use cases because some sample find are too big eg. Find AXBHNMB would have too many cases to consider and would prefer a more general approach.
Eg. AXBH is find, then AXHB is also acceptable for the occurence
A proper example:
Input = AXBHAAGHXAXBH**ABHX**NBMN**AHBX**
Find = AXBH
Output = 4
Prefer if you please code it for the given example with link to explanation/explanation to any new function you use.
You are correct that checking all permutations would take a lot of time. Fortunately we don't need to do that. What we can do is store the string to find in a std::map<char, int>/std::unordered_map<char, int> and then grab sub strings from the string to search through, convert those into the same type of map and see if those maps are equal. This lets use compare without caring about the order, it just makes sure we have the correct amount of each character. So we would have something like
int main()
{
std::string source = "AHAZHBCHZCAHAHZEHHAAZHBZBZHHAAZAAHHZBAAAAHHHHZZBEWWAAHHZ ";
std::string string_to_find = "AAHHZ";
int counter = 0;
// build map of the characters to find
std::unordered_map<char, int> to_find;
for (auto e : string_to_find)
++to_find[e];
// loop through the string, grabbing string_to_find chunks and comparing
for (std::size_t i = 0; i < source.size() - string_to_find.size();)
{
std::unordered_map<char, int> part;
for (std::size_t j = i; j < string_to_find.size() + i; ++j)
++part[source[j]];
if (to_find == part)
{
++counter;
i += string_to_find.size();
}
else
{
++i;
}
}
std::cout << counter;
}
A naive approach is to iterate over the given string and searching the target string.
In each chunk, we need to sort the portion and compare if it matches with the target string.
#include <string>
#include <iostream>
#include <algorithm>
int main()
{
int occurrences = 0;
std::string::size_type pos = 0;
std::string inputz = "AXBHAAGHXAXBH**ABHX**NBMN**AHBX**";
std::string target = "AXBH";
std::sort(target.begin(), target.end());
int inputz_length = inputz.length();
int target_length = target.length();
int i=0;
for(i=0; i<=inputz_length-target_length; i++)
{
std::string sub = inputz.substr(i, target_length);
std::sort(sub.begin(), sub.end());
if (target.compare(sub) == 0)
{
std::cout << i<<"-->"<< target<<"-->" << sub << std::endl;
occurrences++;
i=i+target_length;
}
}
std::cout << occurrences << std::endl;
return 0;
}
Output:
0-->ABHX-->ABHX
9-->ABHX-->ABHX
15-->ABHX-->ABHX
27-->ABHX-->ABHX
4
Extra function: Uses sort function from algorithm header file.
Time complexity: more than O(n2)
One solution is to find a canonical representation for both the search string and a substring. Two fast approaches are possible.
1) Sort the substring.
2) Calculate a histogram of letters.
Option 2 can be calculated incrementally by incrementing histogram bins for the incoming letters and decrementing the bins for outgoing letters in the search window.
While updating the histogram bin, one can also check if this particular update toggles the overall matching:
// before adding the incoming letter
if (h[incoming] == target[incoming]) matches--;
else if (++h[incoming] == target[incoming]) matches++;
// before subtracting outgoing letter
if (h[outgoing] == target[outgoing]) matches--;
else if (--h[outgoing] == target[outgoing]) matches++;
if (matches == number_of_unique_letters) occurences++;
Then the overall complexity becomes O(n).
Background to this: This is not homework, it's completely optional review for a basic c++ class. As I want to pass, I'm going through each example the best I can, This one I'm super stuck on, and have been for about three hours now.
Problem: Write a function to return a string composed of the most frequent lowercase letter found in each row of a 10 x 10 array of lowercase alphabetic chars in the range a through z.
If there is more than one most frequent character, use the one that come first alphabetically.
Use neither cin nor cout.
#include <iostream>
#include <string>
using namespace std;
string mostFrequent(char c[10][10]){
// this is the function I need to create
}
int main(){
char c[10][10] = {
'a','b','f','d','e','f','g','h','i','j',
'a','b','c','r','c','r','g','h','r','j',
'a','b','c','d','e','f','g','h','o','o',
'z','w','p','d','e','f','g','h','i','j',
'o','d','o','d','o','b','o','d','o','d',
'a','l','l','d','e','f','f','h','l','j',
'a','b','c','d','i','f','g','h','i','j',
'a','b','z','v','z','v','g','g','v','z',
'a','b','c','d','e','f','g','h','i','e',
'a','b','s','d','e','f','g','h','s','j',
};
cout << mostFrequent(c) << endl;
return 0;
}
So in research for this I found some material that allows me to count how many times a specific int or char would appear inside the array, but it doesn't quite suit the needs of the problem as it needs to return a string composed of the most frequent character. See below.
int myints[] = {10,20,30,30,20,10,10,20};
int mycount = std::count (myints, myints+8, 10);
Because it doesn't work though, I was thinking a for loop, to go row to row, I'll mostly likely need to save things into an array to count, but I'm not sure at all how to implement something like that. I even considered a caesar shift with an array, but I'm not sure where to go if that is the solution.
If I understood the task correctly, you have a matrix 10x10 and you have to create a string of length 10, where character at position i is the one that is most frequent among characters in the row i.
string mostFrequent(char c[10][10]) {
// The idea here is to find the most common character in each row and then append that character to the string
string s = "";
for (int i = 0; i < 10; i++) s += findMostFrequentCharacter(c[i]);
return s;
}
Now we just have to implement a function char findMostFrequentCharacter(char c). We are going to do that by counting all of the characters and picking the one that is most frequent (or it comes alphabetically before if there is more than one most frequent character):
char findMostFrequentCharacter(char c[10]) {
int counts[256]; // Assuming these are ASCII characters, we can have max 256 different values
// Fill it with zeroes (you can use memset to do that, but for clarity I'll write a for-loop
for (int i = 0; i < 256; i++) c[i] = 0;
// Do the actual counting
for (int i = 0; i < 10; i++) // For each character
counts[ c[i] ]++; // Increase it's count by 1, note that c[i] is going to have values between 65 (upper case A) and 122 (lower case z)
char mostFrequent = 0;
// Find the one that is most frequent
for (char ch = 'A'; ch <= 'z' ch++) // This will ensure that we iterate over all upper and lower case letters (+ some more but we don't care about it)
if (counts[ch] > counts[c]) c = ch; // Take the one that is more frequent, note that in case they have the same count nothing will happen which is what we want since we are iterating through characters in alphabetical order
return c;
}
I have written the code out of my head so I'm sorry if there are any compile errors.
I am using two dynamic arrays to read from a file. They are to keep track of each word and the amount of times it appears. If it has already appeared, I must keep track in one array and not add it into the other array since it already exists. However, I am getting blank spaces in my array when I meet a duplicate. I think its because my pointer continues to advance, but really it shouldn't. I do not know how to combat this. The only way I have was to use a continue; when I print out the results if the array content = ""; if (*(words + i) == "") continue;. This basically ignores those blanks in the array. But I think that is messy. I just want to figure out how to move the pointer back in this method. words and frequency are my dynamic arrays.
I would like guidance in what my problem is, rather than solutions.
I have now changed my outer loop to be a while loop, and only increment when I have found the word. Thank you WhozCraig and poljpocket.
Now this occurs.
Instead of incrementing your loop variable [i] every loop, you need to only increment it when a NEW word is found [i.e. not one already in the words array].
Also, you're wasting time in your inner loop by looping through your entire words array, since words will only exist up to index i.
int idx = 0;
while (file >> hold && idx < count) {
if (!valid_word(hold)) {
continue;
}
// You don't need to check past idx because you
// only have <idx> words so far.
for (int i = 0; i < idx; i++) {
if (toLower(words[i]) == toLower(hold)) {
frequency[i]++;
isFound = true;
break;
}
}
if (!isFound) {
words[idx] = hold;
frequency[idx] = 1;
idx++;
}
isFound = false;
}
First, to address your code, this is what it should probably look like. Note how we only increment i as we add words, and we only ever scan the words we've already added for duplicates. Note also how the first pass will skip the j-loop entirely and simply insert the first word with a frequency of 1.
void addWords(const std::string& fname, int count, string *words, int *frequency)
{
std::ifstream file(fname);
std::string hold;
int i = 0;
while (i < count && (file >> hold))
{
int j = 0;
for (; j<i; ++j)
{
if (toLower(words[j]) == toLower(hold))
{
// found a duplicate at j
++frequency[j];
break;
}
}
if (j == i)
{
// didn't find a duplicate
words[i] = hold;
frequency[i] = 1;
++i;
}
}
}
Second, to really address your code, this is what it should actually look like:
#include <iostream>
#include <fstream>
#include <map>
#include <string>
//
// Your implementation of toLower() goes here.
//
typedef std::map<std::string, unsigned int> WordMap;
WordMap addWords(const std::string& fname)
{
WordMap words;
std::ifstream inf(fname);
std::string word;
while (inf >> word)
++words[toLower(word)];
return words;
}
If it isn't obvious by now how a std::map<> makes this task easier, it never will be.
check out SEEK_CUR(). If you want to set the cursor back
The problem is a logical one, consider several situations:
Your algorithm does not find the current word. It is inserted at position i of your arrays.
Your algorithm does find the word. The frequency of the word is incremented along with i, which leaves you with blank entries in your arrays whenever there's a word which is already present.
To conclude, 1 works as expected but 2 doesn't.
My advice is that you don't rely on for loops to traverse the string but use a "get-next-until-end" approach which uses a while loop. With this, you can track your next insertion point and thus get rid of the blank entries.
int currentCount = 0;
while (file)
{
// your inner for loop
if (!found)
{
*(words + currentCount) = hold;
*(frequency + currentCount) = 1;
currentCount++;
}
}
Why not use a std::map?
void collect( std::string name, std::map<std::string,int> & freq ){
std::ifstream file;
file.open(name.c_str(), std::ifstream::in );
std::string word;
while( true ){
file >> word; // add toLower
if( file.eof() ) break;
freq[word]++;
}
file.close();
}
The problem with your solution is the use of count in the inner loop where you look for duplicates. You'll need another variable, say nocc, initially 0, used as limit in the inner loop and incremented whenever you add another word that hasn't been seen yet.