leetcode:前 K 个高频元素、删除字符串中的所有相邻重复项、有多少小于当前数字的数字、有效的山脉数组、独一无二的出现次数(JavaScript)

347. 前 K 个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:

输入: nums = [1], k = 1
输出: [1]

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

进阶: 你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

思路

先用map记录每个元素出现的频率,再根据频率从小到大排序,最后取出后k个数。

var topKFrequent = function(nums, k) {
    let map = new Map();
    // 记录频率
    for (let c of nums) {
        map.set(c, (map.get(c)|| 0 ) + 1);
    }
    // 根据频率排序
    let r = Array.from(map).sort((a, b) => a[1] - b[1]);
    let res = [];
    // 取后k个元素
    for (let i = r.length - 1; i >= r.length - k; i--) {
        res.push(r[i][0]);
    }
    return res;
};

1047. 删除字符串中的所有相邻重复项

给出由小写字母组成的字符串 S重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

示例:

输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。

提示:

  1. 1 <= S.length <= 20000
  2. S 仅由小写英文字母组成。

思路

利用栈先进后出的特点,每次判断栈顶元素是否和当前元素一样,一样就将栈顶元素出栈,否则就入栈,这样能保证每次栈顶元素都是和当前元素是相邻的(哪怕在删除相邻重复项后),这里可以仔细想一下。

var removeDuplicates = function(s) {
    let stack = [];
    for (let c of s) {
        if (stack.length === 0) {
            stack.push(c);
        } else {
            // 出栈
            if (stack[stack.length - 1] === c) {
                stack.pop();
            } else {
            // 入栈
                stack.push(c);
            }
        }
    }
    return stack.join("");
};

1365. 有多少小于当前数字的数字

给你一个数组 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]

思路

这一题的难点在于如何统计小于 nums[i] 的数,最简单的是双重 for 循环,一层遍历 nums 元素,一层找小于该元素的元素个数,暴力破解,但是这样效率太低,时间复杂度太高。

优化

先给 nums 元素从小到大排序,那么若元素不重复,则元素所在位置下标就是小于它的元素的个数,那就又带来一个新问题,如果有重复的元素怎么办呢?其实可以加个判断若元素相同,则小于它们的元素个数都为该元素第一次出现的下标位置,而不是等于他们各自的下标值了。

但是这样得来的值,它的值得顺序是排序后的元素出现的顺序,和原数组的顺序并不一致。

例:[8,1,2,2,3]
排序后:[1,2,2,3,8]
得到的结果:[0,1,1,3,4]
期望的结果:[4,0,1,1,3]

所以我们可以遍历原数组,按原数组元素出现位置,并找到小于它的元素个数值赋值给当前位置结果数组元素就好了。

var smallerNumbersThanCurrent = function(nums) {
    // 深拷贝,保存元素的原始位置
    const nums1 = [...nums];
    const res = Array(nums.length);
    // 排序
    nums.sort((a,b) => a - b);
    const hash = Array(nums.length);
    // 初始化
    hash[0] = 0;
    // 相同的数res值相同
    for (let i = 1; i < nums.length; i++) {
        if (nums[i] === nums[i - 1]) {
            hash[i] = hash[i - 1];
        } else {
            hash[i] = i;
        }
    }
    // 以原数组的元素顺序改变得到结果数组元素
    for (let i = 0; i < nums.length; i++) {
        let index = nums.indexOf(nums1[i]);
        // 得到最终的结果数组
        res[i] = hash[index];
    }
    return res;
};

继续优化

看题目数值范围很小,那么我们可以直接借助hash的思想,在统计小于该元素的元素个数时,将值存在hash数组相应位置即可,在处理相同元素时只需要从后往前遍历排序后的数组,那么相同的元素就会不断将下标覆盖该相同位置,最后会将相同元素第一次出现位置下标赋值给 hash 数组对应位置。

例:[8,1,2,2,3]
排序后:[1,2,2,3,8]
第一次遍历到2时,hash[2] = 2;
接着向前遍历一位,hash[1] = 1;

最后遍历原始数组是,两个 2 取得都是 hash[2],所以相同的元素的结果值都是第一次出现位置下标。

nums1.forEach(item => {
    res[i] = hash[item];
})   

完整代码

 var smallerNumbersThanCurrent = function(nums) {
    // 深拷贝,保存元素的原始位置
    const nums1 = [...nums];
    nums.sort((a, b) => a - b);
    const res = Array(nums.length);
    // 初始化
    const hash = Array(101).fill(0);
    // 从后往前遍历不断覆盖得到相同元素第一次出现的位置
    for (let i = nums.length - 1; i >= 0; i--) {
        hash[nums[i]] = i;
    }
    // 遍历原始数组的元素顺序取得相应的结果
    nums1.forEach(item => {
        res[i] = hash[item];
    })
    return res;
};

941. 有效的山脉数组

给定一个整数数组 arr,如果它是有效的山脉数组就返回 true,否则返回 false

让我们回顾一下,如果 arr 满足下述条件,那么它是一个山脉数组:

  • arr.length >= 3

  • 0 < i < arr.length - 1
    

    条件下,存在

    i
    

    使得:

    • arr[0] < arr[1] < ... arr[i-1] < arr[i]
    • arr[i] > arr[i+1] > ... > arr[arr.length - 1]

img

示例 1:

输入:arr = [2,1]
输出:false

示例 2:

输入:arr = [3,5,5]
输出:false

示例 3:

输入:arr = [0,3,2,1]
输出:true

思路

仔细读题可以得出结论:此山脉数组必须是一个先单调递增,再单调递减的数组,这就是关键。

此时arr[i]与arr[i-1]三种情况:

  1. arr[i]>arr[i-1]:此时需要将num++,统计递增元素个数,如果num === num === arr.length - 1说明数组一直单调递增,不是山脉数组,返回false,如果flag===false说明已经发生转折,此时又出现本情况,那么也要返回false。
  2. arr[i]===arr[i-1]:这种情况想都不用想肯定返回 false 。
  3. arr[i]<arr[i-1]:此时需要将转折标记置为 false,表示转折已发生,若此时num === 0说明数组没有经历递增就开始递减了,不是山脉数组,返回 false。
var validMountainArray = function(arr) {
    if (arr.length < 3) return false;
    // 表示是否转折,num记录递增的元素个数
    let flag = true, num = 0;
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] > arr[i - 1]) {
            // 记录递增元素
            num++;
            // 数组一直递增或再次发生转折
            if (!flag || num === arr.length - 1) {
                return false;
            } 
        } else if (arr[i] === arr[i - 1]) {
            return false;
        } else if (arr[i] < arr[i - 1]){
            // 未递增就递减
            if (num === 0) {
                return false;
            }
            // 标志转折已发生
            flag = false;
        }
    }
    return true;
};

1207. 独一无二的出现次数

给你一个整数数组 arr,请你帮忙统计数组中每个数的出现次数。

如果每个数的出现次数都是独一无二的,就返回 true;否则返回 false

示例 1:

输入:arr = [1,2,2,1,1,3]
输出:true
解释:在该数组中,1 出现了 3 次,2 出现了 2 次,3 只出现了 1 次。没有两个数的出现次数相同。

示例 2:

输入:arr = [1,2]
输出:false

示例 3:

输入:arr = [-3,0,1,-3,1,1,1,-3,10,0]
输出:true

思路

解本题需要两步:

  1. 使用 map 统计每个元素的出现次数。
  2. 遍历 map 判断元素的出现次数是否出现过,那么在遍历的过程中就需要一个数组保存前面的出现次数,并用数组的includes方法判断当前出现次数是否在该数组中出现过。
var uniqueOccurrences = function(arr) {
    const map = new Map();
    const res = [];
    // 统计每个数字出现的次数
    for (let i = 0; i < arr.length; i++) {
        if (map.has(arr[i])) {
            map.set(arr[i], map.get(arr[i]) + 1);
        } else {
            map.set(arr[i], 1);
        }
    }
    // 判断每个数字的出现次数是否出现过
    for (let v of map.values()) {
        if (!res.includes(v)) {
            res.push(v);
        } else {
            return false;
        }
    }
    return true;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值