Embercode
✕
Character Arrays and StringsLesson 4 of 16

55 min read

Common string interview problems

Why it matters

String questions in interviews repeat a small set of patterns. The same five or six ideas solve most of them: scan once and count, split into words, walk runs of equal letters, slide a window across the text, and use a stack to match open and close symbols. If you can name the pattern from the problem wording, you are most of the way to a solution.

This lesson works through seven representative problems. For each one you will see the plain-language version, the pattern, the code, the exact output and the cost. You will also learn how to talk through an answer, because interviewers score the explanation as much as the code.

How to approach any string question in an interview

Before writing code, follow the same steps every time. They take a minute or two and prevent most mistakes.

  1. Restate the problem in your own words. Say what goes in and what comes out. "So the input is a line of text, and the output is the index of the first letter that appears once."
  2. Ask about the edge cases. Ask about the empty string, a one-letter string, all letters the same, spaces and punctuation, uppercase, and whether the input is ASCII. Write the answers on the board.
  3. Give an example by hand. Pick a short input with a known answer and work it out yourself. This becomes your first test.
  4. Name the simple solution first. A brute-force method is fine as a starting point. Say its cost, then say how you will improve it.
  5. Choose the pattern and say why. The clue words decide it. Words like "first", "count" or "each letter" point to counters. Words like "consecutive", "run" or "in a row" point to scanning with two indexes. Words like "every substring without" point to a sliding window.
  6. Code it cleanly, then test it. Run your example and the empty string through the code.
  7. State the complexity. Give the time and the extra space, and say why.

The table below lists the patterns this lesson covers.

Clue in the problemPatternTypical cost
Count or classify each letter onceSingle scan with a counterO(n) time, O(1) space
First or only occurrenceCount first, then scan againO(n) time, O(1) space for a fixed alphabet
Words and spacesSplit into words, then rebuildO(n) time, O(n) space
Consecutive equal charactersRun scanning with two indexesO(n) time
Longest or shortest piece with a propertySliding window with two pointersO(n) time
Matching open and close symbolsStackO(n) time, O(n) space
Same letters in a different orderCounters or sorted keyO(n) time or O(n log n)

Problem 1: Count the vowels

The problem. Given a line of text, count the letters a, e, i, o and u in either case. Other characters do not count.

The idea. Walk the text once. For each character, convert it to lowercase, then test whether it is one of the five vowels. The conversion matters: without it, A and a would be treated differently and the count would be wrong for capital letters. This is a single pass, so the time is O(n), and the extra space is one counter.

The program below reads lines until the input ends. Type these three lines in the Input box, pressing Enter after each one: Programming Is Fun, then AEIOU xyz, then an empty line.

C++ · editable

The output is:

5
5
0

Check each line by hand. Programming Is Fun has the vowels o, a, i in Programming, I in Is, and u in Fun, which is five. AEIOU xyz has five vowels in capitals, all of which are counted after lowercasing. The empty line has none, so the count is 0. The while (getline(...)) loop also handles the empty line correctly, because getline returns successfully for an empty line and only fails at the end of the input.

The cast to unsigned char before tolower is the same rule as in the earlier lessons. Without it, a character outside ASCII can be negative on some systems, and the call is undefined.

Problem 2: Reverse the words in a sentence

The problem. Given a sentence with any amount of spaces between words, print the words in reverse order with exactly one space between them. Leading and trailing spaces must not appear in the output. The input the sky is blue should give blue is sky the.

The idea. The easy and clean method is to split the sentence into words, collect them in a list, and join them in reverse. A string stream, written istringstream, reads words the way cin reads them from the keyboard. Using >> skips any run of spaces, so the empty pieces that appear with manual splitting never show up. The time is O(n) and the extra space is O(n) for the list of words.

The next program reads lines and prints each result in square brackets, so you can see leading and trailing spaces if any remain. Type these three lines: the sky is blue, then one, then a line with three spaces only.

C++ · editable

The output is:

[blue is sky the]
[one]
[]

The first line has four words, and they appear in reverse order with single spaces. The second line has one word, so no space is added. The third line has no words at all, so the output is an empty pair of brackets. The empty result is correct, and the brackets make it visible.

The loop goes backward from the last word to the first. It adds a space only when i is greater than 0, which keeps the separator out of the front of the sentence. Using int for the index lets it reach 0 and stop. With size_t, the test i >= 0 would always be true, and the loop would never end.

There is a second method, which interviewers sometimes ask for: reverse the whole string in place, then reverse each word inside it. That avoids the list, so the extra space is O(1) when the input is a mutable array. Practise it, but say which version you chose and why. The in-place method is harder to get right under pressure, so start with the clear version and offer the other as an improvement.

Problem 3: First character that appears only once

The problem. Given a string, return the index of the first character that appears exactly once. Return -1 if no character appears once. For loveleetcode the answer is 2, because l appears twice, o appears twice, and v is the first letter that appears once.

The idea. Count first, then scan. Two passes are needed, because you cannot know whether a letter appears again until you have seen the whole text. The first pass fills 26 counters. The second pass walks the text in order and returns the first index whose counter is 1. That is O(n) time with a fixed 26-counter table.

This problem is a good place to see a hash table as a picture, so the visualiser below builds the counts one character at a time. Step through it, then read the program.

The bucket being used has a thick outline and an arrow. Keys in a row are chained in one bucket. The key being handled has a thick outline.

5 empty buckets. A key goes to bucket (sum of its character codes) mod 5.

Step 1 of 13

The program reads lines and prints one answer per line. Type loveleetcode, then aabb, then swiss.

C++ · editable

The output is:

2
-1
1

For loveleetcode, the counts are l 2, o 2, v 1, e 4, t 1, c 1, d 1. The second pass starts at index 0. The letter l has count 2, so it is skipped. The letter o at index 1 has count 2, so it is skipped. The letter v at index 2 has count 1, so the function returns 2.

For aabb every letter appears twice, so no index qualifies, and the function returns -1. For swiss, s appears three times and w once, so the function returns index 1. The i at index 2 also appears once, but it comes later, so the first answer wins.

Notice why the second pass is needed. A single pass could not know, at index 2 in loveleetcode, that l would come back at index 4 and make it a repeat. Counting everything first removes that doubt. This is the pattern for every "first" or "only" question on a text.

Problem 4: Compress runs of letters

The problem. Turn a run of equal letters into the letter followed by its count, and leave single letters without a count. aaabccdddd becomes a3bc2d4. A run of one letter is written without a count.

The idea. Use two indexes. The index i marks the start of a run. The index j moves forward while the letters equal s[i]. When j stops, the run is s[i] through s[j - 1], its length is j - i, and the next run starts at j. Then set i = j and repeat. This is O(n) time, because every letter is visited once by j.

The visualiser below shows the runs for aaabccdddd as blocks. Watch the two indexes split the text into runs.

Run compression of aaabccdddd into four runs: aaa, b, cc and ddddRun compression of aaabccdddd into four runs: aaa, b, cc and dddd

The program reads lines and compresses each one. Type aaabccdddd, then abc, then aabcccc, then zzzzzzzzzzzz (twelve letters z).

C++ · editable

The output is:

a3bc2d4
abc
a2bc4
z12

Trace aaabccdddd. At i = 0, the letter is a. The index j moves to 3, because index 3 is b. The run length is 3, so the output gets a3, and i jumps to 3. At i = 3, the letter is b, and j stops at 4 because index 4 is c. The run has length 1, so only b is written. At i = 4, the run cc has length 2, so the output gets c2. At i = 6, the run dddd has length 4, so the output gets d4. The final output is a3bc2d4.

The line i = j; is the step that makes the algorithm work. Without it, the program would move i forward one letter at a time and write the same run again and again. The common mistakes section shows that bug in full.

Problem 5: Is one string a rotation of another

The problem. Given two strings a and b, decide whether b is a rotated, which means some letters moved from the front of a to the back. waterbottle and erbottlewat are a rotation pair: move wat from the front of the first word to the back, and you get the second word. abc and acb are not.

The idea. If b is a rotation of a, then b appears inside a followed by a. The string a + a contains every rotation of a as a substring of length n. So the test is: the lengths must match, and b must appear inside a + a. The cost depends on the search. Using find is O(n times m) in the worst case, which is acceptable here, and a stronger method exists for interviews that ask for a linear bound.

C++ · editable

Type these three lines: waterbottle erbottlewat, then abc acb, then abc cab. The output is:

waterbottle erbottlewat: YES
abc acb: NO
abc cab: YES

The first pair is a rotation, as the explanation said. abc acb has the same length, but abcabc does not contain acb, so the answer is NO. The third pair works because cab appears in abcabc starting at index 2, which is the rotation that moves ab to the back.

The length check at the start is not optional. Without it, a short b could be found inside a long a + a by accident, which would give a wrong YES.

Problem 6: Longest substring without a repeat

The problem. Given a string, find the length of the longest substring that has no repeated character. A substring is a run of letters next to each other in the original. For abcabcbb the answer is 3, from abc. For bbbbb it is 1. For pwwkew it is 3, from wke.

The idea. This is a sliding window. Keep two indexes, left and right, that mark the ends of the current window. Move right one step at a time to the right, and each time check whether the new letter already appears in the window. If it does, move left past the earlier copy. To find that copy quickly, store the last index where each letter was seen. Each letter is visited once by right and left never moves backward, so the time is O(n).

The visualiser below slides the window across abcabcbb. Watch the left edge jump when a repeat appears.

10left31426384115156right

sum: not computed yet

The left and right pointers are labelled under the boxes. Outlined boxes are the pair being added, filled boxes are the pair that works.

Find two items that add up to 14. One pointer starts at each end.

Step 1 of 4

The picture below shows the window at one moment in abcabcbb.

Sliding window over abcabcbb, with the window holding abc and the repeated letter outside itSliding window over abcabcbb, with the window holding abc and the repeated letter outside it

The program stores the last index of each character in an array of 256 boxes, one for each possible byte value. Using the byte value directly avoids the subtraction trick, because the input can be any character. Type these four lines: abcabcbb, then bbbbb, then pwwkew, then an empty line.

C++ · editable

The output is:

3
1
3
0

Trace abcabcbb. The window starts at left = 0. Letters a, b, c each get a new index, so the window grows to abc and best becomes 3. Then right reaches the second a at index 3. The last index of a is 0, which is inside the window (at least left), so left moves to 1. The window is now bca, length 3. The same thing happens for each repeat, and best never gets above 3.

The check last[c] >= left is the important detail. A letter seen long ago, before the current window started, should not move left. Without the check, the window would shrink for no reason. Try abba by hand to see the effect: the second a at index 3 has last index 0, which is before left (which is 2 after the second b), so the window correctly stays at ba.

Problem 7: Matching brackets with a stack

The problem. Given a string made only of brackets (, [, { and their closing forms, decide whether every opening bracket has a matching closing bracket in the right order. ()[]{} is valid. (] is not, because the bracket types differ. ([)] is not, because the pairs cross.

The idea. Use a stack. A stack is a list where you add and remove items only at one end, the top. It is like a pile of plates: the last plate placed is the first one removed. Each opening bracket goes onto the stack. Each closing bracket must match the top of the stack, and then it is removed. If a closing bracket arrives when the stack is empty, or does not match the top, the answer is no. At the end, the stack must be empty, because any leftover opening bracket has no partner. This is O(n) time and O(n) space in the worst case, when every character is an opening bracket.

The visualiser below runs the stack on {[]}. Watch the pile grow with the opening brackets and shrink as each one closes.

The top is the box marked top. An outlined box was just pushed or peeked.

The stack is empty. The last item pushed is the first one popped.

Step 1 of 8

The program reads lines and prints each result. Type ()[]{}, then (], then ([)], then {[]}, then ((.

C++ · editable

The output is:

()[]{} -> valid
(] -> invalid
([)] -> invalid
{[]} -> valid
(( -> invalid

Trace ([)]. The ( is pushed. The [ is pushed on top of it. Then ) arrives. The top of the stack is [, which is not the matching (, so the function returns false at once. The rule is that the most recent opening bracket must close first. The crossing shape of ([)] breaks that rule.

Trace ((. Both opening brackets are pushed, and the loop ends with two items on the stack. The final test open.empty() is false, so the answer is invalid. That is the case people forget: a valid string must close everything it opens, not just match each closing bracket it sees.

Patterns across the seven problems

Notice how the problems share tools. Problems 1, 3 and the anagram checks use counters. Problems 2 and 4 walk the text with indexes. Problems 5 and 6 use search and windows. Problem 7 uses a stack. Most interview string questions are one of these, or a combination.

When you meet a new problem, ask which shape it has. Is the answer about each letter independently? Use a counter. Is it about a run of equal letters? Use two indexes. Is it about the best piece of text with a property? Use a sliding window. Is it about matching open and close marks? Use a stack. This habit is what turns a hard-looking question into a familiar one.

What happens inside

Consider the counter array in problem 3. The array int count[26] takes 104 bytes on the stack, because each int is four bytes. Each letter costs one array update, which is a single memory write. The array is the same size whether the input has 10 letters or a million, so the extra space does not grow with the input.

Consider the stack in problem 7. The stack<char> adapter stores its items in a deque, a double-ended queue, which is a structure that grows at both ends. Each push and pop changes only the top, so each costs constant time. The stack grows to the largest number of open brackets at one moment. For a string of 1,000 opening brackets, it holds 1,000 characters.

Consider the sliding window in problem 6. The vector<int> last(256, -1) takes about 1 kilobyte. The two indexes move only forward, so each character is handled a fixed number of times, and the loop does constant work per step. The total time is O(n), which is why interviewers accept it as the best possible answer for this question.

Common mistakes

1. Forgetting i = j in run compression. Without it, the outer loop moves one letter at a time and writes the same run repeatedly. The debugging walkthrough below shows the exact output.

2. Using size_t in a reverse loop. The loop for (size_t i = n - 1; i >= 0; i--) never ends, because an unsigned value is never negative. Use int, as the reverse-words program does.

3. Calling top() on an empty stack. Calling open.top() when open is empty is undefined behaviour. The check open.empty() before the call prevents it. Without the check, the program may print nothing, print junk or crash.

4. Using unordered_map without its header. If you write unordered_map<char, int> without #include <unordered_map>, g++ reports:

error: ‘unordered_map’ was not declared in this scope

Read it as: the name exists in another header, and you have not included it. The fix is the matching #include line. The same pattern applies to stack, map and vector.

5. Forgetting the empty answer. A sliding window that starts with best = 0 correctly returns 0 for an empty string. An initial value of 1 would return the wrong answer. Check the empty input before you trust a method.

6. Treating the window check as a plain comparison. In problem 6, last[c] >= left matters. A repeat from before the window must be ignored, as the trace of abba showed.

Debugging walkthrough: the run that keeps repeating

A teammate wrote this compression function. Their output for aaab is wrong, and they ask you why.

#include <iostream>
#include <string>
using namespace std;
 
string compress(const string& s) {
    string out;
    int n = (int)s.size();
    int i = 0;
    while (i < n) {
        int j = i;
        while (j < n && s[j] == s[i]) {
            j++;
        }
        out += s[i];
        if (j - i > 1) {
            out += to_string(j - i);
        }
        i++;
    }
    return out;
}
 
int main() {
    string line;
    while (getline(cin, line)) {
        cout << compress(line) << endl;
    }
    return 0;
}

Type aaab and press Run. At i = 0, the run is aaa and the output gets a3. The bug is the last line of the loop, i++, which moves i one letter instead of to j. So at i = 1, the program starts a new run from the second a. That run is aa with length 2 (from index 1 to 2), so the output gets a2. At i = 2, the run is the single a at index 2, so the output gets a. At i = 3, the single b is written.

The full output is a3a2ab. Compare it with the expected a3b. The clue is that the same letter appears twice in a row in the output, which means the same run was counted more than once. The fix is one word: change i++ to i = j.

The method is worth remembering. When the output repeats a pattern, look at the loop variable that controls where the next piece starts. Ask what value it should have after one piece is finished, then check whether the code sets it to that value.

Variations and edge cases

Each problem has the same edge cases, and interviewers look for them every time.

For vowels, check uppercase, spaces and an empty string. For reversing words, check leading spaces, trailing spaces, repeated spaces and a sentence with one word or no words. For the first unique character, check a string with no unique letters (answer -1) and a string of one letter (answer 0).

For run compression, check a single letter (no count), a long run (count greater than 9, which is two digits), and an empty string (empty output). The count can be larger than 9, so to_string is the right tool, not a single character.

For rotation, check the empty pair (two empty strings are rotations of each other), and pairs with different lengths.

For the longest unique substring, check a repeated letter at the very end, and a string where the best window is at the start.

For brackets, check an empty string (valid), a string with only closing brackets (invalid), and a string with only opening brackets (invalid). A character that is not a bracket is an extra rule: ask whether to ignore it or reject it.

Interview and real-world use

Interviewers use these seven problems to test whether you think in patterns. A strong answer names the pattern, states the cost, handles the edge cases out loud and tests the empty input. If the interviewer asks for a faster method, the sliding window and the stack are both O(n), so they are usually the final answer.

In real work, the same tools appear in data cleaning (run compression and counting), search (rotations and windows), parsers (brackets and stacks, which are the basis of how compilers read code), and text processing (word splitting). A parser for a configuration file often uses the stack pattern to check nested braces. A log analyser uses sliding windows to find the longest stretch of errors. You do not need to memorise these systems to use the patterns.

Problems that combine two patterns

Real interview questions often mix two tools. Consider finding every anagram of a short word inside a longer text. The window has the length of the short word, and it slides one step at a time. At each step the 26-counter method checks the window, and you update the counts by adding the letter that enters and removing the letter that leaves. That is the sliding window from problem 6 combined with the counter check from the anagram lesson. The total time is O(n), because each letter enters and leaves the window once. Name both patterns when you explain it, so the interviewer sees that you recognised the structure rather than recalling one answer.

Explaining an answer out loud

Interviewers grade the explanation as much as the code. A useful pattern is to say four things in order: the idea, the steps, the cost and the test. Here is how that sounds for the first unique letter problem.

"The question asks for the first letter that appears once. I will count each letter first, because I cannot know whether a letter repeats until I have seen the whole string. Then I walk the string in order and return the first index whose count is one. Each pass is linear, and the counters are a fixed 26 boxes, so the time is O(n) and the extra space is constant. For loveleetcode the counts show l twice and v once, so the answer is index 2. I would also test the empty string, and a string where every letter repeats, which should return -1."

That answer names the pattern, states the steps, gives the cost and names a test. It also does not start typing before there is a plan, and it states the assumption that the letters are lowercase.

Two habits make an explanation strong. First, say an assumption out loud when you make it. If you assume lowercase letters, say so, because the interviewer may change the rule. Second, when you change your approach, say why. "The sort works, but it costs O(n log n), so I will switch to counters" shows that you chose on purpose.

Avoid two common habits. Do not say "it should work" without a test, because a claim without a trace is a guess. Do not memorise an answer and recite it, because a follow-up question will expose the gap. If you do not know a detail, say what you would check, then check it.

A checklist before you say you are done

Before you tell the interviewer the code is finished, run through this list and say each answer aloud:

  • Is the empty input handled, and what does it return?
  • Is there a one-letter case, and does the loop still stop?
  • Is every index in range at the first and last step?
  • Is the return value the one the problem asks for, such as an index, a count or a yes or no?
  • Does the cost you state match the loops you wrote?

This habit catches most of the errors that cost candidates points. It takes far less time than a debugging session in front of the interviewer.

Practice

Exercise 1: All characters unique

Write bool allUnique(const string& s) that returns true when no character appears twice. Use a 256-box array of flags for the first version. Test abc (true) and aba (false).

Solution
#include <string>
using namespace std;
 
bool allUnique(const string& s) {
    bool seen[256] = {};
    for (char c : s) {
        unsigned char u = c;
        if (seen[u]) return false;
        seen[u] = true;
    }
    return true;
}

The array is indexed by the unsigned value, so every possible byte has a slot. The function returns at the first repeat, which is O(n) time and O(1) extra space for a fixed alphabet size.

Exercise 2: Most frequent character

Write char mostFrequent(const string& s) that returns the lowercase letter that appears most often. On a tie, return the one that appears first in the text. Test banana (answer a) and abab (answer a, because it appears first).

Solution

Count with 26 counters, then scan the text in order and keep the first letter that reaches the highest count. Scanning the text rather than the alphabet gives the tie rule for free.

#include <string>
using namespace std;
 
char mostFrequent(const string& s) {
    int count[26] = {};
    for (char c : s) count[c - 'a']++;
    char best = s[0];
    int bestCount = 0;
    for (char c : s) {
        if (count[c - 'a'] > bestCount) {
            bestCount = count[c - 'a'];
            best = c;
        }
    }
    return best;
}

For abab both letters have count 2, and a is seen first in the scan, so it is kept. The function assumes a non-empty string of lowercase letters.

Exercise 3: Longest common prefix

Write string commonPrefix(const vector<string>& words) that returns the longest prefix shared by every word. Return an empty string for an empty list. Test ["flower", "flow", "flight"] (answer fl).

Solution

Start with the first word as the candidate prefix. For each other word, shorten the prefix until the other word starts with it.

#include <string>
#include <vector>
using namespace std;
 
string commonPrefix(const vector<string>& words) {
    if (words.empty()) return "";
    string prefix = words[0];
    for (const string& w : words) {
        size_t k = 0;
        while (k < prefix.size() && k < w.size() && prefix[k] == w[k]) k++;
        prefix = prefix.substr(0, k);
    }
    return prefix;
}

For the example, flower and flow share flow, then flow and flight share fl. The cost is O(total characters) in the worst case.

Exercise 4: Decode the run-length string

Write string decode(const string& s) that reverses problem 4. It reads a letter, then an optional number, and writes the letter that many times. A letter with no number appears once. Test a3bc2d4 (answer aaabccdddd).

Solution

Read each letter, then read all digits after it, and build the number from them.

#include <string>
using namespace std;
 
string decode(const string& s) {
    string out;
    size_t i = 0;
    while (i < s.size()) {
        char letter = s[i];
        i++;
        int count = 0;
        while (i < s.size() && s[i] >= '0' && s[i] <= '9') {
            count = count * 10 + (s[i] - '0');
            i++;
        }
        if (count == 0) count = 1;
        out.append(count, letter);
    }
    return out;
}

The inner loop handles multi-digit counts such as z12. The value 0 is treated as "no number", which is a choice you should state in an interview. append(count, letter) adds count copies of the letter.

Exercise 5: Remove adjacent duplicates

Write string removeAdjacent(const string& s) that repeatedly removes pairs of equal letters that sit next to each other. Test abbaca (answer ca). Use a stack.

Solution

Push each letter. If it equals the top of the stack, pop instead of pushing. The stack at the end holds what is left.

#include <string>
using namespace std;
 
string removeAdjacent(const string& s) {
    string stack;
    for (char c : s) {
        if (!stack.empty() && stack.back() == c) {
            stack.pop_back();
        } else {
            stack.push_back(c);
        }
    }
    return stack;
}

For abbaca, the letters b and b cancel, then a and a cancel, leaving c and a. The string's push_back and pop_back act as a stack, so the same pattern works without the stack header.

Exercise 6: Count palindromic substrings

Write int countPalindromes(const string& s) that counts every substring that is a palindrome. Each single letter counts. Test aaa (answer 6: three single letters, two of length 2, one of length 3).

Solution

Expand around each centre, as in the palindrome lesson. Every successful expansion is one more palindrome, so count each one.

#include <string>
using namespace std;
 
int countAround(const string& s, int l, int r) {
    int count = 0;
    while (l >= 0 && r < (int)s.size() && s[l] == s[r]) {
        count++;
        l--;
        r++;
    }
    return count;
}
 
int countPalindromes(const string& s) {
    int total = 0;
    for (int c = 0; c < (int)s.size(); c++) {
        total += countAround(s, c, c);
        total += countAround(s, c, c + 1);
    }
    return total;
}

For aaa, the odd centres count 4: the three single letters and aaa. The even centres count 2: the two pairs aa. The total is 6. The worst case is O(n squared).

Exercise 7: Parse an integer

Write int parseInt(const string& s) that reads an optional leading + or -, then digits, and stops at the first other character. Return 0 if there are no digits. Test -42abc (answer -42) and abc (answer 0). Ignore overflow.

Solution
#include <string>
using namespace std;
 
int parseInt(const string& s) {
    size_t i = 0;
    bool negative = false;
    if (i < s.size() && (s[i] == '+' || s[i] == '-')) {
        negative = (s[i] == '-');
        i++;
    }
    int value = 0;
    while (i < s.size() && s[i] >= '0' && s[i] <= '9') {
        value = value * 10 + (s[i] - '0');
        i++;
    }
    return negative ? -value : value;
}

For -42abc, the sign is read, then 4 and 2 give 42, then a stops the loop. The result is -42. The loop does not check for overflow, which is a point to raise in an interview, because the real library version throws for a value that is too big.

Exercise 8: Subsequence check

Write bool isSubsequence(const string& s, const string& t) that returns true when s can be made from t by deleting some letters, keeping the order. Use two pointers. Test ace in abcde (true) and aec in abcde (false).

Solution

Walk through t once. Each time the current letter of t matches the next needed letter of s, advance in s. If all of s is matched, the answer is true.

#include <string>
using namespace std;
 
bool isSubsequence(const string& s, const string& t) {
    size_t i = 0;
    for (size_t j = 0; j < t.size() && i < s.size(); j++) {
        if (t[j] == s[i]) i++;
    }
    return i == s.size();
}

The text aec fails because after matching a and e in abcde, the program needs a c that comes after e, and there is none. The cost is O(length of t).

Exercise 9: Caesar shift

Write string caesar(const string& s, int k) that shifts each lowercase letter forward by k places, wrapping from z to a, and leaves other characters unchanged. Test xyz with k = 3 (answer abc).

Solution

Convert each letter to its position from 0 to 25, add k, take the remainder by 26 to wrap, and convert back. Use a double modulo so that a negative k also works.

#include <string>
using namespace std;
 
string caesar(const string& s, int k) {
    string out = s;
    for (char& c : out) {
        if (c >= 'a' && c <= 'z') {
            int pos = c - 'a';
            pos = ((pos + k) % 26 + 26) % 26;
            c = (char)('a' + pos);
        }
    }
    return out;
}

For xyz with k = 3, the positions are 23, 24, 25. Adding 3 gives 26, 27, 28, and the remainder by 26 gives 0, 1, 2, which are abc. The inner + 26 keeps the result positive when k is negative.

Exercise 10: Pangram check

Write bool isPangram(const string& s) that returns true when every letter from a to z appears at least once, ignoring case. Test The quick brown fox jumps over the lazy dog (true).

Solution

Mark each letter as seen after converting it to lowercase, then check that all 26 marks are set.

#include <cctype>
#include <string>
using namespace std;
 
bool isPangram(const string& s) {
    bool seen[26] = {};
    for (char c : s) {
        unsigned char u = tolower((unsigned char)c);
        if (u >= 'a' && u <= 'z') seen[u - 'a'] = true;
    }
    for (bool b : seen) {
        if (!b) return false;
    }
    return true;
}

The conversion to unsigned char before tolower avoids undefined behaviour, and the range test skips spaces and punctuation.

Exercise 11: Longest word

Write string longestWord(const string& sentence) that returns the longest word in a sentence. On a tie, return the first one. Use istringstream to split the words. Test the quick brown fox (answer quick).

Solution
#include <sstream>
#include <string>
using namespace std;
 
string longestWord(const string& sentence) {
    istringstream in(sentence);
    string word, best;
    while (in >> word) {
        if (word.size() > best.size()) best = word;
    }
    return best;
}

The comparison uses >, not >=, so the first word of the longest length is kept. quick and brown both have five letters, and quick comes first. The function returns an empty string for an empty sentence.

Exercise 12: Reverse each word in place

Write void reverseEachWord(string& s) that reverses the letters of each word but keeps the order of the words and the spaces. Test hello world (answer olleh dlrow).

Solution

Walk through the string. When you reach the start of a word, find its end, then reverse that piece with two indexes.

#include <string>
using namespace std;
 
void reverseEachWord(string& s) {
    size_t start = 0;
    while (start < s.size()) {
        if (s[start] == ' ') {
            start++;
            continue;
        }
        size_t end = start;
        while (end < s.size() && s[end] != ' ') end++;
        size_t i = start, j = end - 1;
        while (i < j) {
            char t = s[i];
            s[i] = s[j];
            s[j] = t;
            i++;
            j--;
        }
        start = end;
    }
}

Each word is reversed using the two-pointer swap from the palindrome lesson, so every letter is touched once. The total time is O(n) and the extra space is O(1).

What you now know

  • Start every string question by restating it, asking about edge cases and giving a worked example by hand.
  • Clue words point to a pattern: counters for "each letter", two indexes for "runs", a sliding window for "longest piece", a stack for "matching".
  • Count first when a question asks about the first or only occurrence, then scan again.
  • Reverse loops must use int indexes, not size_t.
  • Check the empty input for every method, and state the answer for it.
  • Always name the time and extra space, and say why each one is what it is.
  • The module assignment in the next lesson combines these patterns into one small program.
Practice this topic: coding problems for Character Arrays and Strings →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Palindromes and anagrams