Facebook Pixel

2728. Count Houses in a Circular Street 🔒

EasyArrayInteractive
Leetcode Link

Problem Description

You are given a circular street with houses arranged in a circle. The street is represented by a Street class object, and you know there are at most k houses on this street. Each house has a door that can be either open or closed initially.

You start standing in front of one of the houses on this street. Your goal is to count the total number of houses on the street.

To interact with the street, you have access to these methods:

  • openDoor(): Opens the door of the house you're currently facing
  • closeDoor(): Closes the door of the house you're currently facing
  • isDoorOpen(): Returns true if the current house's door is open, false if closed
  • moveRight(): Move to the next house on your right
  • moveLeft(): Move to the next house on your left

Since the street is circular, moving continuously in one direction will eventually bring you back to where you started.

The solution works by first marking all houses by opening their doors. It moves left k times (guaranteeing a full circle since there are at most k houses), opening each door along the way. Then it counts the houses by checking for open doors and closing them as it goes, continuing until it encounters a closed door (which means it has completed the circle and returned to the starting point).

The algorithm ensures all houses are visited and counted exactly once by using the door states as markers to track which houses have been processed.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need a way to mark our starting position and know when we've completed a full circle around the street. Since we can manipulate door states, we can use them as markers.

Think of it like leaving breadcrumbs to find your way. If we start by opening all doors as we move around, we create a clear marker system. Since the street is circular and has at most k houses, moving k times in any direction guarantees we've gone around the entire circle at least once (even if there are fewer houses than k).

Why open all doors first? This gives us a uniform state - every house will have an open door. This is crucial because we don't know the initial state of the doors, which could be random. By opening all doors, we reset the street to a known state.

After creating this uniform state, we can count houses by traversing the circle once more. As we encounter each open door, we know it's a house we marked earlier. We close it and increment our counter. When we encounter the first closed door, we know we've completed exactly one full circle and returned to where we started counting.

The elegance of this approach is that it handles the circular nature perfectly - we don't need to remember our starting position or worry about counting houses multiple times. The door states act as both our markers and our stopping condition.

Solution Approach

The implementation consists of two main phases: marking and counting.

Phase 1: Marking All Houses

for _ in range(k):
    street.openDoor()
    street.moveLeft()

We iterate k times, where k is the maximum possible number of houses. In each iteration:

  • Open the door of the current house using street.openDoor()
  • Move to the left house using street.moveLeft()

Since the street is circular and contains at most k houses, this guarantees we visit every house at least once. If there are fewer than k houses, we'll loop around the circle multiple times, but that's fine - opening an already open door has no adverse effect.

Phase 2: Counting Houses

ans = 0
while street.isDoorOpen():
    street.closeDoor()
    street.moveLeft()
    ans += 1

After marking, we're positioned at some house in the circle. We then:

  • Check if the current door is open using street.isDoorOpen()
  • If open, we know this is a house we marked:
    • Close the door with street.closeDoor() to mark it as counted
    • Move left to the next house with street.moveLeft()
    • Increment our counter ans += 1
  • Continue until we encounter a closed door

The loop terminates when isDoorOpen() returns false, which happens when we've made a complete circle and returned to the first house we closed. At this point, ans contains the exact count of houses on the street.

The algorithm has a time complexity of O(k) for marking and O(n) for counting where n is the actual number of houses, giving us O(k) overall. The space complexity is O(1) as we only use a single counter variable.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with a circular street containing 4 houses, where k = 10 (the maximum possible houses). Imagine the houses are arranged in a circle like this:

Initial state:
    House 1 (closed)
    You are here
       
House 4     House 2
(open)      (closed)
  
    House 3 (open)

Phase 1: Marking All Houses (Opening Doors)

We'll move left k = 10 times, opening each door:

  • Iteration 1: Open House 1 → Move left to House 4
  • Iteration 2: Open House 4 (already open, no change) → Move left to House 3
  • Iteration 3: Open House 3 (already open, no change) → Move left to House 2
  • Iteration 4: Open House 2 → Move left to House 1
  • Iteration 5: Open House 1 (already open) → Move left to House 4
  • ... (iterations 6-10 continue cycling through, but all doors are already open)

After 10 iterations, we're at House 2, and all doors are open:

    House 1 (open)
       
House 4     House 2
(open)      (open)
         You are here
    House 3 (open)

Phase 2: Counting Houses

Now we count by closing doors until we encounter a closed one:

  • Check House 2: Door is open → Close it, move left to House 1, count = 1
  • Check House 1: Door is open → Close it, move left to House 4, count = 2
  • Check House 4: Door is open → Close it, move left to House 3, count = 3
  • Check House 3: Door is open → Close it, move left to House 2, count = 4
  • Check House 2: Door is closed (we closed it in step 1) → Stop!

Final count: 4 houses

The algorithm correctly identified all 4 houses by using the door states as markers. The key insight is that after opening all doors in Phase 1, we create a uniform state that allows us to count exactly once around the circle in Phase 2.

Solution Implementation

1# Definition for a street.
2# class Street:
3#     def openDoor(self):
4#         pass
5#     def closeDoor(self):
6#         pass
7#     def isDoorOpen(self):
8#         pass
9#     def moveRight(self):
10#         pass
11#     def moveLeft(self):
12#         pass
13
14from typing import Optional
15
16
17class Solution:
18    def houseCount(self, street: Optional["Street"], k: int) -> int:
19        # Mark k houses by opening their doors while moving left
20        # This creates a boundary marker at position -k
21        for _ in range(k):
22            street.openDoor()
23            street.moveLeft()
24      
25        # Count houses by moving left from the starting position
26        # Continue until we find an open door (the boundary marker)
27        house_count = 0
28        while street.isDoorOpen():
29            street.closeDoor()
30            street.moveLeft()
31            house_count += 1
32      
33        return house_count
34
1/**
2 * Definition for a street.
3 * class Street {
4 *     public Street(int[] doors);
5 *     public void openDoor();
6 *     public void closeDoor();
7 *     public boolean isDoorOpen();
8 *     public void moveRight();
9 *     public void moveLeft();
10 * }
11 */
12class Solution {
13    /**
14     * Counts the total number of houses on a circular street.
15     * 
16     * @param street The street object representing a circular street with houses
17     * @param k The initial position offset (number of steps to move left initially)
18     * @return The total number of houses on the street
19     */
20    public int houseCount(Street street, int k) {
21        // Mark the starting position by opening doors while moving left k times
22        // This creates a trail of k open doors to identify when we've made a full circle
23        while (k-- > 0) {
24            street.openDoor();
25            street.moveLeft();
26        }
27      
28        // Count houses by continuing to move left until we encounter an open door
29        // An open door indicates we've completed a full circle around the street
30        int houseCount = 0;
31        while (street.isDoorOpen()) {
32            houseCount++;
33            street.closeDoor();  // Close the door to avoid counting it again
34            street.moveLeft();   // Move to the next house
35        }
36      
37        return houseCount;
38    }
39}
40
1/**
2 * Definition for a street.
3 * class Street {
4 * public:
5 *     Street(vector<int> doors);
6 *     void openDoor();
7 *     void closeDoor();
8 *     bool isDoorOpen();
9 *     void moveRight();
10 *     void moveLeft();
11 * };
12 */
13class Solution {
14public:
15    /**
16     * Counts the total number of houses on the street.
17     * 
18     * @param street - Pointer to the Street object representing the street with houses
19     * @param k - The number of positions available to move (k >= number of houses)
20     * @return The total count of houses on the street
21     */
22    int houseCount(Street* street, int k) {
23        // Step 1: Mark all houses by opening their doors
24        // Move left k times while opening doors at each position
25        while (k--) {
26            street->openDoor();
27            street->moveLeft();
28        }
29      
30        // Step 2: Count the marked houses
31        // We're now at position after k moves left from starting position
32        int houseCount = 0;
33      
34        // Count all houses with open doors (the ones we marked earlier)
35        // Move left and count until we find a closed door (boundary)
36        while (street->isDoorOpen()) {
37            houseCount++;
38            street->closeDoor();  // Close the door after counting
39            street->moveLeft();   // Move to the next position
40        }
41      
42        return houseCount;
43    }
44};
45
1/**
2 * Counts the number of houses on a street by marking doors and then counting them.
3 * 
4 * The algorithm works in two phases:
5 * 1. First phase: Move k positions to the left while opening doors to mark our starting range
6 * 2. Second phase: Continue moving left and count all open doors (previously visited positions)
7 * 
8 * @param street - The street object with methods to interact with houses
9 * @param k - The number of initial positions to mark
10 * @returns The total number of houses on the street
11 */
12function houseCount(street: Street | null, k: number): number {
13    // Phase 1: Mark k houses by opening their doors while moving left
14    // This establishes our initial marked range
15    while (k-- > 0) {
16        street.openDoor();
17        street.moveLeft();
18    }
19  
20    // Phase 2: Count all houses with open doors
21    // Continue moving left until we find a closed door (unvisited house)
22    let houseCounter: number = 0;
23  
24    while (street.isDoorOpen()) {
25        // Increment counter for each open door found
26        ++houseCounter;
27      
28        // Close the door to mark it as counted
29        street.closeDoor();
30      
31        // Move to the next house on the left
32        street.moveLeft();
33    }
34  
35    // Return the total number of houses counted
36    return houseCounter;
37}
38

Time and Space Complexity

Time Complexity: O(k)

The algorithm consists of two main phases:

  1. First loop: Moves left k times while opening doors - O(k) operations
  2. Second loop: Counts houses by checking if doors are open and moving left - this loop runs at most k+1 times since we can only have opened at most k doors in the first phase (plus potentially one door that was already at the starting position)

Therefore, the total time complexity is O(k) + O(k) = O(k).

Space Complexity: O(1)

The algorithm only uses a constant amount of extra space:

  • One integer variable ans to count the houses
  • The loop counter variable _ in the first loop

No additional data structures are created that scale with the input size k, so the space complexity is constant.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Incorrect Direction Assumption

Problem: A common mistake is assuming that after the marking phase, you're positioned at the "first" house that needs to be counted. In reality, after moving left k times, you're positioned at an arbitrary house in the circle, and the counting phase relies on finding a closed door to know when to stop.

Wrong Implementation:

# Incorrect: Assumes we're at a specific starting position
for _ in range(k):
    street.openDoor()
    street.moveLeft()

# This might miss houses or count incorrectly
ans = 0
for _ in range(k):  # Wrong: using fixed iteration count
    if street.isDoorOpen():
        ans += 1
        street.closeDoor()
    street.moveLeft()

Solution: Use the door state as the termination condition, not a fixed loop count. The correct approach continues until finding a closed door, which indicates we've completed the circle.

Pitfall 2: Not Resetting Door States

Problem: If you try alternative approaches like counting first and then resetting, you might forget to properly reset all doors to their original state, which could affect subsequent operations or test cases.

Wrong Implementation:

# Count houses without proper cleanup
ans = 0
while street.isDoorOpen():
    ans += 1
    street.moveLeft()
    # Forgot to close doors - leaves them in altered state

Solution: Always pair state changes appropriately. If you open doors for marking, close them during counting to ensure clean state management.

Pitfall 3: Mixing Movement Directions

Problem: Using inconsistent movement directions between marking and counting phases can lead to incorrect results or infinite loops.

Wrong Implementation:

# Mark houses moving left
for _ in range(k):
    street.openDoor()
    street.moveLeft()

# Count houses moving right - might not work as expected
ans = 0
while street.isDoorOpen():
    street.closeDoor()
    street.moveRight()  # Inconsistent direction
    ans += 1

Solution: Maintain consistent movement direction throughout the algorithm. The solution uses moveLeft() in both phases to ensure predictable behavior and correct termination.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using the following tree as input?

1def serialize(root):
2    res = []
3    def dfs(root):
4        if not root:
5            res.append('x')
6            return
7        res.append(root.val)
8        dfs(root.left)
9        dfs(root.right)
10    dfs(root)
11    return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4    StringJoiner res = new StringJoiner(" ");
5    serializeDFS(root, res);
6    return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10    if (root == null) {
11        result.add("x");
12        return;
13    }
14    result.add(Integer.toString(root.val));
15    serializeDFS(root.left, result);
16    serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2    let res = [];
3    serialize_dfs(root, res);
4    return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8    if (!root) {
9        res.push("x");
10        return;
11    }
12    res.push(root.val);
13    serialize_dfs(root.left, res);
14    serialize_dfs(root.right, res);
15}
16

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More