Facebook Pixel

1150. Check If a Number Is Majority Element in a Sorted Array 🔒

Problem Description

You are given an integer array nums that is sorted in non-decreasing order (ascending order) and an integer target. Your task is to determine whether target is a majority element in the array.

A majority element is defined as an element that appears more than nums.length / 2 times in the array. In other words, the element must occur more than half the total number of elements in the array to be considered a majority element.

Return true if target is a majority element, otherwise return false.

Since the array is sorted, all occurrences of any element will be consecutive. The solution leverages binary search to efficiently find the leftmost and rightmost positions of target in the array. By using bisect_left to find the first occurrence and bisect_right to find the position just after the last occurrence, we can calculate the count of target as right - left. If this count is greater than len(nums) // 2, then target is a majority element.

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

Intuition

The key insight comes from recognizing that the array is sorted in non-decreasing order. In a sorted array, all occurrences of the same element must be grouped together consecutively. This means if target appears in the array, all its occurrences form a continuous segment.

To check if target is a majority element, we need to count how many times it appears. A naive approach would be to iterate through the entire array and count occurrences, which takes O(n) time. However, since the array is sorted, we can do better.

Instead of counting element by element, we can find the boundaries of where target appears in the array. If we know the leftmost position where target starts and the rightmost position where it ends, we can calculate the count directly as the difference between these positions.

Binary search is perfect for this because:

  1. We can use binary search to find the first occurrence of target (leftmost position)
  2. We can use binary search to find the first element greater than target (which gives us the position right after the last occurrence)
  3. The difference between these two positions gives us the exact count of target

This approach reduces our time complexity from O(n) to O(log n) for finding both boundaries. Once we have the count, we simply check if it's greater than len(nums) // 2 to determine if target is a majority element.

The elegance of this solution lies in transforming a counting problem into a boundary-finding problem, leveraging the sorted nature of the array to achieve logarithmic time complexity.

Learn more about Binary Search patterns.

Solution Approach

The solution leverages Python's bisect module which provides efficient binary search operations on sorted sequences. Here's how the implementation works:

Step 1: Find the leftmost position of target

left = bisect_left(nums, target)

bisect_left(nums, target) returns the leftmost position where target should be inserted to keep the array sorted. If target exists in the array, this gives us the index of its first occurrence. If target doesn't exist, it returns the position where it would be inserted.

Step 2: Find the position after the rightmost occurrence

right = bisect_right(nums, target)

bisect_right(nums, target) returns the rightmost position where target should be inserted to keep the array sorted. If target exists, this gives us the index immediately after its last occurrence. If target doesn't exist, it returns the same position as bisect_left.

Step 3: Calculate the count and check majority condition

return right - left > len(nums) // 2

The count of target in the array is right - left. We check if this count is greater than len(nums) // 2 (integer division for the floor value).

Example walkthrough:

  • For nums = [2, 4, 5, 5, 5, 5, 5, 6, 6] and target = 5:
    • bisect_left(nums, 5) returns 2 (first occurrence of 5)
    • bisect_right(nums, 5) returns 7 (position after last 5)
    • Count = 7 - 2 = 5
    • Array length = 9, so 9 // 2 = 4
    • Since 5 > 4, return true

Edge cases handled:

  • If target doesn't exist in the array, both left and right will be the same value, making right - left = 0, which correctly returns false
  • The solution works for arrays of any size, including single-element arrays

Time Complexity: O(log n) for two binary search operations Space Complexity: O(1) as we only use constant extra space

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Example: nums = [1, 2, 3, 3, 3, 3, 4], target = 3

Step 1: Find the leftmost position of target (3) Using binary search to find where 3 first appears:

  • Initial array: [1, 2, 3, 3, 3, 3, 4]
  • Indices: [0, 1, 2, 3, 4, 5, 6]
  • bisect_left(nums, 3) performs binary search:
    • Middle element at index 3 is 3, but we need the leftmost 3
    • Search continues in left half
    • Finds first 3 at index 2
  • Result: left = 2

Step 2: Find the position after the rightmost occurrence of target (3) Using binary search to find where we'd insert a value just greater than 3:

  • bisect_right(nums, 3) performs binary search:
    • Looks for the position after the last 3
    • Finds that index 6 (where 4 is) is the first position greater than 3
  • Result: right = 6

Step 3: Calculate count and check majority

  • Count of target: right - left = 6 - 2 = 4
  • Array length: 7
  • Majority threshold: 7 // 2 = 3
  • Is 4 > 3? Yes!
  • Return: true

The target 3 appears 4 times in an array of length 7, which is more than half the array size, making it a majority element.

Visual representation:

Array:  [1, 2, 3, 3, 3, 3, 4]
Index:   0  1  2  3  4  5  6
              ↑           ↑
            left        right
         (first 3)   (after last 3)
Count = 6 - 2 = 4 occurrences of 3

Solution Implementation

1from typing import List
2from bisect import bisect_left, bisect_right
3
4class Solution:
5    def isMajorityElement(self, nums: List[int], target: int) -> bool:
6        """
7        Check if target is a majority element in the sorted array.
8        A majority element appears more than n/2 times in the array.
9      
10        Args:
11            nums: A sorted list of integers
12            target: The integer to check if it's a majority element
13          
14        Returns:
15            True if target appears more than n/2 times, False otherwise
16        """
17        # Find the leftmost position where target could be inserted to maintain sorted order
18        # This gives us the index of the first occurrence of target (if it exists)
19        left_index = bisect_left(nums, target)
20      
21        # Find the rightmost position where target could be inserted to maintain sorted order
22        # This gives us the index after the last occurrence of target (if it exists)
23        right_index = bisect_right(nums, target)
24      
25        # Calculate the count of target elements by subtracting indices
26        target_count = right_index - left_index
27      
28        # Check if target appears more than half the length of the array
29        return target_count > len(nums) // 2
30
1class Solution {
2    /**
3     * Checks if the target value appears more than n/2 times in the sorted array.
4     * Uses binary search to find the range of target occurrences.
5     * 
6     * @param nums   sorted array of integers
7     * @param target the value to check for majority
8     * @return true if target appears more than n/2 times, false otherwise
9     */
10    public boolean isMajorityElement(int[] nums, int target) {
11        // Find the leftmost position of target
12        int leftBoundary = findFirstPosition(nums, target);
13      
14        // Find the leftmost position of (target + 1), which gives us the right boundary
15        int rightBoundary = findFirstPosition(nums, target + 1);
16      
17        // Check if the count of target elements is more than half the array length
18        return (rightBoundary - leftBoundary) > nums.length / 2;
19    }
20
21    /**
22     * Binary search to find the leftmost position where value >= x.
23     * This finds the first occurrence of x if it exists, or the position 
24     * where x would be inserted to maintain sorted order.
25     * 
26     * @param nums sorted array to search in
27     * @param x    target value to find
28     * @return index of the first position where nums[i] >= x
29     */
30    private int findFirstPosition(int[] nums, int x) {
31        int left = 0;
32        int right = nums.length;
33      
34        // Binary search for the leftmost position
35        while (left < right) {
36            // Calculate middle index using bit shift for efficiency
37            int mid = (left + right) >> 1;
38          
39            if (nums[mid] >= x) {
40                // If mid element is >= x, search in left half (including mid)
41                right = mid;
42            } else {
43                // If mid element is < x, search in right half (excluding mid)
44                left = mid + 1;
45            }
46        }
47      
48        return left;
49    }
50}
51
1class Solution {
2public:
3    bool isMajorityElement(vector<int>& nums, int target) {
4        // Find the first position where target appears in the sorted array
5        auto leftBound = lower_bound(nums.begin(), nums.end(), target);
6      
7        // Find the position just after the last occurrence of target
8        auto rightBound = upper_bound(nums.begin(), nums.end(), target);
9      
10        // Calculate the count of target elements by subtracting iterators
11        // Check if this count is greater than half of the array size
12        return (rightBound - leftBound) > (nums.size() / 2);
13    }
14};
15
1/**
2 * Checks if a target value appears more than n/2 times in a sorted array
3 * @param nums - A sorted array of numbers
4 * @param target - The target value to check for majority
5 * @returns true if target appears more than n/2 times, false otherwise
6 */
7function isMajorityElement(nums: number[], target: number): boolean {
8    /**
9     * Binary search to find the leftmost position where value >= x
10     * @param x - The value to search for
11     * @returns The index of the leftmost position where nums[i] >= x
12     */
13    const findLeftmostPosition = (x: number): number => {
14        let left: number = 0;
15        let right: number = nums.length;
16      
17        // Binary search for the leftmost position
18        while (left < right) {
19            // Calculate middle index using bit shift for efficiency
20            const mid: number = (left + right) >> 1;
21          
22            if (nums[mid] >= x) {
23                // If mid element is >= x, search in left half
24                right = mid;
25            } else {
26                // If mid element is < x, search in right half
27                left = mid + 1;
28            }
29        }
30      
31        return left;
32    };
33  
34    // Find the first occurrence of target
35    const firstOccurrence: number = findLeftmostPosition(target);
36  
37    // Find the first occurrence of the next value after target
38    // This gives us the position right after the last occurrence of target
39    const positionAfterLastOccurrence: number = findLeftmostPosition(target + 1);
40  
41    // Calculate the count of target elements
42    const targetCount: number = positionAfterLastOccurrence - firstOccurrence;
43  
44    // Check if target count is more than half of the array length
45    // Using bit shift (>> 1) to divide by 2 for efficiency
46    return targetCount > (nums.length >> 1);
47}
48

Time and Space Complexity

The time complexity of this solution is O(log n), where n is the length of the array nums. This is because the algorithm uses two binary search operations (bisect_left and bisect_right) to find the leftmost and rightmost positions of the target element in the sorted array. Each binary search operation takes O(log n) time, and since we perform two binary searches sequentially, the overall time complexity remains O(log n) (as O(log n) + O(log n) = O(log n)).

The space complexity is O(1) as the algorithm only uses a constant amount of extra space to store the variables left and right, regardless of the input size. The binary search operations are performed in-place without requiring additional space proportional to the input size.

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

Common Pitfalls

1. Misunderstanding the Majority Element Definition

A common mistake is checking if the count equals n/2 instead of checking if it's strictly greater than n/2. The majority element must appear more than half the times, not exactly half or at least half.

Incorrect Implementation:

# Wrong: checking >= instead of >
return right - left >= len(nums) // 2

Correct Implementation:

# Correct: strictly greater than n/2
return right - left > len(nums) // 2

2. Not Handling Empty Arrays

While the bisect functions handle empty arrays gracefully (both return 0), it's good practice to explicitly handle this edge case for clarity and to avoid unnecessary operations.

Enhanced Solution:

def isMajorityElement(self, nums: List[int], target: int) -> bool:
    if not nums:  # Handle empty array
        return False
  
    left = bisect_left(nums, target)
    right = bisect_right(nums, target)
    return right - left > len(nums) // 2

3. Confusion Between bisect_left and bisect_right

Developers sometimes confuse which function to use or attempt to use bisect_left for both boundaries, leading to off-by-one errors.

Incorrect Approach:

# Wrong: using bisect_left for both boundaries
left = bisect_left(nums, target)
right = bisect_left(nums, target + 1)  # This works but is less intuitive

Better Approach:

# Clear and intuitive
left = bisect_left(nums, target)   # First occurrence
right = bisect_right(nums, target)  # After last occurrence

4. Manual Binary Search Implementation Errors

Some developers try to implement binary search manually instead of using the bisect module, leading to boundary condition errors.

Error-Prone Manual Implementation:

def findLeft(nums, target):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = (left + right) // 2
        if nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1  # Easy to mess up boundaries
    return left

Recommended Solution: Use Python's built-in bisect module which is well-tested and optimized. If manual implementation is required, carefully test boundary conditions.

5. Integer Division vs Float Division

Using float division (/) instead of integer division (//) can cause issues when comparing with the count.

Incorrect:

# Wrong: float division might cause comparison issues
return right - left > len(nums) / 2

Correct:

# Correct: integer division for proper comparison
return right - left > len(nums) // 2
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How would you design a stack which has a function min that returns the minimum element in the stack, in addition to push and pop? All push, pop, min should have running time O(1).


Recommended Readings

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

Load More