3631. Sort Threats by Severity and Exploitability π
Problem Description
You are given a 2D integer array called threats
where each element represents a security threat with three properties:
ID
: A unique identifier for the threatsev
: The severity level of the threatexp
: The exploitability level of the threat
Each threat has a calculated score based on the formula: score = 2 Γ sev + exp
Your task is to sort and return the threats
array based on these rules:
- Sort threats in descending order by their score (highest score first)
- If two or more threats have the same score, sort them by their ID in ascending order (lowest ID first)
For example, if you have threats with:
- Threat A:
[ID=5, sev=3, exp=2]
β score =2Γ3 + 2 = 8
- Threat B:
[ID=2, sev=2, exp=4]
β score =2Γ2 + 4 = 8
- Threat C:
[ID=1, sev=4, exp=1]
β score =2Γ4 + 1 = 9
The sorted output would be: Threat C first (highest score of 9), then Threat B (ID=2), then Threat A (ID=5) since B and A have the same score but B has a lower ID.
The solution uses a custom sorting key that first sorts by negative score (to achieve descending order) and then by ID (ascending) as a tiebreaker. The expression -(x[1] * 2 + x[2])
calculates the negative score for each threat to enable descending sort.
Intuition
When we need to sort elements based on multiple criteria with different ordering directions, we can leverage Python's sorting capabilities with a custom key function.
The key insight is recognizing that this is a multi-level sorting problem:
- Primary criterion: Sort by score in descending order
- Secondary criterion: Sort by ID in ascending order (when scores are equal)
Python's sort()
function naturally sorts in ascending order. To achieve descending order for the score, we have two options:
- Use
reverse=True
parameter (but this would reverse both criteria) - Negate the score value to flip the sorting direction
Since we want different directions for our two criteria (descending for score, ascending for ID), negating the score is the elegant solution. By using -score
, larger positive scores become smaller negative values, naturally placing them first when sorted in ascending order.
The formula score = 2 Γ sev + exp
is straightforward to calculate for each threat. We access sev
as x[1]
and exp
as x[2]
from each threat array.
The lambda function lambda x: (-(x[1] * 2 + x[2]), x[0])
returns a tuple for each threat. Python sorts tuples lexicographically - it first compares the first elements, and only if they're equal does it compare the second elements. This perfectly matches our requirement: sort by score first, then by ID as a tiebreaker.
This approach modifies the original array in-place and returns it, making it both memory-efficient and straightforward to implement in a single line of code.
Learn more about Sorting patterns.
Solution Approach
The solution uses Python's built-in sorting with a custom key function to achieve the required ordering.
Implementation Details:
-
In-place Sorting: We use
threats.sort()
which modifies the original list directly rather than creating a new sorted list. This is memory-efficient for large datasets. -
Custom Key Function: The lambda function
lambda x: (-(x[1] * 2 + x[2]), x[0])
serves as the sorting key:x
represents each threat array[ID, sev, exp]
x[1] * 2 + x[2]
calculates the score using the formula2 Γ sev + exp
- The negative sign
-
before the score calculation reverses the sorting order for scores - The tuple
(negative_score, ID)
ensures proper multi-level sorting
-
Tuple Comparison: Python compares tuples element by element from left to right:
- First, it compares the negative scores
- If two threats have the same score (and thus the same negative score), it compares their IDs
- Since IDs are not negated, they sort in ascending order naturally
Algorithm Steps:
- For each threat in the array, calculate its sorting key as a tuple:
(-(2 Γ sev + exp), ID)
- Sort the entire array based on these keys
- Return the sorted array
Time Complexity: O(n log n)
where n is the number of threats, due to the sorting operation.
Space Complexity: O(1)
for the in-place sort (excluding the space used by Python's sorting algorithm internally, which is O(n)
in the worst case).
The note in the reference about using long integers is important for languages with fixed integer sizes to prevent overflow when calculating 2 Γ sev + exp
, though Python handles arbitrary precision integers automatically.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with 4 threats to see how the sorting works:
Input: threats = [[3, 2, 5], [1, 3, 4], [10, 2, 5], [5, 5, 1]]
Step 1: Calculate scores for each threat
- Threat
[3, 2, 5]
: score = 2 Γ 2 + 5 = 9 - Threat
[1, 3, 4]
: score = 2 Γ 3 + 4 = 10 - Threat
[10, 2, 5]
: score = 2 Γ 2 + 5 = 9 - Threat
[5, 5, 1]
: score = 2 Γ 5 + 1 = 11
Step 2: Create sorting keys (negative score, ID)
- Threat
[3, 2, 5]
: key = (-9, 3) - Threat
[1, 3, 4]
: key = (-10, 1) - Threat
[10, 2, 5]
: key = (-9, 10) - Threat
[5, 5, 1]
: key = (-11, 5)
Step 3: Sort by these keys When Python sorts these tuples in ascending order:
(-11, 5)
comes first (smallest negative score = highest actual score)(-10, 1)
comes second(-9, 3)
comes third (same negative score as next, but ID 3 < 10)(-9, 10)
comes last
Step 4: Map back to original threats
- First:
[5, 5, 1]
(score = 11) - Second:
[1, 3, 4]
(score = 10) - Third:
[3, 2, 5]
(score = 9, ID = 3) - Fourth:
[10, 2, 5]
(score = 9, ID = 10)
Output: [[5, 5, 1], [1, 3, 4], [3, 2, 5], [10, 2, 5]]
Notice how threats with ID 3 and ID 10 both have score 9, but ID 3 appears before ID 10 in the final result, demonstrating the tiebreaker rule working correctly.
Solution Implementation
1class Solution:
2 def sortThreats(self, threats: List[List[int]]) -> List[List[int]]:
3 """
4 Sort threats based on custom criteria.
5
6 Args:
7 threats: A list of threat entries, where each entry is [id, priority1, priority2]
8
9 Returns:
10 The sorted list of threats
11 """
12 # Sort threats using a custom key function
13 # Primary sort: By negative sum of (priority1 * 2 + priority2) for descending order
14 # Secondary sort: By id (ascending) when the primary values are equal
15 threats.sort(key=lambda threat: (-(threat[1] * 2 + threat[2]), threat[0]))
16
17 return threats
18
1class Solution {
2 /**
3 * Sorts a 2D array of threats based on a custom scoring system.
4 * Each threat has three attributes: [id, attribute1, attribute2]
5 *
6 * @param threats 2D array where each row represents a threat with 3 integer values
7 * @return the sorted 2D array of threats
8 */
9 public int[][] sortThreats(int[][] threats) {
10 // Sort the threats array using a custom comparator
11 Arrays.sort(threats, (threat1, threat2) -> {
12 // Calculate threat score: 2 * second_attribute + third_attribute
13 // Using long to prevent integer overflow
14 long threatScore1 = 2L * threat1[1] + threat1[2];
15 long threatScore2 = 2L * threat2[1] + threat2[2];
16
17 // If both threats have the same score
18 if (threatScore1 == threatScore2) {
19 // Sort by the first attribute (likely an ID) in ascending order
20 return Integer.compare(threat1[0], threat2[0]);
21 }
22
23 // Sort by threat score in descending order (higher scores first)
24 return Long.compare(threatScore2, threatScore1);
25 });
26
27 return threats;
28 }
29}
30
1class Solution {
2public:
3 vector<vector<int>> sortThreats(vector<vector<int>>& threats) {
4 // Sort threats based on custom scoring logic
5 sort(threats.begin(), threats.end(), [](const vector<int>& a, const vector<int>& b) {
6 // Calculate threat scores using formula: 2 * second_element + third_element
7 // Using long long to prevent integer overflow
8 long long scoreA = 2LL * a[1] + a[2];
9 long long scoreB = 2LL * b[1] + b[2];
10
11 // If scores are equal, sort by first element in ascending order
12 if (scoreA == scoreB) {
13 return a[0] < b[0];
14 }
15
16 // Sort by score in descending order (higher score comes first)
17 return scoreA > scoreB;
18 });
19
20 return threats;
21 }
22};
23
1/**
2 * Sorts an array of threats based on a calculated threat score
3 * @param threats - 2D array where each threat is represented as [id, factor1, factor2]
4 * @returns The sorted array of threats in descending order by score, with ties broken by id
5 */
6function sortThreats(threats: number[][]): number[][] {
7 // Sort threats array in-place
8 threats.sort((threatA: number[], threatB: number[]): number => {
9 // Calculate threat score using formula: 2 * factor1 + factor2
10 const threatScoreA: number = 2 * threatA[1] + threatA[2];
11 const threatScoreB: number = 2 * threatB[1] + threatB[2];
12
13 // If scores are equal, sort by id (ascending)
14 if (threatScoreA === threatScoreB) {
15 return threatA[0] - threatB[0];
16 }
17
18 // Otherwise, sort by score (descending - higher scores first)
19 return threatScoreB - threatScoreA;
20 });
21
22 return threats;
23}
24
Time and Space Complexity
The time complexity of this code is O(n Γ log n)
, where n
is the length of the threats
array. This is because the dominant operation is the sort()
method, which uses Timsort algorithm in Python with a time complexity of O(n Γ log n)
in the average and worst cases. The lambda function used as the sorting key performs constant-time operations (multiplication, addition, and negation) for each element, which doesn't affect the overall time complexity.
The space complexity is O(log n)
, where n
is the length of the threats
array. Python's Timsort algorithm uses a hybrid sorting approach that requires O(log n)
space for the recursion stack during the merge sort phase. The lambda function itself doesn't create any additional data structures that scale with input size, only performing arithmetic operations on existing values.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow in Other Languages
While Python handles arbitrary precision integers automatically, implementing this solution in languages like Java, C++, or JavaScript could cause integer overflow when calculating 2 Γ sev + exp
for large values.
Solution: Use appropriate data types (e.g., long
in Java/C++, BigInt
in JavaScript) or ensure your inputs are within safe bounds.
2. Incorrect Tuple Ordering for Tie-Breaking
A common mistake is reversing both elements in the sorting key:
# WRONG: This would sort IDs in descending order for ties threats.sort(key=lambda x: (-(x[1] * 2 + x[2]), -x[0]))
Solution: Only negate the score, not the ID:
# CORRECT: Score descending, ID ascending threats.sort(key=lambda x: (-(x[1] * 2 + x[2]), x[0]))
3. Using sorted()
Instead of sort()
Unnecessarily
Using sorted(threats, ...)
creates a new list, which uses extra memory and might not update the original reference if that's expected.
Solution: Use threats.sort()
for in-place sorting when you want to modify the original list, or explicitly assign the result if using sorted()
:
# If you need a new list
sorted_threats = sorted(threats, key=lambda x: (-(x[1] * 2 + x[2]), x[0]))
# Or modify in-place
threats.sort(key=lambda x: (-(x[1] * 2 + x[2]), x[0]))
4. Forgetting Parentheses in Score Calculation
Writing lambda x: (-x[1] * 2 + x[2], x[0])
would only negate x[1]
, not the entire score.
Solution: Use proper parentheses:
# CORRECT: Negates the entire score lambda x: (-(x[1] * 2 + x[2]), x[0])
5. Misunderstanding Stable Sort Behavior
Relying on Python's stable sort to handle ties without explicitly including ID in the key could lead to incorrect results if the input order doesn't match the expected ID ordering.
Solution: Always explicitly include all sorting criteria in the key function rather than relying on initial ordering.
Depth first search is equivalent to which of the tree traversal order?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!