Facebook Pixel

2490. Circular Sentence

EasyString
Leetcode Link

Problem Description

You are given a sentence that consists of words separated by single spaces, with no leading or trailing spaces. Each word contains only uppercase and lowercase English letters, where uppercase and lowercase letters are treated as different characters.

A sentence is considered circular if it meets two conditions:

  1. The last character of each word matches the first character of the next word
  2. The last character of the final word matches the first character of the first word

For example:

  • "leetcode exercises sound delightful" is circular because:

    • 'leetcode' ends with 'e', 'exercises' starts with 'e'
    • 'exercises' ends with 's', 'sound' starts with 's'
    • 'sound' ends with 'd', 'delightful' starts with 'd'
    • 'delightful' ends with 'l', 'leetcode' starts with 'l'
  • "Leetcode is cool" is not circular because 'Leetcode' ends with 'e' but 'is' starts with 'i'

The solution splits the sentence into words and checks if each word's last character equals the next word's first character. It uses modulo arithmetic (i + 1) % n to wrap around and compare the last word with the first word, ensuring the circular property is satisfied.

Given a string sentence, return true if it forms a circular sentence, otherwise return false.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that we need to check a chain of connections between consecutive words, where each connection is formed by matching the last character of one word with the first character of the next word.

Think of the words as forming a circle where each word must "link" to the next one through matching characters. Like a chain where each link must properly connect to form a complete loop.

We can break this down into checking pairs of adjacent words:

  • Word 1's last character must match Word 2's first character
  • Word 2's last character must match Word 3's first character
  • And so on...

The tricky part is remembering that it's circular - the last word must also connect back to the first word. This means after checking all consecutive pairs, we need one final check: does the last word's ending character match the first word's starting character?

Instead of writing separate logic for the wraparound case, we can use modulo arithmetic. When we're at word i, we check if it connects to word (i + 1) % n. This elegantly handles the wraparound: when i is the last index n-1, then (n-1 + 1) % n = 0, which correctly points us back to the first word.

The solution becomes clean: split the sentence into words, then verify that every word i has its last character s[-1] matching the first character ss[(i + 1) % n][0] of the next word in the circular sequence. If all pairs satisfy this condition, the sentence is circular.

Solution Approach

The implementation follows a straightforward simulation approach where we check each word connection in the sentence.

Step 1: Split the sentence into words

ss = sentence.split()

We use Python's split() method to break the sentence into individual words. This automatically handles multiple spaces and gives us a list of words.

Step 2: Get the number of words

n = len(ss)

Store the total count of words for use in the modulo operation.

Step 3: Check all word connections

all(s[-1] == ss[(i + 1) % n][0] for i, s in enumerate(ss))

This line does all the heavy lifting using Python's all() function with a generator expression:

  • enumerate(ss) gives us each word s along with its index i
  • For each word at index i, we access:
    • s[-1]: the last character of the current word
    • ss[(i + 1) % n][0]: the first character of the next word
  • The modulo operation (i + 1) % n ensures that when we reach the last word (index n-1), the next index wraps around to 0, checking the connection back to the first word

The all() function returns True only if every comparison in the generator expression evaluates to True. If even one word doesn't connect properly to its next word, the function returns False.

Time Complexity: O(n) where n is the number of words, as we iterate through each word once.

Space Complexity: O(n) for storing the split words in the list ss.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with the example: "apple eagle eat ta"

Step 1: Split the sentence into words

ss = ["apple", "eagle", "eat", "ta"]

Step 2: Get the number of words

n = 4

Step 3: Check each word connection

Now let's trace through the generator expression for each iteration:

Iteration 0: i = 0, s = "apple"

  • Current word's last char: s[-1] = 'e'
  • Next word index: (0 + 1) % 4 = 1
  • Next word's first char: ss[1][0] = 'e' (from "eagle")
  • Check: 'e' == 'e' ✓ True

Iteration 1: i = 1, s = "eagle"

  • Current word's last char: s[-1] = 'e'
  • Next word index: (1 + 1) % 4 = 2
  • Next word's first char: ss[2][0] = 'e' (from "eat")
  • Check: 'e' == 'e' ✓ True

Iteration 2: i = 2, s = "eat"

  • Current word's last char: s[-1] = 't'
  • Next word index: (2 + 1) % 4 = 3
  • Next word's first char: ss[3][0] = 't' (from "ta")
  • Check: 't' == 't' ✓ True

Iteration 3: i = 3, s = "ta"

  • Current word's last char: s[-1] = 'a'
  • Next word index: (3 + 1) % 4 = 0 (wraps around!)
  • Next word's first char: ss[0][0] = 'a' (from "apple")
  • Check: 'a' == 'a' ✓ True

Since all four checks returned True, the all() function returns True, confirming this is a circular sentence.

The key insight is how the modulo operation (3 + 1) % 4 = 0 naturally handles the wraparound, allowing us to check if the last word "ta" connects back to the first word "apple" without any special case logic.

Solution Implementation

1class Solution:
2    def isCircularSentence(self, sentence: str) -> bool:
3        # Split the sentence into individual words
4        words = sentence.split()
5      
6        # Get the number of words in the sentence
7        num_words = len(words)
8      
9        # Check if each word's last character matches the next word's first character
10        # Use modulo operator to wrap around from last word to first word
11        for i, word in enumerate(words):
12            next_word_index = (i + 1) % num_words
13          
14            # Check if current word's last character equals next word's first character
15            if word[-1] != words[next_word_index][0]:
16                return False
17      
18        return True
19
1class Solution {
2    /**
3     * Checks if a sentence is circular.
4     * A sentence is circular if the last character of each word equals 
5     * the first character of the next word, and the last character of 
6     * the last word equals the first character of the first word.
7     * 
8     * @param sentence the input sentence containing words separated by spaces
9     * @return true if the sentence is circular, false otherwise
10     */
11    public boolean isCircularSentence(String sentence) {
12        // Split the sentence into individual words
13        String[] words = sentence.split(" ");
14        int wordCount = words.length;
15      
16        // Check each word's last character against the next word's first character
17        for (int i = 0; i < wordCount; i++) {
18            // Get the current word and the next word (wrapping around using modulo)
19            String currentWord = words[i];
20            String nextWord = words[(i + 1) % wordCount];
21          
22            // Get the last character of current word
23            char lastCharOfCurrent = currentWord.charAt(currentWord.length() - 1);
24            // Get the first character of next word
25            char firstCharOfNext = nextWord.charAt(0);
26          
27            // If characters don't match, sentence is not circular
28            if (lastCharOfCurrent != firstCharOfNext) {
29                return false;
30            }
31        }
32      
33        // All adjacent words satisfy the circular condition
34        return true;
35    }
36}
37
1class Solution {
2public:
3    /**
4     * Checks if a sentence is circular.
5     * A sentence is circular if the last character of each word equals 
6     * the first character of the next word (including wrap-around from last to first word).
7     * 
8     * @param sentence The input sentence containing words separated by spaces
9     * @return true if the sentence is circular, false otherwise
10     */
11    bool isCircularSentence(string sentence) {
12        // Split the sentence into individual words
13        vector<string> words = split(sentence, ' ');
14        int wordCount = words.size();
15      
16        // Check circular condition for each consecutive pair of words
17        for (int i = 0; i < wordCount; ++i) {
18            // Get the last character of current word
19            char lastCharOfCurrentWord = words[i].back();
20            // Get the first character of next word (with wrap-around using modulo)
21            char firstCharOfNextWord = words[(i + 1) % wordCount][0];
22          
23            // If characters don't match, sentence is not circular
24            if (lastCharOfCurrentWord != firstCharOfNextWord) {
25                return false;
26            }
27        }
28      
29        // All consecutive pairs satisfy the circular condition
30        return true;
31    }
32
33private:
34    /**
35     * Splits a string into tokens based on a delimiter character.
36     * 
37     * @param str The string to be split
38     * @param delimiter The character used to separate tokens
39     * @return A vector containing all the tokens
40     */
41    vector<string> split(string& str, char delimiter) {
42        stringstream stringStream(str);
43        string token;
44        vector<string> tokens;
45      
46        // Extract tokens separated by the delimiter
47        while (getline(stringStream, token, delimiter)) {
48            tokens.emplace_back(token);
49        }
50      
51        return tokens;
52    }
53};
54
1/**
2 * Checks if a sentence is circular - where the last character of each word
3 * matches the first character of the next word, and the last character of
4 * the last word matches the first character of the first word.
5 * 
6 * @param sentence - The input sentence to check
7 * @returns true if the sentence is circular, false otherwise
8 */
9function isCircularSentence(sentence: string): boolean {
10    // Split the sentence into an array of words
11    const words: string[] = sentence.split(' ');
12  
13    // Get the total number of words
14    const wordCount: number = words.length;
15  
16    // Check each word's last character against the next word's first character
17    for (let i = 0; i < wordCount; i++) {
18        // Get the current word and the next word (wrapping around to first word)
19        const currentWord: string = words[i];
20        const nextWord: string = words[(i + 1) % wordCount];
21      
22        // Get the last character of current word
23        const lastCharOfCurrent: string = currentWord[currentWord.length - 1];
24      
25        // Get the first character of next word
26        const firstCharOfNext: string = nextWord[0];
27      
28        // If characters don't match, sentence is not circular
29        if (lastCharOfCurrent !== firstCharOfNext) {
30            return false;
31        }
32    }
33  
34    // All adjacent words satisfy the circular condition
35    return true;
36}
37

Time and Space Complexity

The time complexity is O(n), where n is the length of the input string sentence.

  • The split() operation traverses the entire string once to split it into words: O(n)
  • The enumerate() creates an iterator over the list of words: O(1) to create, O(m) to iterate where m is the number of words
  • For each word, we access the last character s[-1] and the first character of the next word ss[(i + 1) % n][0]: O(1) per word
  • The all() function evaluates the generator expression for all words: O(m) where m is the number of words
  • Since the total length of all words combined is at most n, the overall time complexity is O(n)

The space complexity is O(n), where n is the length of the input string sentence.

  • The split() operation creates a new list ss containing all words from the sentence, which requires O(n) space to store all characters from the original string (distributed across multiple word strings)
  • The generator expression in all() uses O(1) additional space as it evaluates one condition at a time
  • Therefore, the total space complexity is O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to Handle Single-Word Sentences

A common mistake is assuming there will always be multiple words. For a single-word sentence like "a" or "aba", the circular condition requires the word's last character to match its own first character.

Pitfall Example:

# Incorrect approach that might fail for single words
def isCircularSentence(self, sentence: str) -> bool:
    words = sentence.split()
    if len(words) == 1:
        return True  # Wrong! Should check if first and last char match
    # ... rest of the code

Solution: The modulo approach naturally handles this case. When there's only one word, (0 + 1) % 1 = 0, so it correctly compares the word's last character with its own first character.

2. Using Space-Based Character Checking Instead of Word Splitting

Some might try to check characters directly around spaces without properly splitting into words first.

Pitfall Example:

# Incorrect approach
def isCircularSentence(self, sentence: str) -> bool:
    # This fails because it doesn't handle the wrap-around case
    for i in range(len(sentence)):
        if sentence[i] == ' ':
            if sentence[i-1] != sentence[i+1]:
                return False
    return True

Problems with this approach:

  • Doesn't check if the last word connects to the first word
  • Index out of bounds errors at sentence boundaries
  • More complex logic needed to find word boundaries

Solution: Always split the sentence into words first using split(), then work with the word list. This makes the logic cleaner and the modulo wrap-around straightforward.

3. Case Sensitivity Confusion

The problem states that uppercase and lowercase letters are different characters. A common mistake is normalizing the case.

Pitfall Example:

# Incorrect - normalizing case when we shouldn't
def isCircularSentence(self, sentence: str) -> bool:
    words = sentence.lower().split()  # Wrong! 'A' and 'a' are different
    # ... rest of the code

Solution: Keep the original case of the sentence. The characters 'A' and 'a' should be treated as different, so "Alpha apple" would NOT be circular since 'a' ≠ 'a'.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

You are given an array of intervals where intervals[i] = [start_i, end_i] represent the start and end of the ith interval. You need to merge all overlapping intervals and return an array of the non-overlapping intervals that cover all the intervals in the input.


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More