-->

How can I reverse the words in a sentence without

2019-08-27 09:43发布

问题:

This was the interview question:

How to convert Dogs like cats to cats like Dogs ?

My code shows: cats like cats. Where am I making the mistakes?

#include <iostream>
using namespace std;

int main()
{
    char sentence[] = ("dogs like cats");
    cout << sentence << endl;

    int len = 0;

    for (int i = 0; sentence[i] != '\0'; i++)
    {
        len++;
    }
    cout << len << endl;

    char reverse[len];
    int k = 0;

    for (int j = len - 1; j >= 0; j--)
    {
        reverse[k] = sentence[j];
        k++;
    }

    cout << reverse << endl;

    int words = 0;
    char str[len];

    for (int l = 0; reverse[l] != '\0'; l++)
    {
        if (reverse[l] == ' ' || reverse[l] == '\0') // not sure about this part
        {
            for (int m = l; m >= 0; m--)
            {
                str[words] = reverse[m];
                words++;
            }
        }
    }

    cout << str;

    return 0;
}

I know you can do this using pointers, stack, vectors... but interviewer was not interested in that!

回答1:

This is a fixed version of your sample code:

  • Your principal problem is that every time you found and ' ' or '\0' you copy the bytes of the reverse string from the beginning to that point. Example in loop 5 you copy from index 0-5 (stac) from reverse to str in reverse order, but in in loop 10 you copy from index 0-10 (stac ekil) from reverse to str in reverse order, until here you have already the printed result string ('cats like cats'), and the same in loop 15 all of this incrementing the index of str, in the last loop you are written pass the end of the valid memory of str (and because of that not printed as output).
  • You need to keep track when end the last word reversed to reverse only the actual word, and not the string from the beginning to the actual index.
  • You don't want to count the special character (' ' and '\0') in the reversing of the words, you would end with cats like\0dogs

Modified sample code provided:

#include <iostream>
using namespace std;

int main() {
    char sentence[] = ("dogs like cats");
    cout << sentence << endl;

    int len = 0;

    for (int i = 0; sentence[i] != '\0'; i++) {
        len++;
    }
    cout << len << endl;

    char reverse[len];
    int k = 0;

    for (int j = len - 1; j >= 0; j--) {
        reverse[k] = sentence[j];
        k++;
    }

    cout << reverse << endl;

    int words = 0;
    char str[len];

    // change here added last_l to track the end of the last word reversed, moved
    // the check of the end condition to the end of loop body for handling the \0
    // case
    for (int l = 0, last_l = 0; ; l++) {
        if (reverse[l] == ' ' || reverse[l] == '\0')
        {
            for (int m = l - 1; m >= last_l; m--) { // change here, using last_t to
                str[words] = reverse[m];            // only reverse the last word
                words++;                            // without the split character
            }
            last_l = l + 1;                         // update the end of the last
                                                    // word reversed
            str[words] = reverse[l];                // copy the split character
            words++;
        }
        if (reverse[l] == '\0')                     // break the loop
            break;
    }

    cout << str << endl;

    return 0;
}

Some code, written with the restriction of using the most simple features of the language.

#include <iostream>

// reverse any block of text.
void reverse(char* left, char* right) {
    while (left < right) {
        char tmp = *left;
        *left = *right;
        *right = tmp;

        left++;
        right--;
    }
}

int main() {
    char sentence[] = "dogs like cats";
    std::cout << sentence << std::endl;

    // The same length calculation as sample code.
    int len = 0;
    for (int i = 0; sentence[i] != '\0'; i++) {
        len++;
    }
    std::cout << len << std::endl;

    // reverse all the text (ex: 'stac ekil sgod')
    reverse(sentence, sentence + len - 1);

    // reverse word by word.
    char* end = sentence;
    char* begin = sentence;
    while (end < sentence + len) {
        if (*end != ' ')
            end++;

        if (end == sentence + len || *end == ' ') {
            reverse(begin, end - 1);
            begin = end + 1;
            end = begin;
        }
    }

    std::cout << sentence << std::endl;

    return 0;
}


回答2:

Dissecting your algorithm in pieces. First, you find the length of the string, not including the null char terminator. This is correct, though could be simplified.

size_t len = 0;
for (int i = 0; sentence[i] != '\0'; i++) {
    len++;
}
cout << len << endl;

This could easily be written simply as:

size_t len = 0;
while (sentence[len])
    ++len;

Next, you reverse the entire string, but the first defect surfaces. The VLA (variable length array) you declare here, (which you don't need and shouldn't use, as it is a C++ extension and non-standard) does not account for, nor set, a terminating null-char.

char reverse[len]; // !! should be len+1
int k = 0;
for (int j = len - 1; j >= 0; j--) {
    reverse[k] = sentence[j];
    k++;
}
// !! Should have reverse[k] = 0; here.
cout << reverse << endl; // !! Undefined-behavior. no terminator.

This temporary buffer string is not needed at all. There is no reason you can't do this entire operation in-place. Once we calculate len correctly, you simply do something like the following to reverse the entire sequence, which retains the null char terminator in proper position:

// reverse entire sequence
int i = 0, j = len;
while (i < j--)
{
    char c = sentence[i];
    sentence[i++] = sentence[j];
    sentence[j] = c;
}

Next we move to where you try to reverse each internal word. Again, just as before, the buffer length is not correct. It should be len+1. Worse (hard to imagine), you never remember where you left off when finding the end point of a word. That location should be the next point you start checking for, and skipping, whitespace. Without retaining that you copy from current point all the way back to the beginning of the string. which essentially blasts cats over dogs.

int words = 0;
char str[len]; // !! should be len+1
for (int l = 0; reverse[l] != '\0'; l++) 
{
    if (reverse[l] == ' ' || reverse[l] == '\0') // not sure about this part
    {
        for (int m = l; m >= 0; m--) {
            str[words] = reverse[m];
            words++;
        }
    }
}
cout << str; //!! Undefined behavior. non-terminated string.

Once again, this can be done in-place without difficulty at all. One such algorithm looks like this (and notice the loop that reverses the actual word is not-coincidentally the same algorithm as reversing our entire buffer):

// walk again, reversing each word.
i = 0;
while (sentence[i])
{
    // skip ws; root 'i' at beginning of word
    while (sentence[i] == ' ') // or use std::isspace(sentence[i])
        ++i;

    // skip until ws or eos; root 'j' at one-past end of word
    j = i;
    while (sentence[j] && sentence[j] != ' ') // or use !std::isspace(sentence[j])
        ++j;

    // remember the last position
    size_t last = j;

    // same reversal algorithm we had before
    while (i < j--)
    {
        char c = sentence[i];
        sentence[i++] = sentence[j];
        sentence[j] = c;
    }

    // start at the termination point where we last stopped
    i = last;
}

Putting It All Together

Though considerably simpler to use pointers than all these index variables, the following will do what you're attempting, in place.

#include <iostream>

int main()
{
    char s[] = "dogs like cats";
    std::cout << s << '\n';

    size_t len = 0, i, j;
    while (s[len])
        ++len;

    // reverse entire sequence
    i = 0, j = len;
    while (i < j--)
    {
        char c = s[i]; // or use std::swap
        s[i++] = s[j];
        s[j] = c;
    }

    // walk again, reversing each word.
    i = 0;
    while (s[i])
    {
        // skip ws; root 'i' at beginning of word
        while (s[i] == ' ') // or use std::isspace
            ++i;

        // skip until ws or eos; root 'j' at one-past end of word
        j = i;
        while (s[j] && s[j] != ' ') // or use !std::isspace
            ++j;

        // remember the last position
        size_t last = j;
        while (i < j--)
        {
            char c = s[i]; // or use std::swap
            s[i++] = s[j];
            s[j] = c;
        }

        // start at last-left posiion
        i = last;
    }

    std::cout << s << '\n';
    return 0;
}

Output

dogs like cats
cats like dogs


回答3:

My advise would be to break up the original string into an array of words, reverse that array. Then add those words to your reversed sentence with a space in between.



回答4:

Since they asked for no libraries, I assumed no std::string, no vectors, nothing at all and so I wrote it in C.. the only thing used is printf. Everything else is from scratch :l

The idea is that you reverse the array first. Then split the array by space and reverse each word.

Example: http://ideone.com/io6Bh9

Code:

#include <stdio.h>

int strlen(const char* s)
{
    int l = 0;
    while (*s++) ++l;
    return l;
}


void reverse(char* str)
{
    int i = 0, j = strlen(str) - 1;

    for(; i < j; ++i, --j)
    {
       str[i] ^= str[j];
       str[j] ^= str[i];
       str[i] ^= str[j];
    }
}

void nulltok(char* str, char tok, int* parts)
{
    int i = 0, len = strlen(str);
    *parts = 1;

    for (; i < len; ++i)
    {
        if (str[i] == tok)
        {
            str[i] = '\0';
            ++(*parts);
        }
    }
}

char* reverse_sentence(char* str)
{
    char* tmp = str;
    reverse(str);

    int i = 0, parts = 0, len = strlen(str);
    nulltok(str, 0x20, &parts);

    while(parts--)
    {
        reverse(str);
        str += strlen(str) + 1;
    }

    for(; i < len; ++i)
        if (tmp[i] == '\0')
            tmp[i] = 0x20;

    return tmp;
}


int main(void)
{
    char str[] = "dogs like cats";
    printf("%s", reverse_sentence(str));

    return 0;
}


回答5:

My solution

#include<iostream>
#include<string.h>
using namespace std;
int main()
{
string str;
cout<<"enter the sentence"<<endl;
getline(cin,str);
char* pch;
pch = strtok((char*)str.c_str()," ");
string rev = "";

while(NULL != pch)
{
    rev.insert(0,pch);
    rev.insert(0," ");
    pch = strtok(NULL," ");
}
cout<<"the reversed string is :"<<rev<<endl;
return 0;

}