题目:计算右侧小于当前元素的个数
给你一个整数数组 nums
,按要求返回一个新数组 counts
。数组 counts
有该性质: counts[i]
的值是 nums[i]
右侧小于 nums[i]
的元素的数量。
示例 1:
输入:nums = [5,2,6,1]
输出:[2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素
示例 2:
输入:nums = [-1]
输出:[0]
示例 3:
输入:nums = [-1,-1]
输出:[0,0]
提示:
1 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
下面是正确的官方给出的解题思路:
解法1: 归并排序+索引数组
- 主要思路是借鉴 剑指 Offer 51. 数组中的逆序对 中归并排序的解法, 只是本题需要分别计算每个元素的逆序对
- 当发生归并, 前后序数组的指针分别为 i,j , 若 i指向的元素较小, 则后序数组中, mid之后 j之前的元素均构成逆序对, 个数为 j-mid-1, 理解可见下配图
注意必须是前序数组 i 归并时统计, 这时可以知道相应结果数组的对应索引, 而若统计 j 归并时, 无法获取结果索引, 因为其索引不同 - 由于每次进行归并操作时, 元素的下标位置都会发生变化, 这样无法对元素进行定位, 所以考虑使用 索引数组来处理
- 索引数组记录每个元素的下标, 在归并排序时, 只调整索引数组的元素, 相当于有一个映射
class Solution {
int[] index;
int[] temp;
int[] ans;
public List<Integer> countSmaller(int[] nums) {
// 索引数组, 辅助数组, 结果数组
index = new int[nums.length];
temp = new int[nums.length];
ans = new int[nums.length];
// 初始化index数组, 开始时索引即为 i
for (int i = 0; i < index.length; i++) {
index[i] = i;
}
mergeSort(nums, 0, nums.length - 1);
List<Integer> ansList = new ArrayList<>();
for (int i = 0; i < ans.length; i++) {
ansList.add(ans[i]);
}
return ansList;
}
public void mergeSort(int[] nums, int left, int right) {
if (left >= right) {
return;
}
int mid = left + (right - left) / 2;
mergeSort(nums, left, mid);
mergeSort(nums, mid + 1, right);
// 若前后序数组已经有序, 则说明已经排序好了, 直接退出
if (nums[index[mid]] <= nums[index[mid + 1]]) {
return;
}
// 开始归并过程
int i = left, j = mid + 1;
int k = left;
while (i <= mid && j <= right) {
// 注意这里是小于等于, 原因可以思考
if (nums[index[i]] <= nums[index[j]]) {
ans[index[i]] += j - mid - 1;
temp[k++] = index[i++];
} else {
temp[k++] = index[j++];
}
}
// 此时后序数组全部都小于 i 之后的元素, 所以后面每个 i 要加上后序数组的所有元素
while (i <= mid) {
ans[index[i]] += right - mid;
temp[k++] = index[i++];
}
while (j <= right) {
temp[k++] = index[j++];
}
for (k = left; k <= right; k++) {
index[k] = temp[k];
}
}
}
但是这个题在每日一练中是选择题,这么长的代码我真的看不下去呜呜呜*__*|||
所以索性自己解,还请大佬指导:
思路是:用count计数比列表中第一个数大的数字的数量之和,加入到新的列表中,然后从与原列表删除第一个数字,就可以实现遍历(哈哈哈哈),append()函数是给列表加数据,返回结果。
class Solution:
def countSmaller(self, nums: List[int]) -> List[int]:
m = len(nums)
result = []
while nums:
count = 0
for i in nums:
if nums[0] > i:
count += 1
result.append(count)
nums.pop(0)
return result
但是有个很致命的问题,那就是他的时间复杂度是O(2^n),直接哭了出来,懂了,这就去学。
根据题解,真的学到了!!
解法如下,python的nice之处就是可以调用很多实用的库!
from sortedcontainers import SortedList
class Solution:
def countSmaller(self, nums: List[int]) -> List[int]:
n = len(nums)
res = [0] * n
sl = SortedList()
for i in range(n-1, -1, -1): # 反向遍历
cnt = sl.bisect_left(nums[i]) # 找到右边比当前值小的元素个数
res[i] = cnt # 记入答案
sl.add(nums[i]) # 将当前值加入有序数组中
return res
但是自己的思路,跪着也样让它AC,通过不懈努力,终于!!!
class Solution:
def countSmaller(self, nums: List[int]) -> List[int]:
a=sorted(nums)
res=[]
for i in nums:
tmp=a.index(i)
res.append(tmp)
a.pop(tmp)
return res
首先通过将nums列表进行排序,得到一个新的列表a,其中元素按照升序排列。然后,使用一个空列表res来存储计算结果。接下来,遍历nums列表中的每个元素i,并使用列表a的index()方法找到元素i在列表a中的索引值tmp。然后,将tmp添加到res列表中,并使用a.pop(tmp)方法将列表a中的元素i移除,以确保之后的查找不会重复计算。最后,返回结果列表res。
这段代码的时间复杂度为O(n^2),其中n为nums列表的长度。因为在每次循环中,使用了列表的index()方法来查找元素的索引,并且使用了pop()方法来删除元素,这两个操作都需要线性时间复杂度。
仅供学习参考记录,官方解法转自官方思路+解法