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.
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 thanb
(sincec ≥ b
)b + c
must be greater thana
(since bothb
andc
are at least as large asa
)
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:
-
Sort the array: First, we sort
nums
in ascending order. This allows us to leverage the property that for sorted sidesa ≤ b ≤ c
, we only need to check ifa + b > c
. -
Fix two sides with nested loops: We use two nested loops to fix the first two sides:
- The outer loop iterates
i
from0
ton - 2
(wheren
is the array length) - The inner loop iterates
j
fromi + 1
ton - 1
- This ensures we pick two distinct elements where
nums[i] ≤ nums[j]
- The outer loop iterates
-
Find valid third sides using binary search: For each pair
(nums[i], nums[j])
, we need to find all valid third sidesnums[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 wherenums[k] >= nums[i] + nums[j]
. The parameters are:nums
: the sorted array to search innums[i] + nums[j]
: the target sum we're searching forlo=j + 1
: start searching from positionj + 1
onwards
-
Count valid triangles: The expression
k = bisect_left(...) - 1
gives us the last valid index wherenums[k] < nums[i] + nums[j]
. The number of valid third sides isk - j
, which represents all positions fromj + 1
tok
inclusive. -
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 EvaluatorExample 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
andnums[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
andnums[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
andnums[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
andnums[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:
- (2, 2, 3) using indices (0, 1, 2)
- (2, 3, 4) using indices (0, 2, 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 wheren
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 usingbisect_left
, which takesO(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 isO(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
What's the relationship between a tree and a graph?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Want a Structured Path to Master System Design Too? Don’t Miss This!