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;
};

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

猜你喜欢

转载自www.cnblogs.com/echolun/p/13169166.html
今日推荐