javascript:leetcode中的智力发散题

149 阅读3分钟

面试题 17.09. 第 k 个数

首先队列中初始化一个1,设置3个指针p3p5p7都指向1p3指向的数字每次乘3p5指向的数字每次乘5p7指向的数字每次乘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. 亲密字符串

  1. 情况一:两个字符相等,并且字符内有重复字符

  2. 情况二:两个字符不相等,先找到两个字符串sgoal第一个不一样的位置(图示13),再找到第二个不一样的位置(图示24),被两个位置隔开的3段字符都一样,找到的这两个位置上的四个字符得满足1等于42等于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. 任务调度器

首先把出现次数最多的任务安排上,再出现的任务,优先填入冷却时间中

  1. 所有冷却时间都填满,最短时间就是任务总数
  2. 如果冷却时间填不满,假设次数最多的任务出现了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;
};