这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战 | 创作学习持续成长,夺宝闯关赢大奖 - 掘金 (juejin.cn)
题目链接
- 最近的请求次数 leetcode-cn.com/problems/nu…
- 第k个数 leetcode-cn.com/problems/ge…
- 亲密字符串 leetcode-cn.com/problems/bu…
- 柠檬水找零 leetcode-cn.com/problems/le…
- 煎饼排序 leetcode-cn.com/problems/pa…
题解及分析
最近的请求次数
写一个 RecentCounter 类来计算特定时间范围内最近的请求。
请你实现 RecentCounter 类:
RecentCounter()初始化计数器,请求数为0。
int ping(int t)在时间t添加一个新请求,其中t表示以毫秒为单位的某个时间,并返回过去3000毫秒内发生的所有请求数(包括新请求)。确切地说,返回在[t-3000, t]内发生的请求数。
保证每次对ping的调用都使用比之前更大的t值。
这道题我是没看懂的...leetcode的题解如下:
我们只会考虑最近 3000 毫秒到现在的 ping 数,因此我们可以使用队列存储这些 ping 的记录。当收到一个时间 t 的 ping 时,我们将它加入队列,并且将所有在时间 t - 3000 之前的 ping 移出队列。
即是说,需要在每次数据进入队列的时候查看整个队列并移除所有大于t-3000的值
var RecentCounter = function() {
this.queue = []
};
RecentCounter.prototype.ping = function(t) {
this.queue.push(t);
while (this.queue[0] < t - 3000) {
this.queue.shift();
}
return this.queue.length;
};
折腾一点,整个二分法,效率一下子下来了:
var RecentCounter = function() {
this.arr = [];
};
RecentCounter.prototype.ping = function(t) {
this.arr.push(t);
return this.arr.length - bisectLeft(this.arr, t - 3000);
};
// 二分查找
function bisectLeft(arr, target) {
let left = 0
let right = arr.length
while(left < right) {
const middle = Math.floor((left + right) / 2)
if(target <= arr[middle]) {
right = middle
} else {
left = middle + 1
}
}
return left
}
第k个数
有些数的素因子只有3,5,7,请设计一个算法找出第k个数。注意,不是必须有这些素因子,而是必须不包含其他的素因子。例如,前几个数按顺序应该是1,3,5,7,9,15,21。
这道题完全看不懂...感谢leetcode的大神题解[第 k 个数]:就是想不通为何三指针? - 第 k 个数 - 力扣(LeetCode) (leetcode-cn.com)
解题重点:一个丑数总是由前面的某一个丑数x3/x5/x7得到 (丑数可以看做是素数)
这道题思路大致如下:
- 维护一个数组,存放k+1个元素
- 维护三个指针,分表代表3,5,7倍数的下标
- 维护这三个指针的意义:从结果的规律上看,大致是第n+3个元素为n的倍数,我们可以以此来取得对应的n值。因此这三个指针的作用是保证尽可能小的素数来取值,以获得尽可能小的乘积
- 这三个指针每次向数组推入的值都会使对应的指针向后移一位
- 另一层,指针同时也可以用来互乘时(如3*5)的去重
- 每次我们从数组中取得上一次值对应的下标,分别乘以对应的值,讲最小值填入数组 (语无伦次.jpg)
var getKthMagicNumber = function(k) {
const arr = new Array(k + 1).fill(1)
let p3 = 1
let p5 = 1
let p7 = 1
for(let i = 2; i <= k; i++) {
let num3 = arr[p3] * 3
let num5 = arr[p5] * 5
let num7 = arr[p7] * 7
arr[i] = Math.min(num3, num5, num7)
if(arr[i] === num3) p3++;
if(arr[i] === num5) p5++;
if(arr[i] === num7) p7++;
}
return arr[k]
};
亲密字符串
给你两个字符串s和goal,只要我们可以通过交换s中的两个字母得到与goal相等的结果,就返回true ;否则返回false 。
交换字母的定义是:取两个下标i和j(下标从0开始)且满足i != j,接着交换s[i]和s[j]处的字符。
例如,在"abcd"中交换下标0和下标2的元素可以生成"cbad"。
亲密字符串需要满足的条件:
- 长度相等
- 如果两个字符串相同,那么字符串中一定存在相同的字符
- 如果两个字符串(s和goal)不同,那么字符串中差异的位置必定存在s[i]=goal[j],s[j]=goal[i]
var buddyStrings = function(s, goal) {
if (s.length != goal.length) {
return false;
}
if (s === goal) {
const count = new Array(26).fill(0);
for (let i = 0; i < s.length; i++) {
// 感谢leetcode,让我又了解了一个api的用法
count[s[i].charCodeAt() - 'a'.charCodeAt()]++;
if (count[s[i].charCodeAt() - 'a'.charCodeAt()] > 1) {
return true;
}
}
return false;
} else {
let first = -1, second = -1;
for (let i = 0; i < s.length; i++) {
if (s[i] !== goal[i]) {
if (first === -1)
first = i;
else if (second === -1)
second = i;
else
return false;
}
}
return (second !== -1 && s[first] === goal[second] && s[second] === goal[first]);
}
};
柠檬水找零
在柠檬水摊上,每一杯柠檬水的售价为5美元。顾客排队购买你的产品,(按账单bills支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付5美元、10美元或20美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
给你一个整数数组bills,其中bills[i]是第i位顾客付的账。如果你能给每位顾客正确找零,返回true,否则返回false 。
(这道题有算法吗?)
var lemonadeChange = function(bills) {
let five = 0
let ten = 0;
for (const bill of bills) {
if(bill === 5) {
five++
} else if(bill === 10) {
ten++
five--
} else if(bill === 20) {
if (five && ten ) {
five--;
ten--;
} else if (five >= 3) {
five -= 3;
} else {
return false;
}
}
if(ten < 0 || five < 0) {
return false
}
}
return true;
};
煎饼排序
给你一个整数数组 arr ,请使用 煎饼翻转 完成对数组的排序。
一次煎饼翻转的执行过程如下:
选择一个整数 k ,1 <= k <= arr.length
反转子数组 arr[0...k-1](下标从 0 开始)
例如,arr=[3,2,1,4],选择k=3进行一次煎饼翻转,反转子数组[3,2,1],得到arr=[1,2,3,4]。
以数组形式返回能使 arr 有序的煎饼翻转操作所对应的k值序列。任何将数组排序且翻转次数在10*arr.length范围内的有效答案都将被判断为正确。
这道题目实际上是一种队列的处理。按照题意,我们得出的思路如下:
- 首先找出队列中的最大的值,将它放在队首
- 然后将整个队列翻转一次,将最大值放到最后一位,并派出在排序队列之外(折腾)
- 在剩余的队列中找出最大的值,重复前两步,直到队列排序完毕
var pancakeSort = function(arr) {
const res = []
let maxIndex
while(arr.length > 1) {
maxIndex = findMaxIndex(arr)
// maxIndex为0时默认省去一次翻转
maxIndex > 0 && res.push(maxIndex + 1)
reverse(arr, maxIndex)
reverse(arr, arr.length - 1)
res.push(arr.length)
// 去掉最后一位,只需要处理剩下的数组即可
arr.pop()
}
return res
};
// 查找最大值的index
var findMaxIndex = arr => {
const max = Math.max(...arr)
return arr.findIndex(i => i === max)
}
// 反转队列的方法,只反翻转个部分(0到k)的队列
var reverse = (arr, k) => {
if(k < 1) return
let i = 0
let j = k
while(i < j) {
[arr[i], arr[j]] = [arr[j], arr[i]]
i++
j--
}
}
值得一提的是,这道题示例中的答案是错的...(好家伙,搞心态是吧)
题目总结
不难看到,这些题都是队列类型的题目。队列在js中主要体现为数组,这类题目复杂在多种不同场景的归纳,处理和维护(听着像是业务逻辑?),而2题需要的更多是数学类的规律总结和抽象(最艰难的部分)。