LeetCode 2620 - Counter
The problem is asking us to implement a simple counter function with a closure-like behavior. Given an integer n, we need to return a function counter() that, when called the first time, returns n, and then increments the returned value by one for every subsequent call.
Difficulty: 🟢 Easy
Topics: —
Solution
Problem Understanding
The problem is asking us to implement a simple counter function with a closure-like behavior. Given an integer n, we need to return a function counter() that, when called the first time, returns n, and then increments the returned value by one for every subsequent call. Essentially, each call to the counter() function should produce a sequence starting from n and increasing by 1 each time: n, n+1, n+2, ....
The input n can be any integer between -1000 and 1000. The expected output is the sequence of integers produced by calling the counter() function repeatedly, as specified by a list of "call" operations.
The constraints indicate that the number of calls is at most 1000, which is very small, so performance is not a limiting factor. However, we need to ensure the counter behaves correctly with negative numbers and zero, as these are valid inputs.
Important edge cases include initializing the counter at a negative number, zero, or the maximum/minimum allowed values. The problem guarantees that the calls are always valid and the counter function is always called sequentially, so we do not need to handle invalid operations.
Approaches
The simplest approach is a naive one where you store the current counter value externally and manually update it every time the function is called. While this works perfectly, it does not leverage closures and would require additional external bookkeeping if implemented in a global context.
The optimal approach leverages closures (or function encapsulation) to maintain the internal state of the counter. By using a closure, the internal counter value persists across multiple calls without needing external variables. Each time the returned function is invoked, it increments and returns the stored value. This is simple, efficient, and idiomatic in both Python and Go.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(1) per call | O(1) | Maintain an external counter variable outside the function and increment it manually. |
| Optimal | O(1) per call | O(1) | Use a closure to encapsulate the counter state internally, automatically incrementing on each call. |
Algorithm Walkthrough
- Define a function
createCounterthat accepts an integernas input. This will be the initial value of our counter. - Inside
createCounter, define an inner functioncounter()that will serve as our returned function. This function will access a variablecurrentthat holds the current value of the counter. - Initialize a variable
currentwith the value ofn. - Every time the
counter()function is called, it should first store the current value to return, then incrementcurrentby 1. - Return the stored value to the caller.
- Finally,
createCountershould return thecounter()function itself.
The invariant here is that current always holds the next value that the counter function should return. Because current is enclosed within the closure, each call correctly updates the internal state without affecting external variables.
Python Solution
from typing import Callable
def createCounter(n: int) -> Callable[[], int]:
current = n
def counter() -> int:
nonlocal current
result = current
current += 1
return result
return counter
In this implementation, we define createCounter which initializes current to n. The inner function counter() uses nonlocal to modify current from the enclosing scope. It first stores the current value in result, increments current, and then returns result. This ensures the first call returns n and subsequent calls increment correctly.
Go Solution
package main
func createCounter(n int) func() int {
current := n
return func() int {
result := current
current++
return result
}
}
In Go, closures work similarly. The variable current is captured by the returned anonymous function, and its value persists across calls. Each invocation increments current and returns the previous value. Go does not require nonlocal because the closure automatically has access to outer scope variables.
Worked Examples
Example 1: n = 10
| Call | current | Returned |
|---|---|---|
| counter() | 10 | 10 |
| counter() | 11 | 11 |
| counter() | 12 | 12 |
Example 2: n = -2
| Call | current | Returned |
|---|---|---|
| counter() | -2 | -2 |
| counter() | -1 | -1 |
| counter() | 0 | 0 |
| counter() | 1 | 1 |
| counter() | 2 | 2 |
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(1) per call | Each call involves a simple variable increment and return. |
| Space | O(1) | Only a single integer is stored in the closure regardless of the number of calls. |
Since we are storing just one integer, the space usage does not grow with the number of calls. Each call performs a constant amount of work, making this optimal for the constraints.
Test Cases
# test cases
c1 = createCounter(10)
assert c1() == 10 # first call returns initial value
assert c1() == 11 # increments by 1
assert c1() == 12 # increments by 1
c2 = createCounter(-2)
assert c2() == -2 # first call returns initial value
assert c2() == -1
assert c2() == 0
assert c2() == 1
assert c2() == 2
c3 = createCounter(0)
assert c3() == 0 # counter starting at zero
assert c3() == 1
assert c3() == 2
c4 = createCounter(1000)
assert c4() == 1000 # maximum allowed value
assert c4() == 1001
c5 = createCounter(-1000)
assert c5() == -1000 # minimum allowed value
assert c5() == -999
| Test | Why |
|---|---|
| createCounter(10) | Normal positive starting value |
| createCounter(-2) | Negative starting value |
| createCounter(0) | Zero starting value |
| createCounter(1000) | Maximum allowed value |
| createCounter(-1000) | Minimum allowed value |
Edge Cases
One important edge case is when n is negative. The counter must correctly handle negative values, incrementing toward zero and beyond. The closure handles this naturally by incrementing the integer regardless of sign.
Another edge case is starting at zero. Some implementations might incorrectly initialize counters to 1 if they assume positive starting values. Our implementation directly uses n, so zero is returned correctly.
Finally, we need to consider the maximum and minimum bounds of n (1000 and -1000). The algorithm handles these without overflow in Python due to arbitrary-precision integers. In Go, int is platform-dependent, but the constraints ensure that the counter will remain within the 32-bit signed integer range during typical use cases.
This solution ensures correctness for all valid inputs, preserves state through closures, and provides constant-time performance for each call.