文章目录
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 <= S.length <= 20000
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]
示例 1:
输入:arr = [2,1]
输出:false
示例 2:
输入:arr = [3,5,5]
输出:false
示例 3:
输入:arr = [0,3,2,1]
输出:true
思路
仔细读题可以得出结论:此山脉数组必须是一个先单调递增,再单调递减的数组,这就是关键。
此时arr[i]与arr[i-1]
三种情况:
arr[i]>arr[i-1]
:此时需要将num++,统计递增元素个数,如果num === num === arr.length - 1
说明数组一直单调递增,不是山脉数组,返回false,如果flag===false
说明已经发生转折,此时又出现本情况,那么也要返回false。arr[i]===arr[i-1]
:这种情况想都不用想肯定返回 false 。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
思路
解本题需要两步:
- 使用 map 统计每个元素的出现次数。
- 遍历 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;
};