目录
1.双指针
524.通过删除字母匹配到字典里最长单词
2.排序
215.数组中的第K个最大元素
347.前k个高频元素
451.根据字符出现频率排序
74.颜色分类
3.贪心思想
435.无重叠区间
452.用最少数量的箭引爆气球
406.根据身高重建队列
4.数组
566.重塑矩阵
难度:中等 考察点:双指针
给你一个字符串 s 和一个字符串数组 dictionary ,找出并返回 dictionary 中最长的字符串,该字符串可以通过删除 s 中的某些字符得到。
如果答案不止一个,返回长度最长且字母序最小的字符串。如果答案不存在,则返回空字符串。
示例 1:
输入:s = "abpcplea", dictionary = ["ale","apple","monkey","plea"]
输出:"apple"
示例 2:
输入:s = "abpcplea", dictionary = ["a","b","c"]
输出:"a"
提示:
1 <= s.length <= 1000
1 <= dictionary.length <= 1000
1 <= dictionary[i].length <= 1000
s 和 dictionary[i] 仅由小写英文字母组成
/**
* @param {string} s
* @param {string[]} dictionary
* @return {string}
*/
/*判断dictionary中的每个元素是否是s的字串,若是,则记录最长且字典序最小字串*/
var findLongestWord = function(s, dictionary) {
const isSubSequencial = (s, target) => {
let si = 0, st = 0;
while(si < s.length && st < target.length){
if(s[si] == target[st])
st++;
si++;
}
return st == target.length;
}
let longestWord = '';
for(let target of dictionary){
//注意:返回字典序最小的子串
if(target.length < longestWord.length || target.length==longestWord.length&&longestWord.localeCompare(target) < 0)
continue;
if(isSubSequencial(s,target))
longestWord = target;
}
return longestWord;
};
难度:中等 考察点:排序(快排、堆排)
给定整数数组 nums
和整数 k
,请返回数组中第 k
个最大的元素。
请注意,你需要找的是数组排序后的第 k
个最大的元素,而不是第 k
个不同的元素。
你必须设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 1:
输入: [3,2,1,5,6,4],k = 2 输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6],k = 4 输出: 4
提示:
1 <= k <= nums.length <= 105
-104 <= nums[i] <= 10
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
//解法一:利用快速排序的partition(),找到第len-k个位置的元素即为第k大元素
//时间复杂度O(N),空间复杂度O(1)
var findKthLargest = function(nums, k) {
const partition = (nums, left, right) => {
let privot = nums[left]
while(left < right){
while(left < right && privot <= nums[right]){
right --;
}
if(left < right){
nums[left ++] = nums[right];
}
while(left < right && nums[left] <= privot){
left ++;
}
if(left < right){
nums[right --] = nums[left];
}
}
nums[left] = privot;
return left;
}
const targetIndex = nums.length - k;
let left = 0, right = nums.length - 1;
while(left <= right){
let cur = partition(nums, left, right);
if(cur == targetIndex)
return nums[cur];
else if(cur < targetIndex)
left = cur + 1;
else
right = cur - 1;
}
};
/**解法二:
* 利用大顶堆,首先将nums构建成一个大顶堆,
* 然后依次删去堆顶元素,重新调整使其继续符合大顶堆,
* 重复删除k-1次,得到的堆顶元素即为第k大元素
* 时间复杂度O(NlogN),空间复杂度O(N)
**/
var findKthLargest = function(nums, k) {
const swap = (nums,i,j)=>{
let tmp=nums[i];
nums[i]=nums[j];
nums[j] = tmp;
}
const maxHeapify = (nums,i,heapsize)=>{
let left = i*2 + 1;
let right = i*2 + 2;
let large = i;
if(left nums[large])
large = left;
if(right nums[large])
large = right;
if(large != i){
swap(nums,i,large);
maxHeapify(nums,large,heapsize);
}
}
const buildHeap = (nums,heapsize) => {
for(let i = Math.floor(heapsize/2)-1;i>=0;i--){
maxHeapify(nums,i,heapsize);
}
}
let heapsize = nums.length;
buildHeap(nums,heapsize);
for(let i = heapsize-1;i >= nums.length - k + 1;i--){
swap(nums,0,i);
heapsize--;
maxHeapify(nums,0,heapsize)
}
return nums[0]
};
难度:中等 考察点:排序(桶排序)
给你一个整数数组 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 是数组大小。
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
//桶排序,先获取的映射结果,
//再调转利用map自动排序key的性质根据frequent升序排列
var topKFrequent = function(nums, k) {
//字面量创建一个map变量arr,结果会根据key自动排序
const bucketSort = (freqMap, k)=>{
let arr = [], res = [];
freqMap.forEach((value,key)=>{
if(!arr[value])
arr[value] = [key];
else
arr[value] = [...arr[value],key]
})
for(let i = arr.length - 1;i >= 0 && res.length < k;i--)
if(arr[i])
res = [...res,...arr[i]]
return res;
}
const freqMap = new Map();
nums.forEach(num => {
if(freqMap.has(num))
freqMap.set(num, freqMap.get(num)+1);
else
freqMap.set(num,1);
})
if(freqMap.length <= k)
return [...freqMap.keys()]
return bucketSort(freqMap,k);
};
难度:中等 考察点:排序(桶排序)
给定一个字符串 s ,根据字符出现的 频率 对其进行 降序排序 。一个字符出现的 频率 是它出现在字符串中的次数。
返回 已排序的字符串 。如果有多个答案,返回其中任何一个。
示例 1:
输入: s = "tree"
输出: "eert"
解释: 'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。
示例 2:
输入: s = "cccaaa"
输出: "cccaaa"
解释: 'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。
示例 3:
输入: s = "Aabb"
输出: "bbAa"
解释: 此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。
提示:
1 <= s.length <= 5 * 105
s 由大小写英文字母和数字组成
/**
* @param {string} s
* @return {string}
*/
//桶排序,先获取的映射结果,
//再调转利用map自动排序key的性质根据frequent升序排列
var frequencySort = function(s) {
const bucketSort = (freqMap) => {
let arr = [], res = "";
freqMap.forEach((value,key)=>{
if(!arr[value])
arr[value] = [key]
else
arr[value].push(key)
})
//value不是连续值,因此需要判断arr[i]是否存在值
for(let i = arr.length - 1;i >= 0; i--){
if(arr[i]){
arr[i].forEach(c=> res += c.repeat(i))
}
}
return res;
}
const freqMap = new Map();
for(const c of s){
if(freqMap.has(c))
freqMap.set(c,freqMap.get(c) + 1);
else
freqMap.set(c,1);
}
return bucketSort(freqMap);
};
难度:中等 考察点:三色排序
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库的sort函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]
提示:
n == nums.length
1 <= n <= 300
nums[i] 为 0、1 或 2
进阶:
你可以不使用代码库中的排序函数来解决这道题吗?
你能想出一个仅使用常数空间的一趟扫描算法吗?
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var sortColors = function(nums) {
let zero = -1, one = 0, two = nums.length;
const swap = (nums,i,j)=>{
let tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
while(one < two){
if(nums[one] == 0){
swap(nums, ++zero,one++)
}
else if(nums[one] == 2){
swap(nums,--two,one)
}
else{
one++;
}
}
return;
};
难度:中等 考察点:贪心思想
给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
示例 1:
输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
示例 2:
输入: intervals = [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
示例 3:
输入: intervals = [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
提示:
1 <= intervals.length <= 105
intervals[i].length == 2
-5 * 104 <= starti < endi <= 5 * 104
/**
* @param {number[][]} intervals
* @return {number}
*/
var eraseOverlapIntervals = function(intervals) {
if(intervals.length < 2)
return 0;
intervals.sort((a,b)=>{ //按照区间右边界递增排序
return b[1]
难度:中等 考察点:贪心思想
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。
示例 1:
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
-在x = 6处射出箭,击破气球[2,8]和[1,6]。
-在x = 11处发射箭,击破气球[10,16]和[7,12]。
示例 2:
输入:points = [[1,2],[3,4],[5,6],[7,8]]
输出:4
解释:每个气球需要射出一支箭,总共需要4支箭。
示例 3:
输入:points = [[1,2],[2,3],[3,4],[4,5]]
输出:2
解释:气球可以用2支箭来爆破:
- 在x = 2处发射箭,击破气球[1,2]和[2,3]。
- 在x = 4处射出箭,击破气球[3,4]和[4,5]。
提示:
1 <= points.length <= 105
points[i].length == 2
-231 <= xstart < xend <= 231 - 1
/**
* @param {number[][]} points
* @return {number}
*/
//与上题思路一致,注意,本题中[1,2][2,3]算作重叠区间
var findMinArrowShots = function(points) {
if(points.length < 2)
return points.length;
points.sort((a,b)=>{
return b[1]
难度:中等 考察点:贪心思想
假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
示例 1:
输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
示例 2:
输入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
输出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]
提示:
1 <= people.length <= 2000
0 <= hi <= 106
0 <= ki < people.length
题目数据确保队列可以被重建
/**
* @param {number[][]} people
* @return {number[][]}
*/
//为了确保一次就能将元素放在正确位置,因此先确定高个的位置
//将数组按照h降序,k升序排列
//依次取一个元素插入新数组,从左至右遍历新数组,
//当遍历至某个位置,新数组中大于元素的h的个数等于元素的k时,将元素插入至当前位置
var reconstructQueue = function(people) {
people.sort((a,b)=>{return a[0] == b[0]?a[1]-b[1]:b[0]-a[0]}) //排序
const arr = [];
for(const pi of people){
let [hi, ki] = pi; //从排序数组中依次取元素
let i = 0;
for(;i < arr.length && ki > 0;i++){ //遍历新数组,找插入位置
if(arr[i][0] >= hi){
ki --;
}
}
arr.splice(i,0,pi) //将pi插入至位置i,并且删除0个元素,在arr数组上操作
}
return arr;
};
难度:简单
在 MATLAB 中,有一个非常有用的函数 reshape
,它可以将一个 m x n
矩阵重塑为另一个大小不同(r x c
)的新矩阵,但保留其原始数据。
给你一个由二维数组 mat
表示的 m x n
矩阵,以及两个正整数 r
和 c
,分别表示想要的重构的矩阵的行数和列数。
重构后的矩阵需要将原始矩阵的所有元素以相同的 行遍历顺序 填充。
如果具有给定参数的 reshape
操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。
示例 1:
输入:mat = [[1,2],[3,4]], r = 1, c = 4 输出:[[1,2,3,4]]
示例 2:
输入:mat = [[1,2],[3,4]], r = 2, c = 4 输出:[[1,2],[3,4]]
提示:
m == mat.length
n == mat[i].length
1 <= m, n <= 100
-1000 <= mat[i][j] <= 1000
1 <= r, c <= 300
/**
* @param {number[][]} mat
* @param {number} r
* @param {number} c
* @return {number[][]}
*/
/*解法1:先将原数组转成一维数组,再将一维数组转成目标数组*/
var matrixReshape = function(mat, r, c) {
const arr = mat.flat(); /*flat()默认深度为1,将矩阵拉平*/
if(arr.length != r*c)
return mat;
const newMat = [];
for(let row = 0;row < arr.length;row += c) /*函数slice(begin,end)对数组进行浅拷贝*/
newMat.push(arr.slice(row,row + c));
return newMat;
};
/*解法2:计算元素在新数组中的下标,直接将老数组中元素赋值过去*/
var matrixReshape = function(mat, r, c) {
const m = mat.length;
const n = mat[0].length;
if(m*n != r*c)
return mat;
const newMat = new Array(r).fill(0).map(()=>new Array(c).fill(0)); /*第二个new 语句不能嵌套花括号,具体为啥还不知道*/
for(let i = 0;i < m;i ++){
for(let j = 0;j < n;j ++){
let kr = Math.floor((i*n+j)/c); /*注意下取整,js不会自动取整*/
let kc = (i*n+j)%c;
newMat[kr][kc] = mat[i][j];
}
}
return newMat;
};