LeetCode 2665 - Counter II

The problem asks us to implement a counter object that maintains a mutable integer value, initialized to a given number init. This counter object must provide three operations: increment, decrement, and reset.

LeetCode Problem 2665

Difficulty: 🟢 Easy
Topics:

Solution

Problem Understanding

The problem asks us to implement a counter object that maintains a mutable integer value, initialized to a given number init. This counter object must provide three operations: increment, decrement, and reset. The increment method increases the current counter value by 1 and returns the updated value. The decrement method decreases the current counter value by 1 and returns the updated value. The reset method restores the counter to its initial value and returns that value.

The input consists of a single integer init, which can range from -1000 to 1000. The output is a sequence of integers resulting from applying a sequence of method calls on the counter object. The problem guarantees that each call will be one of "increment", "decrement", or "reset". The number of calls can be up to 1000, which is relatively small and allows simple operations to run efficiently.

Important edge cases include initializing the counter with negative numbers, calling the same method multiple times in a row (especially reset), and handling zero as the initial value. Since the problem is about encapsulating state and simple arithmetic, the main concern is correctly maintaining and updating the internal counter state according to the method calls.

Approaches

The brute-force approach would involve manually tracking the counter value with a variable and updating it directly for each operation. This approach is correct and sufficient given the constraints because each operation is constant time, but it does not leverage object-oriented encapsulation, which is required by the problem.

The optimal approach uses closures in Python or struct methods in Go to encapsulate the counter state. By storing the current value and the initial value within the closure or struct, each method can update and return the state efficiently in constant time. This approach directly models the object-oriented behavior required by the problem while ensuring operations remain simple and fast.

Approach Time Complexity Space Complexity Notes
Brute Force O(1) per operation O(1) Use a single variable to track state; does not provide encapsulation
Optimal O(1) per operation O(1) Encapsulates state in closure (Python) or struct (Go); methods maintain internal state

Algorithm Walkthrough

  1. Store the initial value init in a variable called initial and create a variable current to track the current value of the counter. Initialize current with init.
  2. Define the increment method. When called, this method adds 1 to current and returns the new value.
  3. Define the decrement method. When called, this method subtracts 1 from current and returns the new value.
  4. Define the reset method. When called, this method sets current back to initial and returns it.
  5. Return an object (Python dictionary or Go struct with methods) containing the three methods, so that the state remains encapsulated and can be modified only via these operations.

Why it works: The algorithm maintains a single invariant: current always reflects the counter value after all previous operations. Each method updates current according to the operation requested, and reset ensures current can always return to the initial value. This invariant guarantees correctness for any sequence of calls.

Python Solution

from typing import Callable, Dict

def createCounter(init: int) -> Dict[str, Callable[[], int]]:
    current = init
    initial = init
    
    def increment() -> int:
        nonlocal current
        current += 1
        return current
    
    def decrement() -> int:
        nonlocal current
        current -= 1
        return current
    
    def reset() -> int:
        nonlocal current
        current = initial
        return current
    
    return {"increment": increment, "decrement": decrement, "reset": reset}

The Python implementation uses a closure to encapsulate the current counter and the immutable initial value. Each method modifies current using nonlocal to indicate that current exists in the outer function scope. This ensures that state is maintained across method calls. The dictionary returned maps method names to their respective closures, allowing external code to call the methods dynamically.

Go Solution

package main

type Counter struct {
    current int
    initial int
}

func createCounter(init int) *Counter {
    return &Counter{
        current: init,
        initial: init,
    }
}

func (c *Counter) Increment() int {
    c.current += 1
    return c.current
}

func (c *Counter) Decrement() int {
    c.current -= 1
    return c.current
}

func (c *Counter) Reset() int {
    c.current = c.initial
    return c.current
}

In Go, we use a struct Counter to encapsulate the state. Methods with pointer receivers allow modifications to the internal current field. This struct-based approach mirrors the closure approach in Python but uses Go idioms for encapsulation and state management.

Worked Examples

Example 1: init = 5, calls = ["increment", "reset", "decrement"]

Step Operation Current Output
1 increment 6 6
2 reset 5 5
3 decrement 4 4

Example 2: init = 0, calls = ["increment", "increment", "decrement", "reset", "reset"]

Step Operation Current Output
1 increment 1 1
2 increment 2 2
3 decrement 1 1
4 reset 0 0
5 reset 0 0

Complexity Analysis

Measure Complexity Explanation
Time O(1) per operation Each method performs a single arithmetic operation and return
Space O(1) Only two integers are stored for state; additional storage for method references is constant

The complexity is optimal for this problem because each operation affects only a single value, and there is no need for dynamic data structures.

Test Cases

counter = createCounter(5)
assert counter["increment"]() == 6  # increment from 5
assert counter["reset"]() == 5      # reset to initial value
assert counter["decrement"]() == 4  # decrement from 5 to 4

counter = createCounter(0)
assert counter["increment"]() == 1  # 0 -> 1
assert counter["increment"]() == 2  # 1 -> 2
assert counter["decrement"]() == 1  # 2 -> 1
assert counter["reset"]() == 0      # reset to 0
assert counter["reset"]() == 0      # reset again to 0

counter = createCounter(-3)
assert counter["decrement"]() == -4  # decrement negative initial
assert counter["increment"]() == -3  # back to initial
assert counter["reset"]() == -3      # reset to initial
Test Why
init=5, calls=["increment","reset","decrement"] basic functionality test
init=0, calls with multiple resets ensures repeated resets work
init=-3, operations mix tests negative initial values and state maintenance

Edge Cases

One edge case is initializing the counter with zero. Zero is neither positive nor negative, so incrementing or decrementing should behave normally. The implementation handles this because current starts at zero and is modified by ±1.

Another edge case is initializing with a negative value. A naive implementation might incorrectly handle negative arithmetic, but our solution treats all integers equally. current can be negative, positive, or zero, and operations update it consistently.

A third edge case is calling reset multiple times in a row. Some implementations might accidentally reset current to a previous value other than init, but storing initial separately guarantees that reset always restores the original initial value, regardless of the current state.