【问题标题】:LeetCode 1365 - looking for a better solution than O(n^2)LeetCode 1365 - 寻找比 O(n^2) 更好的解决方案
【发布时间】:2020-05-15 21:37:13
【问题描述】:

我正在处理 leetcode 问题编号 1365。以下是斜体字符:

给定数组 nums,对于每个 nums[i] 找出数组中有多少个数小于它。也就是说,对于每个 nums[i],您必须计算有效 j 的数量,使得 j != i 并且 nums[j]

https://leetcode.com/problems/how-many-numbers-are-smaller-than-the-current-number/

我能够使用蛮力完成任务,这给了 O(n^2) 时间。有没有更快的方法来解决这个问题?

   public static void main(String[] args) {
        int[] nums = new int[] {8,1,2,2,3};
        System.out.println(Arrays.toString(smallerNumbersThanCurrent(nums)));
    }

    public static int[] smallerNumbersThanCurrent(int[] nums) {
        int[] result = new int[nums.length];    

        for (int x = 0; x < nums.length; x++) {
            int ctr = 0;
            for (int y = 0; y < nums.length; y++) {
                if (nums[y] < nums[x]) {
                    ctr++;
                }
                result[x] = ctr;
            }
        }       
        return result;
    }

【问题讨论】:

    标签: java algorithm


    【解决方案1】:

    带有O(n) 空格的简单O(nlgn) 解决方案是:

    1. 将数组复制到临时数组中,O(n)
    2. 排序新数组O(ngln)
    3. 遍历原始数组
      • 对于每个元素,对排序后的数组进行二分搜索并获取元素的第一个索引。
      • 索引将是您所追求的计数。

    【讨论】:

    • 在使用二分搜索时如何处理数组中的重复项?
    • @Raushan 你需要调整二分搜索获取第一个索引的位:)
    • 如果你在排序之前存储数组元素的原始索引,你可以避免二分搜索,而只是对排序后的数组进行线性扫描。
    【解决方案2】:

    有一个稍微好一点的O(n^2) 方法,您只需比较每对索引一次并相应地更新计数:

    public static int[] smallerNumbersThanCurrent(int[] nums)
    {
        int[] result = new int[nums.length];
    
        for (int x = 0; x < nums.length; x++)
        {
            for (int y = x + 1; y < nums.length; y++)
            {
                if (nums[y] < nums[x])
                    result[x]++;
                else if (nums[y] > nums[x])
                    result[y]++;
            }
        }
        return result;
    }
    

    但是,我们可以在O(ngln) 中通过对原始数组的索引进行排序,然后遍历这些排序的索引,相应地更新计数,以增加一个数组为代价。唯一的复杂之处在于处理重复的数字,例如您的示例中的 2s。

    public static int[] smallerNumbersThanCurrent(int[] nums)
    {
        Integer[] idx = new Integer[nums.length];
        for(int i=0; i<idx.length; i++) idx[i] = i;
        Arrays.sort(idx, (a, b) -> (nums[a]-nums[b]));
    
        int[] res = new int[nums.length];
    
        for(int i=1; i<idx.length; i++) 
        {
            if(nums[idx[i]] == nums[idx[i-1]])
                res[idx[i]] = res[idx[i-1]];
            else
                res[idx[i]] = i;
        }
    
        return res;
    }
    

    测试:

    int[] nums = new int[] {8,1,2,2,3};
    System.out.println(Arrays.toString(smallerNumbersThanCurrent(nums)));
    

    输出:

    [4, 0, 1, 1, 3]
    

    【讨论】:

    • 当@SirRaffleBuffle!你擅长编码算法。你的第二个解决方案比我的快两倍多,但是我的头很复杂。
    • @nikotromus 测试没有重复数字的输入并打印已排序的 idx 数组的值以了解该步骤。希望随后您能够了解如何通过此步骤计算出有多少数字位于nums 中的相应数字之前。
    • @SurRaffkeBuffle - 你能帮我一个大忙并解释一下这一行:Arrays.sort(idx, (a, b) -> (nums[a]-nums[b])); “a”和“b”是从哪里来的?
    • ab 将成为数组 nums 的索引。如果nums[a] 小于nums[b],那么a 应该在idx 中的b 之前。这是在保持数组不变的情况下获取数组排序顺序的技巧。如果您直接对nums 进行排序,您将丢失原始值所在的位置,并且无法回答问题。
    • 非常感谢。我明白了。我会不断地重新审视这个问题,直到我可以在没有笔记的情况下做到这一点。我认为这种创建新数组并根据目标数组中的值对索引进行建模的技术可能会在其他情况下派上用场。
    猜你喜欢
    • 1970-01-01
    • 2021-05-23
    • 2014-07-20
    • 2013-01-09
    • 2022-08-19
    • 1970-01-01
    • 2019-09-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多