3019. Number of Changing Keys
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.
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:
-
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. -
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')
. -
Counting Changes: For each pair
(a, b)
generated bypairwise()
:- 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)
- We evaluate
-
Summing Results: The
sum()
function aggregates all the boolean results. Since Python treatsTrue
as 1 andFalse
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 EvaluatorExample 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()
takesO(n)
time - The
pairwise
function iterates through the string once, creating pairs of consecutive characters, which takesO(n)
time - The generator expression
a != b for a, b in pairwise(s.lower())
evaluatesn-1
comparisons - The
sum()
function iterates through all these comparisons once, takingO(n)
time
The space complexity is O(n)
rather than O(1)
. This is because:
s.lower()
creates a new string of lengthn
, requiringO(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
Which of the following uses divide and conquer strategy?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!