House Robber II

Difficulty: Medium

Same setup as before — you're robbing houses without ever hitting two adjacent ones — except now the houses are arranged in a circle: the first house and the last house count as neighbors too. Given the cash in each house as nums, find the maximum you can steal.

Examples

Input: nums = [2, 3, 2]
Output: 3

Robbing houses 0 and 2 isn't allowed since they're adjacent in the circle, so the best is just house 1 (3).

Input: nums = [1, 2, 3, 1]
Output: 4

Rob houses 0 and 2: 1 + 3 = 4. House 3 can't join house 0 since they're now neighbors.

Input: nums = [1, 2, 3]
Output: 3

All three houses touch each other in a circle of 3, so only one house can be robbed.

Constraints

  • 1 <= nums.length <= 100

  • 0 <= nums[i] <= 1000

Approach

The circular wrap-around only matters because of one pair: house 0 and the last house. Any robbery plan either skips house 0 entirely, or skips the last house entirely — it's never allowed to include both.

So break the circle into two straight-line versions of the same problem: one that considers every house except the last, and one that considers every house except the first. Solve each with the ordinary House Robber approach, and take whichever total is bigger. The one edge case to handle separately is a single house, since a street of one house has no neighbors to conflict with.

Solutions

Brute Force — Two Linear Recursions

Recursively solve plain (non-circular) House Robber on the range that excludes the last house, and again on the range that excludes the first house, then take the max of the two.

function rob(nums) {
  const n = nums.length;
  if (n === 1) return nums[0];

  function bestInRange(start, end) {
    function best(i) {
      if (i < start) return 0;
      const skip = best(i - 1);
      const take = best(i - 2) + nums[i];
      return Math.max(skip, take);
    }
    return best(end);
  }

  const excludeLast = bestInRange(0, n - 2);
  const excludeFirst = bestInRange(1, n - 1);
  return Math.max(excludeLast, excludeFirst);
}

Time: O(2^n) · Space: O(n) — recursion depth

Optimal — Bottom-Up Twice

Run the O(n) two-variable House Robber sweep on the slice that drops the last house, then again on the slice that drops the first house, and return the larger result.

function rob(nums) {
  const n = nums.length;
  if (n === 1) return nums[0];

  function robLine(houses) {
    let prev2 = 0;
    let prev1 = 0;
    for (const money of houses) {
      const current = Math.max(prev1, prev2 + money);
      prev2 = prev1;
      prev1 = current;
    }
    return prev1;
  }

  const excludeLast = robLine(nums.slice(0, n - 1));
  const excludeFirst = robLine(nums.slice(1));
  return Math.max(excludeLast, excludeFirst);
}

Time: O(n) · Space: O(n) — for the two slices