LeetCode 2299 - Strong Password Checker II

The problem asks us to determine whether a given password string satisfies a set of security requirements. We are given a single string, password, and we must return true if every condition is satisfied, otherwise return false.

LeetCode Problem 2299

Difficulty: 🟢 Easy
Topics: String

Solution

Problem Understanding

The problem asks us to determine whether a given password string satisfies a set of security requirements. We are given a single string, password, and we must return true if every condition is satisfied, otherwise return false.

A password is considered strong only if all of the following are true:

  • The length is at least 8 characters.
  • There is at least one lowercase English letter.
  • There is at least one uppercase English letter.
  • There is at least one digit.
  • There is at least one special character from the exact set "!@#$%^&*()-+".
  • No two adjacent characters are the same.

The input size is very small because the maximum password length is only 100. This means performance is not a concern in the traditional sense, and even less efficient solutions would work comfortably within limits. However, the goal is still to design a clean and optimal solution.

The most important detail is that all conditions must be checked simultaneously. Missing even one requirement makes the password invalid.

Several edge cases are important to think about early:

  • Passwords shorter than 8 characters should immediately fail.
  • Adjacent repeated characters can appear anywhere in the string, including special characters like "++" or digits like "11".
  • The password may contain all required character types but still fail because of repeated adjacent characters.
  • A password with exactly 8 characters should still pass if all other conditions are satisfied.
  • The set of valid special characters is restricted. Only characters from "!@#$%^&*()-+" count.

Approaches

Brute Force Approach

A brute-force style solution would separately scan the string multiple times. One pass could check for lowercase letters, another for uppercase letters, another for digits, another for special characters, and yet another pass could check adjacent duplicates.

This approach is straightforward because each condition is independent. By repeatedly iterating over the string and checking one requirement at a time, we can eventually determine whether the password is valid.

The solution is correct because every rule is verified explicitly. However, it is inefficient because the string is traversed multiple times unnecessarily. Even though the constraints are small enough that this inefficiency does not matter practically, it is still not ideal from a design perspective.

Optimal Approach

The key observation is that every condition can be checked during a single traversal of the password.

As we iterate through each character:

  • We can track whether we have seen a lowercase letter.
  • We can track whether we have seen an uppercase letter.
  • We can track whether we have seen a digit.
  • We can track whether we have seen a special character.
  • We can compare the current character with the previous character to detect adjacent duplicates.

By maintaining a few boolean flags, we can validate all conditions in one pass. This reduces unnecessary repeated work and produces a clean, efficient implementation.

Approach Time Complexity Space Complexity Notes
Brute Force O(5n) O(1) Multiple passes over the string for different checks
Optimal O(n) O(1) Single traversal checks all conditions simultaneously

Algorithm Walkthrough

  1. First, check whether the password length is at least 8. If the length is smaller than 8, immediately return false because the password cannot possibly be strong.
  2. Create a set containing all allowed special characters. A set is useful because membership checks are very fast and the code becomes cleaner.
  3. Initialize four boolean variables:
  • has_lower
  • has_upper
  • has_digit
  • has_special

These variables track whether each required category has been encountered. 4. Traverse the password string character by character. 5. For every character:

  • If it is lowercase, set has_lower = True.
  • If it is uppercase, set has_upper = True.
  • If it is a digit, set has_digit = True.
  • If it belongs to the special character set, set has_special = True.
  1. While iterating, also compare the current character with the previous character. If they are equal, immediately return false because adjacent duplicate characters are not allowed.
  2. After the loop finishes, return the logical AND of all four boolean flags. The password is valid only if every category requirement has been satisfied.

Why it works

The algorithm works because every password rule is checked exactly once during the traversal. The boolean flags guarantee that each required character category is present somewhere in the password, while the adjacent comparison guarantees that no consecutive characters are identical. Since the algorithm rejects invalid passwords immediately and accepts only when all conditions hold, the result is always correct.

Python Solution

class Solution:
    def strongPasswordCheckerII(self, password: str) -> bool:
        if len(password) < 8:
            return False

        special_characters = set("!@#$%^&*()-+")

        has_lower = False
        has_upper = False
        has_digit = False
        has_special = False

        for index, char in enumerate(password):
            if index > 0 and password[index] == password[index - 1]:
                return False

            if char.islower():
                has_lower = True
            elif char.isupper():
                has_upper = True
            elif char.isdigit():
                has_digit = True

            if char in special_characters:
                has_special = True

        return (
            has_lower
            and has_upper
            and has_digit
            and has_special
        )

The implementation begins with the length check because passwords shorter than 8 characters automatically fail.

A set is used for special characters because checking membership in a set is efficient and expressive.

The four boolean flags track whether each required category has appeared. During the traversal, the current character is compared against the previous character to detect adjacent duplicates immediately.

Character classification uses Python string helper methods:

  • islower() checks lowercase letters.
  • isupper() checks uppercase letters.
  • isdigit() checks digits.

At the end, the function returns True only if every required condition has been satisfied.

Go Solution

func strongPasswordCheckerII(password string) bool {
	if len(password) < 8 {
		return false
	}

	specialCharacters := map[rune]bool{
		'!': true,
		'@': true,
		'#': true,
		'$': true,
		'%': true,
		'^': true,
		'&': true,
		'*': true,
		'(': true,
		')': true,
		'-': true,
		'+': true,
	}

	hasLower := false
	hasUpper := false
	hasDigit := false
	hasSpecial := false

	for i, ch := range password {
		if i > 0 && rune(password[i]) == rune(password[i-1]) {
			return false
		}

		if ch >= 'a' && ch <= 'z' {
			hasLower = true
		} else if ch >= 'A' && ch <= 'Z' {
			hasUpper = true
		} else if ch >= '0' && ch <= '9' {
			hasDigit = true
		}

		if specialCharacters[ch] {
			hasSpecial = true
		}
	}

	return hasLower && hasUpper && hasDigit && hasSpecial
}

The Go solution follows the same logic as the Python implementation. Since Go does not provide convenience methods like islower() or isdigit(), character classification is performed using ASCII range comparisons.

A map[rune]bool is used to store the allowed special characters. This provides constant time membership checks similar to a Python set.

Go strings are UTF-8 encoded, but this problem only contains ASCII characters, so indexing into the string is safe here.

Worked Examples

Example 1

Input:

password = "IloveLe3tcode!"
Index Character Lower Upper Digit Special Adjacent Duplicate?
0 I No Yes No No No
1 l Yes Yes No No No
2 o Yes Yes No No No
3 v Yes Yes No No No
4 e Yes Yes No No No
5 L Yes Yes No No No
6 e Yes Yes No No No
7 3 Yes Yes Yes No No
8 t Yes Yes Yes No No
9 c Yes Yes Yes No No
10 o Yes Yes Yes No No
11 d Yes Yes Yes No No
12 e Yes Yes Yes No No
13 ! Yes Yes Yes Yes No

Final result:

True

All conditions are satisfied.

Example 2

Input:

password = "Me+You--IsMyDream"
Index Character Lower Upper Digit Special Adjacent Duplicate?
0 M No Yes No No No
1 e Yes Yes No No No
2 + Yes Yes No Yes No
3 Y Yes Yes No Yes No
4 o Yes Yes No Yes No
5 u Yes Yes No Yes No
6 - Yes Yes No Yes No
7 - Yes Yes No Yes Yes

At index 7, the current character matches the previous character.

Final result:

False

The password also lacks a digit.

Example 3

Input:

password = "1aB!"

Length:

4

Since the password length is less than 8, the algorithm immediately returns:

False

Complexity Analysis

Measure Complexity Explanation
Time O(n) The password is scanned exactly once
Space O(1) Only a few boolean variables and a fixed-size character set are used

The algorithm performs a single linear traversal through the password. Each character is processed in constant time, resulting in overall linear complexity. The amount of extra memory does not grow with the input size because the boolean flags and special character set remain constant in size.

Test Cases

solution = Solution()

assert solution.strongPasswordCheckerII("IloveLe3tcode!") == True
# Valid strong password

assert solution.strongPasswordCheckerII("Me+You--IsMyDream") == False
# Adjacent duplicate characters and no digit

assert solution.strongPasswordCheckerII("1aB!") == False
# Too short

assert solution.strongPasswordCheckerII("Aa1!aaaa") == False
# Adjacent duplicate lowercase letters

assert solution.strongPasswordCheckerII("Aa1!Aa1!") == True
# Exactly 8 characters and all conditions satisfied

assert solution.strongPasswordCheckerII("abcdefgh") == False
# Missing uppercase, digit, and special character

assert solution.strongPasswordCheckerII("ABCDEFGH") == False
# Missing lowercase, digit, and special character

assert solution.strongPasswordCheckerII("12345678") == False
# Missing letters and special character

assert solution.strongPasswordCheckerII("AaAaAaAa") == False
# Missing digit and special character

assert solution.strongPasswordCheckerII("Aa1Aa1Aa") == False
# Missing special character

assert solution.strongPasswordCheckerII("Aa1!Aa11") == False
# Adjacent repeated digits

assert solution.strongPasswordCheckerII("Aa1!+Bb2") == True
# Multiple special characters and valid structure
Test Why
"IloveLe3tcode!" Standard valid example
"Me+You--IsMyDream" Detects adjacent duplicates
"1aB!" Verifies minimum length rule
"Aa1!aaaa" Detects repeated lowercase characters
"Aa1!Aa1!" Valid password with minimum valid length
"abcdefgh" Missing multiple required categories
"ABCDEFGH" Missing lowercase and other requirements
"12345678" Digits only
"AaAaAaAa" Missing digit and special character
"Aa1Aa1Aa" Missing special character
"Aa1!Aa11" Detects repeated adjacent digits
"Aa1!+Bb2" Valid password with mixed character types

Edge Cases

One important edge case is a password whose length is exactly 8 characters. It is easy to accidentally write code that requires more than 8 instead of at least 8. The implementation correctly checks len(password) < 8, which means length 8 is accepted.

Another important edge case is repeated adjacent special characters such as "Aa1!!abc". Some implementations only check repeated alphabetic characters, but the rule applies to every character type. The solution compares raw characters directly, so duplicates are detected regardless of whether they are letters, digits, or symbols.

A third edge case is a password that contains unsupported symbols. The problem guarantees that the input only contains valid allowed characters, but the implementation still safely checks special characters only against the explicit allowed set. This ensures that only the required special characters count toward satisfying the condition.

A final subtle edge case is when all requirements are met very early in the string, but an adjacent duplicate appears later. The implementation continues scanning the entire password and immediately rejects if any duplicate pair is found. This guarantees correctness even when most conditions are already satisfied.