❓1365. 有多少小于当前数字的数字
难度:简单
给你一个数组 nums
,对于其中每个元素 nums[i]
,请你统计数组中比它小的所有数字的数目。
换而言之,对于每个 nums[i]
你必须计算出有效的 j
的数量,其中 j
满足 j != i
且 nums[j] < nums[i]
。
以数组形式返回答案。
示例 1:
输入:nums = [8,1,2,2,3]
输出:[4,0,1,1,3]
解释:
对于 nums[0]=8 存在四个比它小的数字:(1,2,2 和 3)。
对于 nums[1]=1 不存在比它小的数字。
对于 nums[2]=2 存在一个比它小的数字:(1)。
对于 nums[3]=2 存在一个比它小的数字:(1)。
对于 nums[4]=3 存在三个比它小的数字:(1,2 和 2)。
示例 2:
输入:nums = [6,5,4,8]
输出:[2,1,0,3]
示例 3:
输入:nums = [7,7,7,7]
输出:[0,0,0,0]
提示:
- 2 < = n u m s . l e n g t h < = 500 2 <= nums.length <= 500 2<=nums.length<=500
- 0 < = n u m s [ i ] < = 100 0 <= nums[i] <= 100 0<=nums[i]<=100
💡思路:
两层
for
循环暴力查找,时间复杂度明显为 O ( n 2 ) O(n^2) O(n2)。这里不做介绍,我们来看一下如何优化。
法一:排序
首先要找小于当前数字的数字,那么从小到大排序之后,该数字之前的数字就都是比它小的了。
- 所以可以定义一个新数组
temp
,将数组排个序,排序之后,其实每一个数值的下标就代表这前面有几个比它小的了; - 然后在遍历原数组
nums
是,对每一个num[i]
在temp
使用二分查找,查到每个元素第一个位置的下标,即为比它小的所有数字的数目
。
法二: 计数排序
注意到数组元素的值域为 [0,100]
,所以可以考虑建立一个频次数组 cnt
,cnt[i]
表示数字 i
出现的次数。
那么对于数字 i
而言,小于它的数目就为 cnt[0...i−1]
的总和。
🍁代码:(Java、C++)
法一:排序
Java
class Solution {
public int[] smallerNumbersThanCurrent(int[] nums) {
int n = nums.length;
int[] temp = Arrays.copyOf(nums, nums.length);
Arrays.sort(temp);
int[] ans = new int[nums.length];
for(int i = 0; i < n; i++){
int left = 0, right = n - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(temp[mid] >= nums[i]) right--;
else left++;
}
ans[i] = right + 1;
}
return ans;
}
}
C++
class Solution {
public:
vector<int> smallerNumbersThanCurrent(vector<int>& nums) {
int n = nums.size();
vector<int> temp(nums), ans(n);
sort(temp.begin(), temp.end());
for(int i = 0; i < n; i++){
int left = 0, right = n - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(temp[mid] >= nums[i]) right--;
else left++;
}
ans[i] = right + 1;
}
return ans;
}
};
法二: 计数排序
Java
class Solution {
public int[] smallerNumbersThanCurrent(int[] nums) {
int n = nums.length;
int[] cnt = new int[101];
int[] ans = new int[n];
for(int num : nums){
cnt[num]++;
}
int sum = 0;
for(int i = 0; i <= 100; i++){
if(cnt[i] != 0){
sum += cnt[i];
cnt[i] = sum - cnt[i];
}
}
for(int i = 0; i < n; i++){
ans[i] = cnt[nums[i]];
}
return ans;
}
}
C++
class Solution {
public:
vector<int> smallerNumbersThanCurrent(vector<int>& nums) {
int n = nums.size();
vector<int> cnt(101, 0), ans(n);
for(int num : nums){
cnt[num]++;
}
int sum = 0;
for(int i = 0; i <= 100; i++){
if(cnt[i] != 0){
sum += cnt[i];
cnt[i] = sum - cnt[i];
}
}
for(int i = 0; i < n; i++){
ans[i] = cnt[nums[i]];
}
return ans;
}
};
🚀 运行结果:
🕔 复杂度分析:
法一:排序
- 时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),其中
n
为数组的长度,排序需要 O ( n l o g n ) O(nlogn) O(nlogn) 的时间,随后需要 O ( n ) O(n) O(n) 时间来遍历 乘上 二分查找的时间 O ( l o g n ) O(logn) O(logn) ,所以总时间为 O ( n l o g n ) O(nlogn) O(nlogn)。 - 空间复杂度: O ( n ) O(n) O(n),因为要额外开辟一个数组。
法二: 计数排序
- 时间复杂度:
O
(
n
+
k
)
O(n + k)
O(n+k),其中
k
为值域大小。需要遍历两次原数组,同时遍历一次频次数组cnt
找出前缀和。。 - 空间复杂度: O ( k ) O(k) O(k),因为要额外开辟一个值域大小的数组。
题目来源:力扣。
放弃一件事很容易,每天能坚持一件事一定很酷,一起每日一题吧!
关注我LeetCode主页 / CSDN—力扣专栏,每日更新!