Facebook Pixel

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 threat
  • sev: The severity level of the threat
  • exp: 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:

  1. Sort threats in descending order by their score (highest score first)
  2. 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.

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

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:

  1. Primary criterion: Sort by score in descending order
  2. 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:

  1. 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.

  2. 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 formula 2 Γ— 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
  3. 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:

  1. For each threat in the array, calculate its sorting key as a tuple: (-(2 Γ— sev + exp), ID)
  2. Sort the entire array based on these keys
  3. 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 Evaluator

Example 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:

  1. (-11, 5) comes first (smallest negative score = highest actual score)
  2. (-10, 1) comes second
  3. (-9, 3) comes third (same negative score as next, but ID 3 < 10)
  4. (-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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Depth first search is equivalent to which of the tree traversal order?


Recommended Readings

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

Load More