3019. Number of Changing Keys

EasyString
Leetcode Link

Problem Description

In this problem, we're given a 0-indexed string s that represents the sequence of keys typed by a user. The string could consist of lowercase and uppercase letters from 'a' to 'z'. Our goal is to determine the number of key changes that the user has made. A key change is defined as typing a character that is different from the immediately preceding character, regardless of whether the characters are in different cases or the same (for example, typing 'a' after 'A' is not considered a change). Essentially, we need to count the instances where one character followed by another is different when both characters are converted to lowercase.

Modifiers like 'shift' or 'caps lock' which are used to change the case of letters are not considered key changes in themselves. This means that just switching from uppercase to lowercase or vice versa does not count as a change. The user has to move to a different letter altogether for it to be considered a change of keys.

Intuition

To arrive at the solution for this problem, we need an efficient way to compare consecutive characters in the string and count only the instances where a letter is followed by a different letter. Here's the intuition behind our approach:

  1. We iterate over the string s to examine each character in sequence.
  2. We compare each character with the next one, ignoring case (we use the lowercase of both characters for comparison).
  3. If the characters differ, it means the user had to change the key, so we increment a counter.
  4. We continue this process for the entire string, and the sum of increments in the counter gives us the total number of key changes.

So, the key idea is to compare each character with its successor while treating uppercase and lowercase characters as equal. To compare each character with its direct follower efficiently, we use the pairwise utility, which allows us to iterate over the string in pairs of consecutive characters. For each pair, we compare their lowercase forms to check for a key change. By summing the instances where the comparison is True (meaning a change has occurred), we derive the total number of key changes.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

Given an array of 1,000,000 integers that is almost sorted, except for 2 pairs of integers. Which algorithm is fastest for sorting the array?

Solution Approach

Following the intuition from before, the implementation involves traversing the string and performing a simple comparison for each pair of consecutive characters. Here's how it breaks down:

Data Structures and Patterns

We use the pairwise function, which is typically used to generate a new iterable by returning pairs of elements from the input iterable. In Python, it would provide us with an iterator over pairs of consecutive items. Here, pairwise(s) will return (s[0], s[1]), (s[1], s[2]), ... (s[n-2], s[n-1]) where n is the length of the string.

Algorithms

The solution makes use of the following simple algorithm:

  1. Convert Characters to Lowercase and Compare: The pairwise function gives us each pair of consecutive characters a and b as tuples. For each tuple, we convert a and b to lowercase and then check if they are not equal to each other using a.lower() != b.lower().

  2. Count Key Changes: If the lowercase versions of a and b are not equal, it indicates a key change. Using a generator expression inside sum, we iterate over all pairs, convert to lowercase, compare, and count these instances directly.

  3. Summation: sum is used to add up all the True values from the generator expression, where each True represents a different comparison and hence a key change.

The algorithm completes in a single pass over the string, which gives us a time complexity of O(n), where n is the length of the string.

1def countKeyChanges(self, s: str) -> int:
2    return sum(a.lower() != b.lower() for a, b in pairwise(s))

This single line of Python code is all that's needed to implement the complete algorithm, leveraging the power of Python's built-in functions and concise syntax.

Efficiency

Since we're performing one iteration of the string, and each operation within that iteration is O(1), the overall time complexity is O(n), making this approach efficient and scalable for large strings. There are no complex data structures involved, and we only require additional space for the lowercase character comparisons, which is constant space O(1), thus making the space complexity of this algorithm also very efficient.

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

What are the most two important steps in writing a depth first search function? (Select 2)

Example Walkthrough

Let's consider a small example to illustrate the solution approach, using the string s = "AabB". We want to find out the number of key changes the user has made, recalling that a 'key change' is defined as typing a distinct character than the previous one, case-insensitively.

Following the provided solution approach:

  1. We will use the pairwise function to create pairs of consecutive characters from our string s, resulting in the pairs: ('A', 'a'), ('a', 'b'), and ('b', 'B'). Here, pairwise(s) basically generates an iterator that would give us these pairs when looped over.

  2. For each pair, we convert each element to its lowercase version and compare them:

    • Compare A with a: Since A.lower() == a.lower(), there's no key change.
    • Compare a with b: Since a.lower() != b.lower(), we have a key change.
    • Compare b with B: Since b.lower() == B.lower(), there's no key change.
  3. We use a generator expression within sum to count the key changes. In our case, the pairs would give us [False, True, False] when comparing their lowercased versions. We are only interested in the instances where the comparison yields True.

  4. Finally, we sum up the True values using sum(), which represents the total key changes. For our example, we get sum([False, True, False]), which simplistically is just 0 + 1 + 0, equaling 1.

Therefore, using the given method, the user has made 1 key change while typing the string "AabB".

The corresponding Python code using the provided algorithm would process this example as follows:

1from itertools import pairwise
2
3def countKeyChanges(s: str) -> int:
4    return sum(a.lower() != b.lower() for a, b in pairwise(s))
5
6# Example string
7s = "AabB"
8# Expected output: 1
9print(countKeyChanges(s))

When we execute this code with our example string s = "AabB", the output will be 1, which matches our manual calculation.

Solution Implementation

1from itertools import pairwise  # pairwise is from itertools in Python 3.10 and later. For earlier versions, you may need to define it manually.
2
3class Solution:
4    def countKeyChanges(self, keyboard_sequence: str) -> int:
5        """
6        Count the number of key changes in a given keyboard sequence.
7      
8        :param keyboard_sequence: A string representing the sequence of key presses.
9        :return: An integer representing the number of key changes.
10        """
11      
12        # Initialize a counter for key changes
13        key_changes = 0
14
15        # Use the pairwise function to create pairs of consecutive elements
16        for current_key, next_key in pairwise(keyboard_sequence):
17            # Check if the lowercase versions of the consecutive keys are different
18            if current_key.lower() != next_key.lower():
19                # If so, increment the key changes counter
20                key_changes += 1
21
22        # Return the total number of key changes
23        return key_changes
24      
25# Example usage:
26# solution = Solution()
27# result = solution.countKeyChanges("aAAbBBbC")
28# print(result)  # Output would be the number of key changes.
29```
30
31Key points in this rework:
32- The method name `countKeyChanges` remains unchanged as per the instructions.
33- The `s` parameter is renamed to `keyboard_sequence` to provide more clarity on what it represents.
34- A comment section has been added before the method to explain the functionality, inputs, and output.
35- Added inline comments to explain each step of the code for better understanding.
36
37Remember that the `pairwise` function was added in Python 3.10. If you're using a version of Python prior to 3.10, you would have to define the `pairwise` function manually like this:
38
39```python
40def pairwise(iterable):
41    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
42    a, b = itertools.tee(iterable)
43    next(b, None)
44    return zip(a, b)
45
1class Solution {
2    // Method to count the number of key changes in the input string
3    public int countKeyChanges(String s) {
4        // Initialize the count of key changes to zero
5        int keyChangeCount = 0;
6
7        // Iterate over the characters in the string starting from the second character
8        for (int i = 1; i < s.length(); ++i) {
9            // Compare current and previous characters in a case-insensitive manner
10            if (Character.toLowerCase(s.charAt(i)) != Character.toLowerCase(s.charAt(i - 1))) {
11                // Increment the number of key changes when the current and previous characters differ
12                keyChangeCount++;
13            }
14        }
15
16        // Return the total count of key changes
17        return keyChangeCount;
18    }
19}
20
1#include <algorithm> // Include algorithm header for transform
2#include <string>    // Include string header for string class
3
4class Solution {
5public:
6    // This function counts the number of times the key changes (case-insensitive).
7    int countKeyChanges(std::string s) {
8        // Convert the entire string to lower case to make comparison case-insensitive
9        std::transform(s.begin(), s.end(), s.begin(),
10            [](unsigned char c) { return std::tolower(c); });
11
12        // Initialize a counter for the number of key changes
13        int count = 0;
14
15        // Iterate through the string starting from the second character
16        for (size_t i = 1; i < s.size(); ++i) {
17            // If the current character is different from the previous one,
18            // increment the count of key changes
19            count += (s[i] != s[i - 1]);
20        }
21
22        // Return the total count of key changes
23        return count;
24    }
25};
26
1function countKeyChanges(inputString: string): number {
2    // Convert the input string to lowercase for a case-insensitive comparison
3    inputString = inputString.toLowerCase();
4
5    // Initialize a counter for the number of key changes
6    let changeCount = 0;
7
8    // Iterate over the characters of the string, starting from the second character
9    for (let index = 1; index < inputString.length; ++index) {
10        // Compare the current character with the previous one
11        if (inputString[index] !== inputString[index - 1]) {
12            // If they differ, increment the change count
13            ++changeCount;
14        }
15    }
16
17    // Return the total number of key changes in the string
18    return changeCount;
19}
20
Not Sure What to Study? Take the 2-min Quiz:

What is the best way of checking if an element exists in an unsorted array once in terms of time complexity? Select the best that applies.

Time and Space Complexity

The time complexity of the provided code is O(n), where n is the length of the string s. This is because the pairwise function generates pairs of consecutive characters from the string and the sum function goes through each pair only once in a single pass. Hence, the number of operations is proportional to the number of characters in the string.

The space complexity of the code is O(1). This is due to the fact that the pairwise function does not create a separate list of pairs but instead generates them on-the-fly, and the summation computation does not require additional space that grows with the input size. Only a constant amount of extra memory is used for the iterator and the count variable within the sum, regardless of the size of the string s.

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

Fast Track Your Learning with Our Quick Skills Quiz:

What data structure does Breadth-first search typically uses to store intermediate states?


Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫