Facebook Pixel

611. Valid Triangle Number

Problem Description

You are given an integer array nums. Your task is to find how many combinations of three numbers from this array can form a valid triangle.

For three sides to form a valid triangle, they must satisfy the triangle inequality theorem: the sum of any two sides must be greater than the third side. Specifically, if we have three sides a, b, and c, they can form a triangle if and only if:

  • a + b > c
  • a + c > b
  • b + c > a

You need to count all possible triplets (groups of three numbers) from the array that satisfy this condition. The same element at different positions can be part of different triplets, but you cannot use the same array element multiple times within a single triplet.

For example, if nums = [2, 2, 3, 4], the valid triplets that can form triangles are:

  • (2, 3, 4) at indices (0, 2, 3)
  • (2, 3, 4) at indices (1, 2, 3)
  • (2, 2, 3) at indices (0, 1, 2)

So the answer would be 3.

The solution sorts the array first, then for each pair of sides, uses binary search to find how many third sides can form a valid triangle with them. When the array is sorted and we fix two smaller sides nums[i] and nums[j] where i < j, we only need to check the condition nums[i] + nums[j] > nums[k] for the third side, as the other two conditions are automatically satisfied due to the sorted order.

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

Intuition

The key insight is that checking all three triangle inequality conditions can be simplified. When we sort the array, if we pick three sides where a ≤ b ≤ c, we only need to verify one condition: a + b > c. The other two conditions (a + c > b and b + c > a) are automatically satisfied because c is the largest side.

Think about it this way: if c is the largest side and a + b > c, then:

  • a + c must be greater than b (since c ≥ b)
  • b + c must be greater than a (since both b and c are at least as large as a)

This observation leads us to sort the array first. Once sorted, we can fix two sides and efficiently find all valid third sides.

For any pair of sides nums[i] and nums[j] where i < j, we need to find all positions k where k > j and nums[i] + nums[j] > nums[k]. Since the array is sorted, all valid k values form a continuous range. The largest valid k is the position just before nums[k] becomes greater than or equal to nums[i] + nums[j].

This is where binary search becomes useful. We can use bisect_left to find the first position where nums[k] >= nums[i] + nums[j]. The position just before this (minus 1) gives us the last valid index for the third side. The count of valid triangles with sides at positions i and j is then k - j, representing all positions from j + 1 to k.

By iterating through all possible pairs (i, j) and using binary search to count valid third sides, we efficiently count all triangles without checking every single triplet combination, reducing the time complexity from O(n³) to O(n² log n).

Learn more about Greedy, Two Pointers, Binary Search and Sorting patterns.

Solution Approach

The implementation follows a systematic approach using sorting and binary search:

  1. Sort the array: First, we sort nums in ascending order. This allows us to leverage the property that for sorted sides a ≤ b ≤ c, we only need to check if a + b > c.

  2. Fix two sides with nested loops: We use two nested loops to fix the first two sides:

    • The outer loop iterates i from 0 to n - 2 (where n is the array length)
    • The inner loop iterates j from i + 1 to n - 1
    • This ensures we pick two distinct elements where nums[i] ≤ nums[j]
  3. Find valid third sides using binary search: For each pair (nums[i], nums[j]), we need to find all valid third sides nums[k] where:

    • k > j (to avoid reusing elements)
    • nums[i] + nums[j] > nums[k] (triangle inequality)

    The code uses bisect_left(nums, nums[i] + nums[j], lo=j + 1) to find the first position where nums[k] >= nums[i] + nums[j]. The parameters are:

    • nums: the sorted array to search in
    • nums[i] + nums[j]: the target sum we're searching for
    • lo=j + 1: start searching from position j + 1 onwards
  4. Count valid triangles: The expression k = bisect_left(...) - 1 gives us the last valid index where nums[k] < nums[i] + nums[j]. The number of valid third sides is k - j, which represents all positions from j + 1 to k inclusive.

  5. Accumulate the result: We add k - j to our answer for each valid pair (i, j) and return the total count.

The algorithm efficiently counts all valid triangles with time complexity O(n² log n) - O(n²) for the two nested loops and O(log n) for each binary search operation.

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 the solution with nums = [4, 2, 3, 2].

Step 1: Sort the array After sorting: nums = [2, 2, 3, 4]

Step 2: Iterate through pairs of sides

Iteration 1: i=0, j=1 (sides: 2, 2)

  • We have nums[0] = 2 and nums[1] = 2
  • Sum = 2 + 2 = 4
  • Use binary search starting from position j+1=2 to find where value ≥ 4 would be inserted
  • bisect_left([2, 2, 3, 4], 4, lo=2) returns index 3
  • k = 3 - 1 = 2
  • Valid third sides are at positions from j+1=2 to k=2, which is just position 2 (value 3)
  • Count: k - j = 2 - 1 = 1
  • This forms triangle (2, 2, 3)

Iteration 2: i=0, j=2 (sides: 2, 3)

  • We have nums[0] = 2 and nums[2] = 3
  • Sum = 2 + 3 = 5
  • Use binary search starting from position j+1=3 to find where value ≥ 5 would be inserted
  • bisect_left([2, 2, 3, 4], 5, lo=3) returns index 4 (end of array)
  • k = 4 - 1 = 3
  • Valid third sides are at positions from j+1=3 to k=3, which is just position 3 (value 4)
  • Count: k - j = 3 - 2 = 1
  • This forms triangle (2, 3, 4)

Iteration 3: i=0, j=3 (sides: 2, 4)

  • We have nums[0] = 2 and nums[3] = 4
  • Sum = 2 + 4 = 6
  • No elements after position 3, so k - j = 0
  • Count: 0

Iteration 4: i=1, j=2 (sides: 2, 3)

  • We have nums[1] = 2 and nums[2] = 3
  • Sum = 2 + 3 = 5
  • bisect_left([2, 2, 3, 4], 5, lo=3) returns index 4
  • k = 4 - 1 = 3
  • Count: k - j = 3 - 2 = 1
  • This forms triangle (2, 3, 4) with different indices

Iteration 5: i=1, j=3 (sides: 2, 4)

  • Sum = 6, no elements after position 3
  • Count: 0

Iteration 6: i=2, j=3 (sides: 3, 4)

  • Sum = 7, no elements after position 3
  • Count: 0

Step 3: Return total count Total = 1 + 1 + 0 + 1 + 0 + 0 = 3

The three valid triangles are:

  1. (2, 2, 3) using indices (0, 1, 2)
  2. (2, 3, 4) using indices (0, 2, 3)
  3. (2, 3, 4) using indices (1, 2, 3)

Solution Implementation

1from typing import List
2from bisect import bisect_left
3
4
5class Solution:
6    def triangleNumber(self, nums: List[int]) -> int:
7        """
8        Count the number of valid triangles that can be formed from the array.
9        A valid triangle must satisfy: a + b > c for all three sides.
10      
11        Args:
12            nums: List of integers representing potential triangle sides
13          
14        Returns:
15            Number of valid triangles that can be formed
16        """
17        # Sort the array to enable binary search and ensure a <= b <= c
18        nums.sort()
19      
20        # Initialize counter and get array length
21        triangle_count = 0
22        array_length = len(nums)
23      
24        # Iterate through all possible first sides (smallest in the triangle)
25        for first_side_idx in range(array_length - 2):
26            # Iterate through all possible second sides (middle value)
27            for second_side_idx in range(first_side_idx + 1, array_length - 1):
28                # Find the largest valid third side using binary search
29                # We need: first_side + second_side > third_side
30                # So we search for the leftmost position where nums[k] >= first_side + second_side
31                sum_of_two_sides = nums[first_side_idx] + nums[second_side_idx]
32                insertion_point = bisect_left(nums, sum_of_two_sides, lo=second_side_idx + 1)
33              
34                # The largest valid index is insertion_point - 1
35                largest_valid_idx = insertion_point - 1
36              
37                # Count all valid triangles with current first and second sides
38                # All indices from second_side_idx + 1 to largest_valid_idx form valid triangles
39                triangle_count += largest_valid_idx - second_side_idx
40      
41        return triangle_count
42
1class Solution {
2    /**
3     * Counts the number of valid triangles that can be formed from the array.
4     * A valid triangle requires the sum of any two sides to be greater than the third side.
5     * 
6     * @param nums Array of side lengths
7     * @return Number of valid triangles
8     */
9    public int triangleNumber(int[] nums) {
10        // Sort the array to enable two-pointer technique
11        Arrays.sort(nums);
12      
13        int arrayLength = nums.length;
14        int triangleCount = 0;
15      
16        // Fix the largest side and find valid pairs for the other two sides
17        for (int largestIndex = arrayLength - 1; largestIndex >= 2; largestIndex--) {
18            int left = 0;
19            int right = largestIndex - 1;
20          
21            // Use two pointers to find all valid pairs
22            while (left < right) {
23                // Check if current pair forms a valid triangle with the largest side
24                if (nums[left] + nums[right] > nums[largestIndex]) {
25                    // All elements between left and right form valid triangles
26                    // because array is sorted and nums[left+1...right] are all greater than nums[left]
27                    triangleCount += right - left;
28                    right--;
29                } else {
30                    // Sum is too small, need a larger value
31                    left++;
32                }
33            }
34        }
35      
36        return triangleCount;
37    }
38}
39
1class Solution {
2public:
3    int triangleNumber(vector<int>& nums) {
4        // Sort the array to enable binary search and ensure a <= b <= c
5        sort(nums.begin(), nums.end());
6      
7        int count = 0;
8        int n = nums.size();
9      
10        // Iterate through all possible pairs of first two sides
11        for (int i = 0; i < n - 2; ++i) {
12            // Skip if current element is 0 (cannot form valid triangle with 0)
13            if (nums[i] == 0) {
14                continue;
15            }
16          
17            for (int j = i + 1; j < n - 1; ++j) {
18                // For sides a = nums[i] and b = nums[j], find the largest valid c
19                // Triangle inequality: a + b > c (other conditions automatically satisfied due to sorting)
20                // Find the rightmost position where nums[k] < nums[i] + nums[j]
21                int targetSum = nums[i] + nums[j];
22              
23                // Binary search for the first element >= targetSum
24                int k = lower_bound(nums.begin() + j + 1, nums.end(), targetSum) - nums.begin();
25              
26                // All elements from index (j+1) to (k-1) can be the third side
27                // k-1 is the last valid index, so count = (k-1) - j
28                count += (k - 1) - j;
29            }
30        }
31      
32        return count;
33    }
34};
35
1/**
2 * Counts the number of valid triangles that can be formed from the given array of numbers.
3 * A valid triangle must satisfy: sum of any two sides > third side
4 * 
5 * @param nums - Array of positive integers representing potential triangle sides
6 * @returns Number of valid triangles that can be formed
7 */
8function triangleNumber(nums: number[]): number {
9    // Sort the array in ascending order to enable two-pointer technique
10    nums.sort((a: number, b: number) => a - b);
11  
12    const arrayLength: number = nums.length;
13    let validTriangleCount: number = 0;
14  
15    // Iterate from the largest element as the longest side of potential triangles
16    for (let longestSideIndex: number = arrayLength - 1; longestSideIndex >= 2; longestSideIndex--) {
17        let leftPointer: number = 0;
18        let rightPointer: number = longestSideIndex - 1;
19      
20        // Use two pointers to find all valid pairs that can form a triangle with nums[longestSideIndex]
21        while (leftPointer < rightPointer) {
22            // Check if current pair forms a valid triangle with the longest side
23            if (nums[leftPointer] + nums[rightPointer] > nums[longestSideIndex]) {
24                // All pairs from (leftPointer, rightPointer) to (rightPointer-1, rightPointer) are valid
25                // because the array is sorted and nums[leftPointer+1...rightPointer-1] + nums[rightPointer] 
26                // will also be greater than nums[longestSideIndex]
27                validTriangleCount += rightPointer - leftPointer;
28                rightPointer--;
29            } else {
30                // Sum is too small, need a larger value from the left side
31                leftPointer++;
32            }
33        }
34    }
35  
36    return validTriangleCount;
37}
38

Time and Space Complexity

Time Complexity: O(n² log n)

The algorithm uses a nested loop structure with binary search:

  • The outer loop runs n - 2 times where n is the length of the array
  • The inner loop runs up to n - 1 - (i + 1) times for each iteration of the outer loop
  • For each pair (i, j), a binary search is performed using bisect_left, which takes O(log n) time
  • The initial sorting takes O(n log n) time

Overall: O(n log n) for sorting + O(n²) iterations × O(log n) for binary search = O(n² log n)

Space Complexity: O(1) or O(log n)

  • The algorithm sorts the input array in-place and uses only a constant amount of extra variables (ans, n, i, j, k)
  • If we consider the space used by the sorting algorithm (typically O(log n) for the recursion stack in algorithms like quicksort), then the space complexity is O(log n)
  • Otherwise, if we only count the auxiliary space explicitly used by our algorithm, it's O(1)

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

Common Pitfalls

1. Handling Zero or Negative Values Incorrectly

A critical pitfall is not considering that the input array might contain zeros or negative values. While negative side lengths don't make physical sense for triangles, the problem doesn't explicitly exclude them. The current solution might count invalid triangles when zeros are present.

Why it's a problem: When the array contains zeros, combinations like [0, 3, 4] would be counted as valid since 0 + 3 = 3 < 4 satisfies our check, but a triangle with a side of length 0 cannot exist.

Solution:

def triangleNumber(self, nums: List[int]) -> int:
    # Filter out non-positive values first
    nums = [x for x in nums if x > 0]
    nums.sort()
    # ... rest of the implementation remains the same

2. Off-by-One Error in Binary Search Result

A common mistake is misunderstanding what bisect_left returns and how to use it. Developers might forget to subtract 1 from the result or might use bisect_right instead, leading to incorrect counts.

Why it's a problem: bisect_left(nums, target, lo=j+1) returns the leftmost position where we could insert target. If we want the last valid index where nums[k] < target, we need to subtract 1. Using this value directly would include triangles where the sum equals the third side (invalid).

Solution:

# Correct approach - subtract 1 to get last valid index
insertion_point = bisect_left(nums, sum_of_two_sides, lo=second_side_idx + 1)
largest_valid_idx = insertion_point - 1

# Alternative using bisect_right (also correct)
largest_valid_idx = bisect_right(nums, sum_of_two_sides - 1, lo=second_side_idx + 1) - 1

3. Not Handling Edge Cases with Array Length

The algorithm assumes there are at least 3 elements in the array. If the array has fewer than 3 elements, the loops still execute but won't cause errors due to the range limits. However, it's cleaner to handle this explicitly.

Why it's a problem: While not causing runtime errors, it's inefficient to process arrays that cannot possibly form any triangles.

Solution:

def triangleNumber(self, nums: List[int]) -> int:
    if len(nums) < 3:
        return 0
  
    nums.sort()
    # ... rest of the implementation

4. Incorrect Count Calculation

A subtle mistake is calculating the count as largest_valid_idx - second_side_idx - 1 or largest_valid_idx - second_side_idx + 1 instead of the correct largest_valid_idx - second_side_idx.

Why it's a problem: The valid third sides are at indices [second_side_idx + 1, ..., largest_valid_idx]. The count is largest_valid_idx - second_side_idx, not off by one in either direction.

Solution:

# Correct: includes all indices from second_side_idx + 1 to largest_valid_idx
triangle_count += largest_valid_idx - second_side_idx

# Wrong: would miss one valid triangle
# triangle_count += largest_valid_idx - second_side_idx - 1

# Wrong: would count one extra invalid triangle  
# triangle_count += largest_valid_idx - second_side_idx + 1
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What's the relationship between a tree and a graph?


Recommended Readings

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

Load More