LeetCode 2315 - Count Asterisks
The problem gives us a string s containing lowercase English letters, vertical bars '|', and asterisks ''. The key rule is that every two consecutive vertical bars form a pair. Any asterisk located between the two bars of a pair must be ignored when counting.
Difficulty: 🟢 Easy
Topics: String
Solution
Problem Understanding
The problem gives us a string s containing lowercase English letters, vertical bars '|', and asterisks '*'. The key rule is that every two consecutive vertical bars form a pair. Any asterisk located between the two bars of a pair must be ignored when counting.
Our task is to count only the asterisks that are outside these paired bar sections.
Another way to think about the problem is that the string alternates between two modes:
- Outside a pair of bars, where
'*'characters should be counted - Inside a pair of bars, where
'*'characters should be ignored
Each time we encounter a vertical bar '|', we switch between these two modes.
For example, consider:
l|*e*et|c**o|*de|
The substring *e*et lies between the first pair of bars, so its asterisks are ignored. The substring *de lies between the second pair of bars, so those are ignored as well. Only the two asterisks in c**o are counted.
The constraints are small:
1 <= s.length <= 1000- The string contains only lowercase letters,
|, and* - The number of vertical bars is guaranteed to be even
Since the maximum length is only 1000, even less efficient approaches could work. However, the problem naturally suggests a simple linear scan solution.
Some important edge cases include:
- Strings with no asterisks at all
- Strings with no vertical bars
- Strings where all asterisks are inside bar pairs
- Strings where all asterisks are outside bar pairs
- Consecutive bar pairs with no characters between them
- Strings beginning or ending with bars
The guarantee that the number of bars is even is important because it ensures every opening section has a matching closing section.
Approaches
Brute Force Approach
A brute force solution could first identify every pair of vertical bars and record all character ranges that should be ignored. After determining these excluded intervals, we could iterate through the string again and count only the asterisks that are not inside any excluded range.
One possible implementation is:
- Store the indices of all
'|'characters. - Group them into pairs.
- Mark all indices inside those pairs as excluded.
- Iterate through the string and count valid
'*'characters.
This approach is correct because every ignored region is explicitly identified before counting begins.
However, this method is unnecessarily complicated. It may also require additional space to store excluded ranges or markers. Although the constraints are small enough that this would still pass, the problem can be solved much more elegantly with a single pass.
Optimal Approach
The key observation is that we do not actually need to store ranges between bars. We only need to know whether the current position is inside or outside a bar pair.
We can maintain a boolean flag:
inside_bars = Falseinitially- Every time we encounter
'|', toggle the flag - If we encounter
'*'whileinside_barsisFalse, increment the answer
This works because the bars always come in valid pairs. Each '|' flips the current counting state.
The solution becomes a straightforward linear scan with constant extra space.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n) | O(n) | Stores bar positions or excluded ranges before counting |
| Optimal | O(n) | O(1) | Single pass using a boolean state |
Algorithm Walkthrough
- Initialize a counter variable
asterisk_countto0. This will store the number of valid asterisks outside bar pairs. - Initialize a boolean variable
inside_barstoFalse. This variable tracks whether the current character lies inside a paired bar section. - Traverse the string character by character.
- For each character:
-
If the character is
'|', toggle the value ofinside_bars. -
If we were outside a pair, we now enter a pair.
-
If we were inside a pair, we now exit the pair.
-
Else if the character is
'*'andinside_barsisFalse, incrementasterisk_count. -
All lowercase letters can simply be ignored because they do not affect the answer.
- After processing the entire string, return
asterisk_count.
Why it works
The algorithm maintains the invariant that inside_bars correctly represents whether the current position lies between a matching pair of vertical bars.
Since every '|' belongs to exactly one pair, toggling the state at each bar correctly tracks whether asterisks should currently be ignored or counted. Therefore, every counted asterisk is outside all bar pairs, and every ignored asterisk is inside a bar pair.
Python Solution
class Solution:
def countAsterisks(self, s: str) -> int:
asterisk_count = 0
inside_bars = False
for char in s:
if char == '|':
inside_bars = not inside_bars
elif char == '*' and not inside_bars:
asterisk_count += 1
return asterisk_count
The implementation directly follows the algorithm described earlier.
The variable asterisk_count stores the final answer. The boolean inside_bars tracks whether the current scan position lies between a matching pair of vertical bars.
As we iterate through the string:
- Encountering
'|'flips the state - Encountering
'*'increments the answer only if we are currently outside a bar pair - Letters are ignored because they do not affect counting
The implementation is concise because the problem naturally maps to state tracking during a single traversal.
Go Solution
func countAsterisks(s string) int {
asteriskCount := 0
insideBars := false
for _, char := range s {
if char == '|' {
insideBars = !insideBars
} else if char == '*' && !insideBars {
asteriskCount++
}
}
return asteriskCount
}
The Go implementation is nearly identical to the Python version.
One minor language-specific difference is that Go iterates over the string using range, which returns runes. Since all characters in this problem are ASCII, rune handling introduces no complications.
No special handling for nil or empty input is required because the constraints guarantee at least one character.
Worked Examples
Example 1
Input:
s = "l|*e*et|c**o|*de|"
| Character | inside_bars | Action | Count |
|---|---|---|---|
| l | False | Ignore | 0 |
| | | True | Enter bar section | 0 |
| * | True | Ignore | 0 |
| e | True | Ignore | 0 |
| * | True | Ignore | 0 |
| e | True | Ignore | 0 |
| t | True | Ignore | 0 |
| | | False | Exit bar section | 0 |
| c | False | Ignore | 0 |
| * | False | Count | 1 |
| * | False | Count | 2 |
| o | False | Ignore | 2 |
| | | True | Enter bar section | 2 |
| * | True | Ignore | 2 |
| d | True | Ignore | 2 |
| e | True | Ignore | 2 |
| | | False | Exit bar section | 2 |
Final answer:
2
Example 2
Input:
s = "iamprogrammer"
| Character | inside_bars | Action | Count |
|---|---|---|---|
| i | False | Ignore | 0 |
| a | False | Ignore | 0 |
| m | False | Ignore | 0 |
| p | False | Ignore | 0 |
| r | False | Ignore | 0 |
| o | False | Ignore | 0 |
| g | False | Ignore | 0 |
| r | False | Ignore | 0 |
| a | False | Ignore | 0 |
| m | False | Ignore | 0 |
| m | False | Ignore | 0 |
| e | False | Ignore | 0 |
| r | False | Ignore | 0 |
Final answer:
0
Example 3
Input:
s = "yo|uar|e**|b|e***au|tifu|l"
| Character | inside_bars | Action | Count |
|---|---|---|---|
| y | False | Ignore | 0 |
| o | False | Ignore | 0 |
| | | True | Enter section | 0 |
| u | True | Ignore | 0 |
| a | True | Ignore | 0 |
| r | True | Ignore | 0 |
| | | False | Exit section | 0 |
| e | False | Ignore | 0 |
| * | False | Count | 1 |
| * | False | Count | 2 |
| | | True | Enter section | 2 |
| b | True | Ignore | 2 |
| | | False | Exit section | 2 |
| e | False | Ignore | 2 |
| * | False | Count | 3 |
| * | False | Count | 4 |
| * | False | Count | 5 |
| a | False | Ignore | 5 |
| u | False | Ignore | 5 |
| | | True | Enter section | 5 |
| t | True | Ignore | 5 |
| i | True | Ignore | 5 |
| f | True | Ignore | 5 |
| u | True | Ignore | 5 |
| | | False | Exit section | 5 |
| l | False | Ignore | 5 |
Final answer:
5
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | The string is scanned exactly once |
| Space | O(1) | Only a few variables are used |
The algorithm performs a single linear traversal through the input string. Each character is processed exactly once, so the runtime grows proportionally to the string length.
The memory usage remains constant because we only store two variables: the answer counter and the boolean state tracking whether we are inside a bar pair.
Test Cases
solution = Solution()
assert solution.countAsterisks("l|*e*et|c**o|*de|") == 2
# Basic example with ignored and counted asterisks
assert solution.countAsterisks("iamprogrammer") == 0
# No asterisks present
assert solution.countAsterisks("yo|uar|e**|b|e***au|tifu|l") == 5
# Multiple bar sections
assert solution.countAsterisks("***") == 3
# All asterisks counted
assert solution.countAsterisks("|***|") == 0
# All asterisks ignored
assert solution.countAsterisks("|a|*|b|**") == 3
# Mixed counted and ignored regions
assert solution.countAsterisks("||***") == 3
# Empty bar section followed by counted asterisks
assert solution.countAsterisks("*|*|*") == 2
# Single ignored asterisk in the middle
assert solution.countAsterisks("|*||*|") == 0
# Multiple adjacent bar pairs
assert solution.countAsterisks("abc") == 0
# No bars and no asterisks
assert solution.countAsterisks("*") == 1
# Smallest meaningful counting case
| Test | Why |
|---|---|
| `"l | _e_et |
"iamprogrammer" |
No asterisks |
| `"yo | uar |
"***" |
All asterisks counted |
| `" | *** |
| `" | a |
| `" | |
| `"* | * |
| `" | * |
"abc" |
No special characters |
"*" |
Minimum nontrivial case |
Edge Cases
One important edge case is a string with no vertical bars at all, such as "***" or "abc". A naive implementation that assumes bars always exist could fail or unnecessarily complicate the logic. The current implementation handles this naturally because inside_bars remains False for the entire traversal, so all asterisks are counted correctly.
Another important edge case is when all asterisks are inside a bar pair, such as "|***|". It is easy to accidentally count these if the state transition logic is incorrect. The implementation correctly toggles into ignored mode after the first '|' and back out after the second '|', ensuring all interior asterisks are skipped.
A third important edge case is adjacent or empty bar pairs, such as "||***" or "|*||*|". These cases can expose bugs in implementations that incorrectly assume there must be characters between bars. Since the algorithm simply toggles state whenever a bar appears, empty sections are handled correctly without requiring any special logic.
A final edge case is strings beginning or ending with bars. For example, "|abc|" or "*|abc|". Incorrect implementations might accidentally count characters after the final bar or mishandle the initial transition. The boolean state approach cleanly handles both scenarios because each bar consistently flips the current mode.*