每日一练(内心OS:看选项不如我自己解)

文章介绍了如何计算数组中每个元素右侧有多少个更小的元素,提供了两种解法:一种基于归并排序和索引数组,另一种利用Python的SortedContainers库。第一种方法具有较高的时间复杂度,第二种方法优化了时间效率。同时,文中也提到了一个时间复杂度为O(n^2)的解决方案,但效率较低。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:计算右侧小于当前元素的个数

给你一个整数数组 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: 归并排序+索引数组

  1. 主要思路是借鉴 剑指 Offer 51. 数组中的逆序对 中归并排序的解法, 只是本题需要分别计算每个元素的逆序对
  2. 当发生归并, 前后序数组的指针分别为 i,j , 若 i指向的元素较小, 则后序数组中, mid之后 j之前的元素均构成逆序对, 个数为 j-mid-1, 理解可见下配图
    注意必须是前序数组 i 归并时统计, 这时可以知道相应结果数组的对应索引, 而若统计 j 归并时, 无法获取结果索引, 因为其索引不同
  3. 由于每次进行归并操作时, 元素的下标位置都会发生变化, 这样无法对元素进行定位, 所以考虑使用 索引数组来处理
  4. 索引数组记录每个元素的下标, 在归并排序时, 只调整索引数组的元素, 相当于有一个映射

在这里插入图片描述

 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()方法来删除元素,这两个操作都需要线性时间复杂度。

仅供学习参考记录,官方解法转自官方思路+解法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值