You are playing a game that contains multiple characters, and each of the characters has two main properties: attack and defense. You are given a 2D integer array properties
where properties[i] = [attacki, defensei]
represents the properties of the ith
character in the game.
A character is said to be weak if any other character has both attack and defense levels strictly greater than this character's attack and defense levels. More formally, a character i
is said to be weak if there exists another character j
where attackj > attacki
and defensej > defensei
.
Return the number of weak characters.
Example 1:
Input: properties = [[5,5],[6,3],[3,6]] Output: 0 Explanation: No character has strictly greater attack and defense than the other.
Example 2:
Input: properties = [[2,2],[3,3]] Output: 1 Explanation: The first character is weak because the second character has a strictly greater attack and defense.
Example 3:
Input: properties = [[1,5],[10,4],[4,3]] Output: 1 Explanation: The third character is weak because the second character has a strictly greater attack and defense.
Constraints:
2 <= properties.length <= 105
properties[i].length == 2
1 <= attacki, defensei <= 105
This problem asks us to find the number of "weak" characters in a list of characters, where each character has an attack and defense value. A character is considered weak if another character exists with strictly greater attack and defense values.
The most efficient approach uses sorting and a single traversal. The core idea is to cleverly order the characters to make the weakness check straightforward.
1. Sorting:
We sort the properties
array using a custom comparator. The sorting criteria is as follows:
-x[0]
in Python, b[0] - a[0]
in Java/C++/etc.). This means characters with higher attack values appear earlier in the sorted array.x[1]
in Python, a[1] - b[1]
in Java/C++/etc.).2. Traversal and Weakness Check:
After sorting, we iterate through the sorted array. We maintain a variable mx
(maximum defense encountered so far). For each character:
x[1]
) is less than mx
, it means a character with both higher attack (because of the sorting) and higher defense has already been encountered. Therefore, the current character is weak, and we increment the ans
(answer) counter.mx
: Regardless of whether the character is weak or not, we update mx
to be the maximum of its current value and the current character's defense value.Time Complexity: The dominant operation is the sorting, which takes O(n log n) time, where n is the number of characters. The traversal takes O(n) time. Therefore, the overall time complexity is O(n log n).
Space Complexity: The space complexity is determined by the sorting algorithm. Most efficient sorting algorithms (like merge sort or quicksort) use O(log n) space in the average case due to the recursive calls. In some implementations, in-place sorting might be used, leading to O(1) space complexity.
Code Examples (Multiple Languages):
The code examples below demonstrate the solution in several programming languages. They all follow the same core logic: sorting and traversal.
class Solution:
def numberOfWeakCharacters(self, properties: List[List[int]]) -> int:
properties.sort(key=lambda x: (-x[0], x[1])) # Sort by attack (descending), then defense (ascending)
ans = mx = 0
for _, x in properties: # Iterate through defense values
if x < mx:
ans += 1
else:
mx = x
return ans
class Solution {
public int numberOfWeakCharacters(int[][] properties) {
Arrays.sort(properties, (a, b) -> b[0] - a[0] == 0 ? a[1] - b[1] : b[0] - a[0]); //Custom comparator for sorting
int ans = 0, mx = 0;
for (int[] p : properties) {
if (p[1] < mx) ans++;
else mx = p[1];
}
return ans;
}
}
class Solution {
public:
int numberOfWeakCharacters(vector<vector<int>>& properties) {
sort(properties.begin(), properties.end(), [](const auto& a, const auto& b) {
return a[0] == b[0] ? a[1] < b[1] : a[0] > b[0]; //Custom lambda for sorting
});
int ans = 0, mx = 0;
for (const auto& p : properties) {
if (p[1] < mx) ans++;
else mx = p[1];
}
return ans;
}
};
var numberOfWeakCharacters = function(properties) {
properties.sort((a, b) => (b[0] - a[0]) || (a[1] - b[1])); // Sort using a comparator
let ans = 0;
let mx = 0;
for (let i = 0; i < properties.length; i++) {
if (properties[i][1] < mx) ans++;
else mx = properties[i][1];
}
return ans;
};
The other languages (Go, TypeScript) would have similar structures, prioritizing efficient sorting and then a linear scan to count weak characters. The key is the careful sorting to make the weakness check simple and efficient during the traversal.