Lesson 18 of 66 6 min

Problem: Search in Rotated Sorted Array

Master the logic of Binary Search on shifted data. Learn how to identify the sorted half of a rotated array to achieve O(log N) search time.

1. Problem Statement

There is an integer array nums sorted in ascending order (with distinct values).

Prior to being passed to your function, nums is possibly rotated at an unknown pivot index k (e.g., [0,1,2,4,5,6,7] becomes [4,5,6,7,0,1,2]).

Given the array nums after the possible rotation and an integer target, return the index of target if it is in nums, or -1 if it is not in nums.

You must write an algorithm with $O(\log n)$ runtime complexity.

Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4

2. The Mental Model: The "Sorted Half" Strategy

Binary search depends on knowing which way to turn. In a normal sorted array, if target > mid, you go right. In a Rotated array, this is not always true.

However, a rotated sorted array has a special property: At least one half of the array (left or right of mid) is always perfectly sorted.

  1. Find the Sorted Half:
    • If nums[left] <= nums[mid], the Left side is sorted.
    • Else, the Right side is sorted.
  2. Check the Target:
    • If the target lies within the boundaries of the Sorted Half, search there.
    • Otherwise, the target must be in the Unsorted Half.

3. Visual Execution (The Pivot logic)

graph LR
    subgraph "Pivot at Index 3"
        A[4, 5, 6, 7] --- B[0, 1, 2]
    end
    
    Mid[Mid: 7] --> LeftSorted{Is Left Sorted?}
    LeftSorted -- Yes: 4 < 7 --> TargetCheck{Is target in 4..7?}
    TargetCheck -- No: 0 not in 4..7 --> SearchRight[Move to Unsorted Right]

4. Java Implementation (Optimal O(log N))

public int search(int[] nums, int target) {
    int left = 0;
    int right = nums.length - 1;

    while (left <= right) {
        int mid = left + (right - left) / 2;

        if (nums[mid] == target) return mid;

        // 1. Identify which half is sorted
        if (nums[left] <= nums[mid]) {
            // Left half is sorted
            if (target >= nums[left] && target < nums[mid]) {
                right = mid - 1; // Target is in the sorted left
            } else {
                left = mid + 1; // Target is in the unsorted right
            }
        } else {
            // Right half is sorted
            if (target > nums[mid] && target <= nums[right]) {
                left = mid + 1; // Target is in the sorted right
            } else {
                right = mid - 1; // Target is in the unsorted left
            }
        }
    }

    return -1;
}

5. Verbal Interview Script (Staff Tier)

Interviewer: "How do you apply binary search if the array isn't fully sorted?"

You: "The power of binary search isn't just about sorted data; it's about the ability to eliminate half the search space in every step. In a rotated sorted array, although the whole array is irregular, any pivot point will result in at least one half of the array being perfectly monotonic. By comparing the mid element with the left boundary, I can definitively identify which half is sorted. I then check if the target falls within that sorted range. If it does, I narrow my search to that half; if not, I'm guaranteed the target must be in the other (possibly rotated) half. This preserves the $O(\log N)$ time complexity while maintaining $O(1)$ space."

6. Staff-Level Follow-Ups

Follow-up 1: "What if there are duplicates in the array?"

  • The Answer: "Duplicates break the $O(\log N)$ guarantee. If nums[left] == nums[mid] == nums[right], we cannot tell which side is sorted. In that specific case, we have to decrement right and increment left to 'squeeze' the search space, potentially falling back to $O(N)$ in the worst case (e.g., an array of all 1s with a single 0)."

Follow-up 2: "Can you use this to find the minimum element?"

  • The Answer: "Yes, this is the 'Find Minimum in Rotated Sorted Array' problem. The logic is similar: we look for the point where the sorted property breaks (nums[i] > nums[i+1]). The element at i+1 is the minimum (the pivot)."

7. Performance Nuances (The Java Perspective)

  1. Branching vs Comparison: This code has more if-else branches than a standard binary search. While this adds a tiny amount of CPU cycle overhead for branch prediction, it is still orders of magnitude faster than a linear scan for large $N$.
  2. Integer Overflow: Using mid = left + (right - left) / 2 is mandatory here because rotated arrays are often used in systems dealing with massive circular buffers where indices can be very large.

6. Staff-Level Verbal Masterclass (Communication)

Interviewer: "How would you defend this specific implementation in a production review?"

You: "In a mission-critical environment, I prioritize the Big-O efficiency of the primary data path, but I also focus on the Predictability of the system. In this implementation, I chose a state-based dynamic programming approach. While a recursive solution is more readable, I would strictly monitor the stack depth. If this were to handle skewed inputs, I would immediately transition to an explicit stack on the heap to avoid a StackOverflowError. From a memory perspective, I leverage primitive arrays to ensure that we minimize the garbage collection pauses (Stop-the-world) that typically plague high-throughput Java applications."

7. Global Scale & Distributed Pivot

When a problem like this is moved from a single machine to a global distributed architecture, the constraints change fundamentally.

  1. Data Partitioning: We would shard the input space using Consistent Hashing. This ensures that even if our dataset grows to petabytes, any single query only hits a small subset of our cluster, maintaining logarithmic lookup times.
  2. State Consistency: For problems involving state updates (like DP or Caching), we would use a Distributed Consensus protocol like Raft or Paxos to ensure that all replicas agree on the final state, even in the event of a network partition (The P in CAP theorem).

8. Performance Nuances (The Staff Perspective)

  1. Cache Locality: Accessing a 2D matrix in row-major order (reading [i][j] then [i][j+1]) is significantly faster than column-major order in modern CPUs due to L1/L2 cache pre-fetching. I always structure my loops to align with how the memory is physically laid out.
  2. Autoboxing and Generics: In Java, using List<Integer> instead of int[] can be 3x slower due to the overhead of object headers and constant wrapping. For the most performance-sensitive sections of this algorithm, I advocate for primitive specialized structures.

Want to track your progress?

Sign in to save your progress, track completed lessons, and pick up where you left off.