面试题 17.09. 第 k 个数
首先队列中初始化一个1,设置3个指针p3、p5、p7都指向1、p3指向的数字每次乘3,p5指向的数字每次乘5,p7指向的数字每次乘7,形成3个待选数字,每一轮选出一个最小值,压入队列中,被选中的指针顺序向后移动一位。如果待选数字中出现重复的数字,把重复的数对应的指针都向后移动一位。
var getKthMagicNumber = function(k) {
// 初始化一个`1
let arr = [1];
// 3个指针一开始指向下标为0的1
let p3 = 0, p5 = 0, p7 = 0;
// 数字不够k个,多轮处理
while(arr.length < k){
// 求最小值
let ans = 3 * arr[p3];
ans = Math.min(ans, 5 * arr[p5]);
ans = Math.min(ans, 7 * arr[p7]);
// 被选中的指针顺序向后移动一位,去重
if(ans === 3 * arr[p3]) p3++;
if(ans === 5 * arr[p5]) p5++;
if(ans === 7 * arr[p7]) p7++;
arr.push(ans);
}
return arr[k - 1];
};
313. 超级丑数
和上题一样的做法,只是所有质因数都出现在质数数组
var nthSuperUglyNumber = function(n, primes) {
// 所有质因数指针,指向相关丑数的位置,一开始都指向0
let p = new Array(primes.length).fill(0);
// 初始化1
let data = [1];
// n为1时不进入循环返回1,所以答案初始化为1
let ans = 1;
while(data.length !== n){
// 每一轮,先初始化一下ans,不然还保留着上一轮的值
ans = primes[0] * data[p[0]];
for(let i = 1; i < primes.length; i++){
// 从所有素数乘上他们指针所对应位置元素中,选出一个最小值
ans = Math.min(ans, primes[i] * data[p[i]]);
}
for(let i = 0; i < primes.length; i++){
// 调整每一个素数指针的位置
if(primes[i] * data[p[i]] === ans) p[i]++;
}
data.push(ans);
}
return ans;
};
859. 亲密字符串
-
情况一:两个字符相等,并且字符内有重复字符
-
情况二:两个字符不相等,先找到两个字符串
s和goal第一个不一样的位置(图示1和3),再找到第二个不一样的位置(图示2和4),被两个位置隔开的3段字符都一样,找到的这两个位置上的四个字符得满足1等于4,2等于3,满足这些情况两个字符是亲密字符串。s: ——1——2——
g: ——3——4——
// 字符是否有重复字符
var has_repeat = function(s){
// s 和 goal 由小写英文字母组成
let arr = new Array(26).fill(0);
for(let i = 0; i < s.length; i++){
// 利用字符的ASCII码值与数组下标对应
let ind = s[i].charCodeAt() - 'a'.charCodeAt();
arr[ind] += 1;
// 出现重复
if(arr[ind] === 2) return true;
}
return false;
}
var buddyStrings = function(s, goal) {
// 特判
if(s.length !== goal.length) return false;
// 情况一
if(s === goal) return has_repeat(s);
// 情况二
let i = 0, j;
// 第一个不相等的位置
while(s[i] === goal[i]) ++i;
j = i + 1;
// 在j没有遍历完的前提下,找第二个不相等的位置
while(j < s.length && s[j] === goal[j]) ++j;
// 如果遍历到最后一位退出循环,证明后面字符都一样
if(j === s.length) return false;
// 证明交叉相等
if(s[i] !== goal[j] || s[j] !== goal[i]) return false;
j += 1;
// 需要j后面的字符全部相等
while(j < s.length){
if(s[j] !== goal[j]) return false;
j += 1;
}
return true;
};
860. 柠檬水找零
只涉及一个问题,顾客给的是10元,只能找5元,如果给的是20元,有两种找零方式,一种是一张5元,一张10元,另一种是3张5元,为了之后更方便的找零,所以优先给一张10元,一张5元,没有10元,再找3张5元。解题中记录5元和10元的数量,20元面值最大不能找零,不用记录数量。
var lemonadeChange = function (bills) {
// 10元和5元的数量
let cnt5 = 0, cnt10 = 0;
for (let i = 0; i < bills.length; i++) {
switch (bills[i]) {
case 5: cnt5 += 1; break;
case 10:{
if (cnt5 == 0) return false;
cnt10 += 1;
cnt5 -= 1;
}break;
case 20:{
if (cnt10 > 0 && cnt5 > 0) {
cnt10 -= 1;
cnt5 -= 1;
} else if (cnt5 >= 3) {
cnt5 -= 3;
} else {
return false;
}
}break;
}
}
return true;
};
969. 煎饼排序
采用从大到小排序,先让最大值到最后一位,再让次大值到倒数第二位,依次类推。那么如何让最大值归位呢,先把最大值反转到第一位,再整个反转,最大值就到了最后一位,次大值也一样,依次类推。
var reverse = function(arr, n, ind){
// 反转前n个,最后一个数的下标是n-1
for(let i = 0, j = n - 1; i < j; i++, j--){
// 交换两个值
[arr[i], arr[j]] = [arr[j], arr[i]];
// 更新交换后值的下标,即重新赋值
ind[arr[i]] = i;
ind[arr[j]] = j;
}
return ;
}
var pancakeSort = function(arr) {
// 先统计每个值的下标,因为值从1开始,所以下标数组大小加1
let ind = new Array(arr.length + 1);
for(let i = 0; i < arr.length; i++) ind[arr[i]] = i;
let ret = [];
// 开始反转
// 循环范围:最大的数是arr.length,最小的数是1
for(let i = arr.length; i >= 1; i--){
// 如果当前数字已经在正确的位置上,无须反转
if(ind[i] + 1 === i) continue;
// 优化:第一位无须反转
if(ind[i] + 1 !== 1){
// 把当前i值反转到第一位,i值的下标是ind[i],反转的k就是ind[i]加1
ret.push(ind[i] + 1);
// 对arr数组的前(ind[i] + 1)位进行反转,还需要把反转后数字的位置更新到ind数组中
reverse(arr, ind[i] + 1, ind);
}
// 优化:第一位无须反转
if(i !== 1){
// 反转到正确的位置上
ret.push(i);
reverse(arr, i, ind);
}
}
return ret;
};
621. 任务调度器
首先把出现次数最多的任务安排上,再出现的任务,优先填入冷却时间中
- 所有冷却时间都填满,最短时间就是任务总数
- 如果冷却时间填不满,假设次数最多的任务出现了
c次,有m种任务出现了m次,冷却时间是n,最短时间是(c-1)*(n+1)+m
如果任务数量>(c-1)*(n+1)+m就一定能填满,所以最短时间就是两者取最大
var leastInterval = function(tasks, n) {
// tasks[i] 是大写英文字母
let cnt = new Array(26).fill(0);
// 统计每一种字符出现的次数
for(let i = 0; i < tasks.length; i++){
let ind = tasks[i].charCodeAt() - 'A'.charCodeAt();
cnt[ind] += 1;
}
// 从小到大排序,最后一位最大
cnt.sort(function(a,b){return a-b});
let m = 0;
// 统计有多少任务出现了最多次数
for(let i = 25; i >= 0 && cnt[i] === cnt[25]; i--, m++);
return Math.max(tasks.length, (cnt[25] - 1) * (n + 1) + m);
};
1753. 移除石子的最大得分
首先对三堆石子进行排序,按着从小到大排序,首先干掉第一堆里面,第三堆比第二堆长的数量。接着判断第一堆里面是否为0,否时说明这时第二堆和第三堆的数量是一样的,分别消掉第一堆里面的二分之一个部分,此时第一堆被削掉了,第二堆和第三堆的数量是还一样的,然后不断地减去第二堆和第三堆的数量。最后返回轮数。
var maximumScore = function(a, b, c) {
// 完成abc排序
if(a > b) [a, b] = [b, a];
if(a > c) [a, c] = [c, a];
if(b > c) [b, c] = [c, b];
let ans = 0;
// 用a尽可能把c多出b的那一部分,尽可能消除
let cnt1 = Math.min(c - b, a);
a -= cnt1;
c -= cnt1;
ans += cnt1;
// a不为0,说明b和c一样高,从b和c中平均的去掉a
if(a !== 0){
// 把a奇偶化
if(a % 2 === 1) a -= 1;
b -= a / 2;
c -= a / 2;
ans += a;
}
// 最后的轮数,取自b
ans += b;
return ans;
};