• JS leetcode 有多少小于当前数字的数字 解题分析,你应该了解的桶排序


    壹 ❀ 引

    刷题其实一直没断,只是这两天懒得整理题目...那么今天来记录一道前天做的题,题目本身不难,不过拓展起来还是有些东西可以讲,题目来自leetcode有多少小于当前数字的数字,题目描述如下:

    给你一个数组 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 <= nums.length <= 500
    0 <= nums[i] <= 100
    

    要求很简单,对于数组中每个元素,统计比它要小的元素的个数,我们来尝试解答它。

    贰 ❀ 解题思路

    那么我首先想到的比较暴力的做法自然是使用双循环嵌套了,设置一个用于统计个数的变量,外层循环每次取一个元素出来,依次跟内层循环的每个元素对比,只要满足条件,让计数变量自增。

    说干就干,实现是这样:

    贰 ❀ 壹 使用双循环

    /**
     * @param {number[]} nums
     * @return {number[]}
     */
    var smallerNumbersThanCurrent = function (nums) {
        let ans = [],
            len = nums.length;
        // 每次取一位出来作为参照
        for (let i = 0; i < len; i++) {
            // 计数变量
            let sum = 0;
            for (let j = 0; j < len; j++) {
                if (nums[i] > nums[j]) {
                    ++sum;
                };
            };
            ans.push(sum);
        };
        return ans;
    };
    

    贰 ❀ 贰 排序加indexOf

    在实现后,我脑中有了一个想法,能不能把数组排个序,比如[8,1,2,2,3]排序后是[1,2,2,3,8],这样我能知道每个元素前有几个元素,不就是我们想要的答案了。

    当然这只是一个模糊的想法,有两个问题需要解决,一是我怎么知道有几个元素比当前元素小,特别是当元素相同的时候,比如[7,7,7,8]

    比第一个7要小的数为0个,第二个7也是0个,第三个7还是0个,而第四个8是三个,0,0,0,3。这不是就在用indexOf获取索引吗,管你元素相不相同,相同获取第一个数字的索引就行了。

    第二个问题是,一旦数组被我们排序,虽然能得到对应的个数,但这个个数的顺序也被打乱了,怎么还原呢?别忘了我们还可以拷贝一份数组。那么到这里我们来看看第二种实现:

    /**
     * @param {number[]} nums
     * @return {number[]}
     */
    var smallerNumbersThanCurrent = function (nums) {
        let ans = [],
            // 拷贝一份数组,用于维持ans查询结果顺序
            nums_ = JSON.parse(JSON.stringify(nums));
        //将nums排序好
        nums = nums.sort((a, b) => a - b);
        nums_.forEach(item => {
            ans.push(nums.indexOf(item));
        });
        return ans;
    };
    

    在评论区,我看到有用户在拷贝数组时使用的是[].concat(nums),比如:

    let arr = [1,2,3];
    let arr_ = [].concat(arr);
    arr_;//[1,2,3]
    

    那么有个问题,concat是浅拷贝还是深拷贝?如果我们修改上面例子中的arr,你会发现互不影响:

    arr.push(4);
    arr_;//[1,2,3]
    

    这能说明concat是深拷贝吗?其实在MDN中关于concat描述很清楚,此方法会依次拷贝目标数组中每个元素,而对于元素类型不同做不同决策。如果元素是简单类型,则直接复制元素,但如果元素是对象类型,而是拷贝对象的引用,所以我们可以来看一个对象数组的类型,比如:

    let arr = [1,[2,3],4];
    let arr_ = [].concat(arr);
    arr[1].push(1);
    arr_;//[1,[2,3,1],4]
    

    那么关于此方案说到这了。当然除了以上两种方案外,还有一种是官方提到的计数排序+求前缀和的做法。当时相比官方所说的计数排序,我觉得更像桶排序,由于这两种排序十分类似,我暂时未能分清他们,所以这里暂且叫桶排序吧。

    我知道第一次听说桶排序的同学一定会很陌生,没事,我们先来简单介绍它,以[3,2,2,1,4]为例。

    由于数组中最大元素是4,所以我们建立五个用于装元素的桶,桶默认值都是0,那为啥是五个?因为数组默认索引是从0开始的,0-1-2-3-4,这不是5个桶吗?

    现在我们开始遍历[3,2,2,1,4],将数字丢到对应索引的桶中,每丢一个进去让桶的初始值自增1,像这样:

    现在我们得到了一个装满数字的桶,因为他也是一个数组,遍历桶,比如桶1的数量为1,输出1个1,桶2数量是2,所以输出2个2,以此类推:

    你看,我们不就得到了一个已经排序好的数组了,让我们尝试实现它(注意,这并非官方的桶排序,我只是按照这个思路去实现它)。

    // 非正式桶排序
    function bucketSort(nums) {
        // 找出最大元素
        let max = Math.max(...nums),
            // 创建桶
            bucket = new Array(max + 1).fill(0),
            ans = [];
        // 统计对应桶位元素数量
        nums.forEach(ele => {
            bucket[ele]++;
        });
        for (var i = 0; i < bucket.length; i++) {
            while (bucket[i] > 0) {
                ans.push(i);
                bucket[i]--;
            };
        };
        return ans;
    };
    bucketSort([3, 2, 2, 1]); //[1,2,2,3]
    

    OK,知道了这个怎么能用于解题呢?仔细想想,当我们桶排序后,对应每个桶的数量,不就是我们希望要的数字吗,比如上方图解中:

    比桶1小的元素位0个,也就是bucket[1-1]个。

    比桶2小的元素有1个,也就是bucket[1-1]+bucket[2-1]个。

    比桶3小的元素有3个,也就是bucket[3-1]+bucket[3-2]+bucket[3-3]个。

    比桶4小的元素有4个,也就是bucket[4-1]+bucket[4-2]+bucket[4-3]+bucket[4-4]个。

    当然有个特例,因为桶0是一个,不会有比它小的元素了,所以比它小的一定是0个。

    这也是我们在前面说官方用计数排序加求前缀和做法,为何要求前缀和的原因。我知道这有点绕,静下心仔细想想,这只是用了桶排序统计了元素的个数,元素成了桶数组的下标,顺带求了数量的和。

    现在让我们实现它,像这样:

    /**
     * @param {number[]} nums
     * @return {number[]}
     */
    var smallerNumbersThanCurrent = function(nums) {
        let ans = [],
            max = Math.max(...nums),
            len = nums.length;
        // 由于i范围是0 <= nums[i] <= 100,因此创建101个桶
        let bucket = new Array(max + 1).fill(0);
        // 将元素装到对应的桶
        for (let i = 0; i < len; i++) {
            // 数字每出现一次,桶的统计数量就加1
            bucket[nums[i]]++;
        };
        // 求n项出现的次数和,考虑越界情况,此处i从1开始
        for (let i = 1; i <= 100; i++) {
            bucket[i] += bucket[i - 1]
        };
        // nums中元素对应了桶的索引,索引非0取前一位,索引为0说明没有比它小的,直接赋予0
        for (let i = 0; i < len; i++) {
            nums[i] ? ans[i] = bucket[nums[i] - 1] : ans[i] = 0
        };
        return ans;
    };
    

    那么关于本题就分析到这里了。

  • 相关阅读:
    shell基础--变量的数值计算
    shell基础--字符串和变量的操作
    shell基础--shell特殊变量
    Asp.net MVC 控制器扩展方法实现jsonp
    ASP.NET web api 跨域请求
    asp.net Web API简单的特性路由配置
    ASP.Net Web API 输出缓存 转载 -- Output caching in ASP.NET Web API
    基础拾遗 C# Json 与对象相互转换
    内存溢出和内存泄漏
    软件测试面试题(一)
  • 原文地址:https://www.cnblogs.com/echolun/p/13169166.html
Copyright © 2020-2023  润新知