2299. Strong Password Checker II
Problem Description
The problem presents a scenario where we need to validate a string, password
, to determine if it qualifies as a strong password. For a password to be considered strong, it must meet all these criteria:
- The length of the password must be at least
8
characters. - It must include at least one lowercase letter.
- It must include at least one uppercase letter.
- It must have at least one digit.
- It must contain at least one special character, which must be from the set
!@#$%^&*()-+
. - It must not have more than one identical character in a row, meaning no two adjacent characters can be the same.
The goal is to write a function that returns true
if the password
meets all the above-described conditions, otherwise returns false
.
Intuition
The intuition behind the solution is to go through the password character by character to check if it meets all the necessary criteria for being strong. We can do this by iterating through the string and using flags to mark if we've detected each type of required character. Here's the step-by-step process:
-
Length Check: First, we check if the password has at least 8 characters. If it's shorter, we immediately return
false
. -
Adjacency Check: As we iterate, we check if the current character is the same as the previous one - if it is, we return
false
because this violates the non-adjacent character condition. -
Character Type Checks: For every character, we need to check if it is a lowercase letter, an uppercase letter, a digit, or a special character. We can do this using the
.islower()
,.isupper()
,.isdigit()
methods, and by verifying if the character is in the specified special characters string. -
Aggregation with Bitmasking: Instead of keeping four separate flags, we can use a bitmask (
mask
) to aggregate all the flags into a single integer. Bitwise OR operations|=
are used to set the corresponding bits when we encounter lowercase letters, uppercase letters, digits, and special characters. Each character type corresponds to a different bit in the mask, so for example:- If we encounter a lowercase letter, we set the first bit (
mask |= 1
). - For an uppercase letter, the second bit (
mask |= 2
), and so on.
- If we encounter a lowercase letter, we set the first bit (
-
Final Verification: After going through every character of the password, we check if the mask equals
15
(binary1111
). This means that all four bits are set, so every type of required character is included in the password at least once.
The code is efficient and compact. By using bit operations, it avoids the use of multiple boolean variables and reduces the number of conditions checked.
Solution Approach
The implementation of the solution involves a simple yet effective approach by scanning through each character in the password and using bitwise operations to track whether the password criteria have been met. Here’s a detailed walk-through:
-
Initial Length Check: Immediately check if the password is less than 8 characters. If so, the function returns
false
.if len(password) < 8: return False
-
Setup: Initialize a variable
mask
to0
. This will serve as a 4-bit mask where each bit represents the presence of a different character type (lowercase, uppercase, digit, special character). -
Iterate Through Password Characters: By using a
for
loop with enumeration, we iterate over the password's characters, keeping track of each character and its index.for i, c in enumerate(password):
-
Adjacency Check: Inside the loop, we first check if the current character is the same as the previous one. If that’s the case, we immediately return
false
since this violates the non-adjacent identical character condition.if i and c == password[i - 1]: return False
-
Character Type Detection: Still in the loop, we check the type of the current character:
- If it’s a lowercase letter (
c.islower()
), set the first bit of the mask (mask |= 1
). - If it’s an uppercase letter (
c.isupper()
), set the second bit of the mask (mask |= 2
). - If it’s a digit (
c.isdigit()
), set the third bit of the mask (mask |= 4
). - If it’s a special character (checked by seeing if it is not any of the above types), set the fourth bit of the mask (
mask |= 8
).
- If it’s a lowercase letter (
-
Final Verification: After the loop, we check if all the bits are set in the mask by comparing it to
15
(binary1111
). This means all required character types are present in the password. The function returnstrue
ifmask == 15
; otherwise, it returnsfalse
. -
Data Structures, Algorithms & Patterns:
- Data Structure: A single integer variable is used for tracking the presence of character types through a concept called bitmasking.
- Algorithms: A single pass through the string is the main algorithmic component. All checks are done in this single pass, making the time complexity
O(n)
withn
as the password length. - Patterns: The solution uses bitwise operations to aggregate checks into a single value, reducing the need for multiple variables.
The simplicity and efficiency of the bitwise operations are key in making the code concise and performant. The choice to use a bitmask over multiple boolean variables exemplifies a common pattern in problems where aggregating flags into a single integer helps optimize space and improve code readability.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider the password string password
as Aa1!Aa1!
. To determine if this is a strong password according to the given criteria, our function will proceed as follows:
-
Initial Length Check: Our password
Aa1!Aa1!
is 8 characters long, so it passes the length requirement. -
Setup: We initialize the
mask
to0
. This mask will help us track whether we have encountered at least one lowercase letter, uppercase letter, digit, and special character. -
Iterate Through Password Characters: We start looping through each character in the password.
-
Adjacency Check:
- For the first character 'A', there is no previous character, so we move on to type checking.
- The second character 'a' is different from the first, so no adjacency violation occurs.
- This process continues, and since no adjacent characters are identical, no adjacency checks fail.
-
Character Type Detection: As we go through each character:
- For 'A': It's uppercase, so we set the second bit of the mask (
mask |= 2
). - For 'a': It's lowercase, so we set the first bit of the mask (
mask |= 1
). - For '1': It's a digit, so we set the third bit of the mask (
mask |= 4
). - For '!': It's a special character, so we set the fourth bit of the mask (
mask |= 8
). - As we continue through each character, no additional bits are set since each type has already been encountered.
- For 'A': It's uppercase, so we set the second bit of the mask (
-
Final Verification: At the end, our
mask
is1111
in binary, or15
in decimal, which means all types of required characters were present at least once. -
Data Structures, Algorithms & Patterns: The use of bitmasking to track character types in a single integer is an efficient data structure choice, and iterating through the password is the primary algorithm. No patterns are additional, and the bitwise operations signify a common pattern to optimize space and improve code readability.
The password Aa1!Aa1!
is therefore confirmed to be a strong password by our implementation.
Solution Implementation
1class Solution:
2 def strongPasswordCheckerII(self, password: str) -> bool:
3 # Minimum password length required
4 min_password_length = 8
5
6 # Check if the password length is at least 8 characters
7 if len(password) < min_password_length:
8 return False
9
10 # Initializing a variable to use as a bitmask to track the requirement fulfillment
11 requirement_mask = 0
12
13 # Loop through each character in the password
14 for i, char in enumerate(password):
15 # Check if the current character is the same as the previous character
16 if i > 0 and char == password[i - 1]:
17 return False # Consecutive characters are not allowed
18
19 # Check if the character is a lowercase letter
20 if char.islower():
21 requirement_mask |= 1 # Set the bit for lowercase letter
22 # Check if the character is an uppercase letter
23 elif char.isupper():
24 requirement_mask |= 2 # Set the bit for uppercase letter
25 # Check if the character is a digit
26 elif char.isdigit():
27 requirement_mask |= 4 # Set the bit for digit
28 # Check if the character is a special character
29 else:
30 requirement_mask |= 8 # Set the bit for special character
31
32 # Check if all 4 requirements are met, which is when all 4 bits are set (i.e., requirement_mask == 1111 binary, which is 15 in decimal)
33 return requirement_mask == 15
34
1class Solution {
2 // Method to check if a given password is strong according to specified rules
3 public boolean strongPasswordCheckerII(String password) {
4 // Requirement: password should be at least 8 characters long
5 if (password.length() < 8) {
6 return false;
7 }
8
9 // A mask to keep track of the types of characters found
10 int characterTypesMask = 0;
11
12 // Iterate through each character in the password
13 for (int i = 0; i < password.length(); ++i) {
14 // Current character being checked
15 char currentChar = password.charAt(i);
16
17 // Requirement: password should not contain consecutive identical characters
18 if (i > 0 && currentChar == password.charAt(i - 1)) {
19 return false;
20 }
21
22 // Identifying the type of the current character and updating the mask accordingly
23 // If it is lowercase, set the first bit using OR operation with 1 (001)
24 if (Character.isLowerCase(currentChar)) {
25 characterTypesMask |= 1; // 0001
26 }
27 // If it is uppercase, set the second bit using OR operation with 2 (010)
28 else if (Character.isUpperCase(currentChar)) {
29 characterTypesMask |= 2; // 0010
30 }
31 // If it is a digit, set the third bit using OR operation with 4 (100)
32 else if (Character.isDigit(currentChar)) {
33 characterTypesMask |= 4; // 0100
34 }
35 // If it is a special character, set the fourth bit using OR operation with 8 (1000)
36 else {
37 characterTypesMask |= 8; // 1000
38 }
39 }
40
41 // Requirement: password must contain all types of characters (lowercase, uppercase, digit, special character)
42 // This is true if, after going through the entire string, the mask equals 15 (1111)
43 // which corresponds to having all four types of characters
44 return characterTypesMask == 15;
45 }
46}
47
1class Solution {
2public:
3 // Function to check if a given password meets strong password criteria.
4 bool strongPasswordCheckerII(string password) {
5 // The password must be at least 8 characters long.
6 if (password.size() < 8) {
7 return false;
8 }
9
10 // 'requirementsMet' will track the types of characters present in the password.
11 // Each bit in 'requirementsMet' corresponds to a different requirement:
12 // Bit 0 (1) represents the presence of a lowercase letter,
13 // Bit 1 (2) represents the presence of an uppercase letter,
14 // Bit 2 (4) represents the presence of a digit,
15 // Bit 3 (8) represents the presence of a special character.
16 int requirementsMet = 0;
17
18 // Iterate over the password characters.
19 for (int i = 0; i < password.size(); ++i) {
20 char currentChar = password[i];
21
22 // Check if the current character is the same as the previous one.
23 if (i > 0 && currentChar == password[i - 1]) {
24 return false; // Return false if two adjacent characters are the same.
25 }
26
27 // Check for different types of characters and update 'requirementsMet'.
28 if (currentChar >= 'a' && currentChar <= 'z') {
29 requirementsMet |= 1; // Presence of a lowercase letter.
30 } else if (currentChar >= 'A' && currentChar <= 'Z') {
31 requirementsMet |= 2; // Presence of an uppercase letter.
32 } else if (currentChar >= '0' && currentChar <= '9') {
33 requirementsMet |= 4; // Presence of a digit.
34 } else {
35 requirementsMet |= 8; // Presence of a special character.
36 }
37 }
38
39 // Check if all four types of characters are present (binary 1111 is decimal 15).
40 return requirementsMet == 15;
41 }
42};
43
1function strongPasswordCheckerII(password: string): boolean {
2 // Length check - password must be at least 8 characters
3 if (password.length < 8) {
4 return false;
5 }
6
7 // Initialize a bitmask to keep track of character types encountered
8 // bit 0 for lowercase, bit 1 for uppercase, bit 2 for digits, bit 3 for special characters
9 let charTypesMask = 0;
10
11 // Iterate over the characters of the password to validate the rules
12 for (let i = 0; i < password.length; ++i) {
13 const currentChar = password[i];
14 // Check for consecutive identical characters
15 if (i > 0 && currentChar === password[i - 1]) {
16 return false;
17 }
18
19 // Check the type of character and update the bitmask accordingly
20 if (currentChar >= 'a' && currentChar <= 'z') {
21 charTypesMask |= 1; // Set bit 0 for lowercase
22 } else if (currentChar >= 'A' && currentChar <= 'Z') {
23 charTypesMask |= 2; // Set bit 1 for uppercase
24 } else if (currentChar >= '0' && currentChar <= '9') {
25 charTypesMask |= 4; // Set bit 2 for digit
26 } else {
27 charTypesMask |= 8; // Set bit 3 for special character
28 }
29 }
30
31 // Check if all four character types are present by confirming all bits are set in the mask
32 return charTypesMask === 15; // (binary 1111)
33}
34
Time and Space Complexity
The time complexity of the provided code is O(n)
, where n
is the length of the password
string. This is because the function consists of a single for loop that iterates through each character of the password string exactly once, performing a constant number of operations per character.
The space complexity of the code is O(1)
. The extra space used by the function is constant and does not depend on the size of the input password string. The variables used (mask
and c
) require a fixed amount of space and their size does not scale with the input.
Learn more about how to find time and space complexity quickly using problem constraints.
Which of the following shows the order of node visit in a Breadth-first Search?
Recommended Readings
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
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!