Facebook Pixel

3019. Number of Changing Keys

EasyString
Leetcode Link

Problem Description

You are given a string s that represents text typed by a user. The string is 0-indexed, meaning positions start from 0.

The problem asks you to count how many times the user had to change the key while typing. A key change occurs when the user presses a different key than the one they pressed last.

Important Rules:

  • Case doesn't matter when determining key changes. Typing 'a' followed by 'A' is NOT considered a key change since they are the same key on the keyboard.
  • Modifiers like shift or caps lock don't count as key changes.

Examples:

  • For s = "ab": The user types 'a' then 'b' (different keys), so there is 1 key change.
  • For s = "bBBb": The user types 'b', 'B', 'B', 'b' (all the same key, just different cases), so there are 0 key changes.

The solution converts the entire string to lowercase using s.lower() to ignore case differences, then uses pairwise() to look at consecutive character pairs (a, b). For each pair where a != b, it counts as a key change. The sum() function counts all the True values (where characters differ) to get the total number of key changes.

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

Intuition

Since we need to count key changes and case doesn't matter (pressing 'a' or 'A' uses the same physical key), the first insight is to normalize the string by converting everything to the same case. This eliminates the case distinction problem entirely.

Once we have a normalized string, the problem becomes straightforward: we need to count how many times consecutive characters are different. If we're typing and the current character differs from the previous one, we must have changed keys.

The key observation is that for a string of length n, there are exactly n-1 transitions between consecutive characters. Each transition either represents a key change (if characters differ) or staying on the same key (if characters are the same).

We can check each consecutive pair of characters: (s[0], s[1]), (s[1], s[2]), ..., (s[n-2], s[n-1]). For each pair where the characters are different, we increment our count.

Python's pairwise() function elegantly gives us these consecutive pairs, and since Python treats True as 1 and False as 0 in numeric contexts, we can directly sum the boolean results of comparing each pair. The expression a != b evaluates to True (counted as 1) when keys change and False (counted as 0) when they don't, making sum(a != b for a, b in pairwise(s.lower())) a concise way to count all key changes.

Solution Approach

The solution implements a single-pass algorithm that efficiently counts key changes by examining consecutive character pairs.

Implementation Steps:

  1. String Normalization: First, we convert the entire string to lowercase using s.lower(). This ensures that 'a' and 'A' are treated as the same key, eliminating case sensitivity from our comparison.

  2. Pairwise Comparison: We use Python's pairwise() function to generate consecutive character pairs from the normalized string. For a string like "abc", pairwise() yields: ('a', 'b'), ('b', 'c').

  3. Counting Changes: For each pair (a, b) generated by pairwise():

    • We evaluate a != b
    • If the characters are different, this expression returns True (counted as 1)
    • If the characters are the same, it returns False (counted as 0)
  4. Summing Results: The sum() function aggregates all the boolean results. Since Python treats True as 1 and False as 0, this directly gives us the count of key changes.

Time Complexity: O(n) where n is the length of the string. We traverse the string once for lowercasing and once for pairwise comparison.

Space Complexity: O(n) for creating the lowercase copy of the string. The pairwise() iterator itself uses O(1) additional space.

The elegance of this solution lies in combining Python's built-in functions to create a concise one-liner that clearly expresses the logic: count how many times consecutive characters (ignoring case) are different.

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 string s = "aAbBcC".

Step 1: Normalize the string

  • Original: "aAbBcC"
  • After s.lower(): "aabbcc"

Step 2: Generate consecutive pairs using pairwise()

  • The pairs are: ('a', 'a'), ('a', 'b'), ('b', 'b'), ('b', 'c'), ('c', 'c')

Step 3: Evaluate each pair for key changes

  • Pair 1: ('a', 'a')'a' != 'a' = False (no key change, same key)
  • Pair 2: ('a', 'b')'a' != 'b' = True (key change from 'a' to 'b')
  • Pair 3: ('b', 'b')'b' != 'b' = False (no key change, same key)
  • Pair 4: ('b', 'c')'b' != 'c' = True (key change from 'b' to 'c')
  • Pair 5: ('c', 'c')'c' != 'c' = False (no key change, same key)

Step 4: Sum the results

  • Boolean values: [False, True, False, True, False]
  • Numeric values: [0, 1, 0, 1, 0]
  • Sum: 0 + 1 + 0 + 1 + 0 = 2

Result: There are 2 key changes in the string "aAbBcC".

This makes sense intuitively: the user types 'a' twice (in different cases), then changes to 'b' (first key change), types 'b' twice, then changes to 'c' (second key change), and types 'c' twice. The solution correctly identifies that case differences don't count as key changes, only actual changes between different letters do.

Solution Implementation

1class Solution:
2    def countKeyChanges(self, s: str) -> int:
3        """
4        Count the number of key changes in a string.
5        A key change occurs when consecutive characters are different (case-insensitive).
6      
7        Args:
8            s: Input string to analyze
9          
10        Returns:
11            Number of key changes (transitions between different characters)
12        """
13        # Convert string to lowercase for case-insensitive comparison
14        s_lower = s.lower()
15      
16        # Initialize counter for key changes
17        count = 0
18      
19        # Iterate through consecutive character pairs
20        for i in range(len(s_lower) - 1):
21            # Check if current character differs from next character
22            if s_lower[i] != s_lower[i + 1]:
23                count += 1
24      
25        return count
26
1class Solution {
2    /**
3     * Counts the number of key changes in a string.
4     * A key change occurs when consecutive characters (case-insensitive) are different.
5     * 
6     * @param s the input string to analyze
7     * @return the number of key changes in the string
8     */
9    public int countKeyChanges(String s) {
10        // Initialize counter for key changes
11        int keyChangeCount = 0;
12      
13        // Iterate through the string starting from the second character
14        for (int i = 1; i < s.length(); i++) {
15            // Get current and previous characters in lowercase for comparison
16            char currentChar = Character.toLowerCase(s.charAt(i));
17            char previousChar = Character.toLowerCase(s.charAt(i - 1));
18          
19            // Check if there's a key change (different characters ignoring case)
20            if (currentChar != previousChar) {
21                keyChangeCount++;
22            }
23        }
24      
25        // Return the total number of key changes
26        return keyChangeCount;
27    }
28}
29
1class Solution {
2public:
3    int countKeyChanges(string s) {
4        // Convert all characters to lowercase for case-insensitive comparison
5        transform(s.begin(), s.end(), s.begin(), ::tolower);
6      
7        // Initialize counter for key changes
8        int changeCount = 0;
9      
10        // Iterate through the string starting from the second character
11        for (int i = 1; i < s.size(); ++i) {
12            // Increment counter if current character differs from previous one
13            if (s[i] != s[i - 1]) {
14                changeCount++;
15            }
16        }
17      
18        return changeCount;
19    }
20};
21
1/**
2 * Counts the number of key changes in a string where consecutive characters differ
3 * @param s - The input string to analyze
4 * @returns The number of times consecutive characters are different
5 */
6function countKeyChanges(s: string): number {
7    // Convert the entire string to lowercase for case-insensitive comparison
8    const normalizedString: string = s.toLowerCase();
9  
10    // Initialize counter for key changes
11    let changeCount: number = 0;
12  
13    // Iterate through the string starting from the second character
14    for (let i: number = 1; i < normalizedString.length; i++) {
15        // Check if current character differs from the previous one
16        if (normalizedString[i] !== normalizedString[i - 1]) {
17            // Increment the change counter when characters differ
18            changeCount++;
19        }
20    }
21  
22    // Return the total number of key changes
23    return changeCount;
24}
25

Time and Space Complexity

The time complexity is O(n), where n is the length of the string s. This is because:

  • Converting the string to lowercase using s.lower() takes O(n) time
  • The pairwise function iterates through the string once, creating pairs of consecutive characters, which takes O(n) time
  • The generator expression a != b for a, b in pairwise(s.lower()) evaluates n-1 comparisons
  • The sum() function iterates through all these comparisons once, taking O(n) time

The space complexity is O(n) rather than O(1). This is because:

  • s.lower() creates a new string of length n, requiring O(n) space
  • The pairwise function itself doesn't create additional significant space as it yields pairs on-the-fly
  • The generator expression also doesn't create additional space as it yields values one at a time

Note: The reference answer states O(1) space complexity, which would be accurate if we consider only the auxiliary space (excluding the input and the space for the lowercase conversion). However, strictly speaking, since s.lower() creates a new string, the total space complexity is O(n).

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

Common Pitfalls

1. Forgetting to Handle Case Insensitivity

The most common mistake is comparing characters directly without normalizing the case first. This leads to incorrectly counting case changes as key changes.

Incorrect Implementation:

def countKeyChanges(self, s: str) -> int:
    count = 0
    for i in range(len(s) - 1):
        if s[i] != s[i + 1]:  # Wrong: 'a' != 'A' would count as a key change
            count += 1
    return count

Why it fails: For input "aA", this would return 1 instead of 0, since it treats 'a' and 'A' as different keys.

Solution: Always convert the string to lowercase (or uppercase) before comparison:

s_lower = s.lower()  # Normalize case first
for i in range(len(s_lower) - 1):
    if s_lower[i] != s_lower[i + 1]:
        count += 1

2. Index Out of Bounds Error

Another common mistake is incorrectly setting the loop range, causing an index error when accessing s[i + 1].

Incorrect Implementation:

def countKeyChanges(self, s: str) -> int:
    s_lower = s.lower()
    count = 0
    for i in range(len(s_lower)):  # Wrong: goes up to len(s_lower) - 1
        if s_lower[i] != s_lower[i + 1]:  # Error when i = len(s_lower) - 1
            count += 1
    return count

Why it fails: When i reaches the last index, s_lower[i + 1] tries to access an element beyond the string's bounds.

Solution: Use range(len(s_lower) - 1) to stop one position before the last character:

for i in range(len(s_lower) - 1):  # Correct range
    if s_lower[i] != s_lower[i + 1]:
        count += 1

3. Not Handling Edge Cases

Failing to consider strings with length 0 or 1, which have no key changes by definition.

Potential Issue:

def countKeyChanges(self, s: str) -> int:
    # May not handle empty strings gracefully in some implementations
    return sum(a != b for a, b in pairwise(s.lower()))

Solution: While the current implementation handles these cases correctly (empty strings and single characters naturally return 0), it's good practice to explicitly document or handle edge cases:

def countKeyChanges(self, s: str) -> int:
    if len(s) <= 1:
        return 0  # No key changes possible
  
    s_lower = s.lower()
    count = 0
    for i in range(len(s_lower) - 1):
        if s_lower[i] != s_lower[i + 1]:
            count += 1
    return count
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following uses divide and conquer strategy?


Recommended Readings

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

Load More