leetCode 算法练习

305 阅读4分钟

1、最近在 LeetCode 看一些算法题 我只把题的名字列出来了, 大家有兴趣的可以在leetCode 自行搜索, 如果有更优的解题思路欢迎大家评论区讨论

反转字符串中的单词

Let's take LeetCode contest 需要输出的结果是 s'teL ekat edoCteeL tsetnoc

    function revertByWorld(str) {
        // 在需要 map 的前提下,需要把字符串 按照 空格拆分
        return str.split(' ').map((item) => {
            // 比如 item 为 Let's 时候 需要换成 s'teL 是要用到 反转, 所以又要转成数组, 最后的是把 [s'teL] 用join
            return item.split('').reverse().join('')
        }).join(' ')
    }
    revertByWorld('Let's take LeetCode contest')

计数二进制子串

'1010' 输出 2 因为 10 是不连续的子串 '1100' 输出2 因为 1100 是不连续的子串, 10 是不连续的子串

function countBinarySubstrings(str) {
    str = str.split('')
    // pre 前一个出现的次数, cur 当前出现的次数
    let result = 0, pre = 0, cur = 1
    for (let i = 0 ; i < str.length - 1; i++) {
        if (str[i] === str[i+1]) {
            // 当 i = 0 的时候 1 出现的两次
            cur++
        } else {
            // 当 i = 1 时 会走到这里 pre 记录 上次 1 的两次
            pre = cur
            // 把当前的设置为 1
            cur = 1
        }
        // 当 i== 1 的时候 第一次走到这里  其实就是走到了 110 这时候肯定至少符合一次了嘛  10
        // 按照这个思路 可以在往里边套
        if (pre >= cur) {
            result++
        }
    }
    return result
}
console.log(countBinarySubstrings("1100"))

电话号码组合

手机键盘有 9几个 数字键盘, 1 为空

let map = ['', 1, 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz']

对应 的数字 按照下标 0 1 2 3 ......

也就是说 我们输入 2 3 4 得到 'abc', 'def', 'ghi'

加入用户每次输入两个数字, 通过一个 外循环, 一个内循环就能解决掉这个问题呢, 但是输入 3 5 个数字 就不能这么解决了,也就是说我们不确定循环的次数

function letterCombinations(str) {
    // 对输入做处理,如果小于1返回空(LeetCode测试用例)
    if (str.length < 1) return []
    // 建立电话号码键盘映射
    let map = ['', 1, 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz']
    let code = []
    // code  找到对应的 字母
    str.split('').map((item) => {
        code.push(map[item].split(''))
    })
    if (code.length === 1) {
        return code
    }
    function comb () {
        let tmp = []
        // 前两个的组合
        for(let i = 0; i <= code[0].length - 1; i++) {
            for(let j = 0; j <= code[1].length - 1; j++) {
                tmp.push(`${code[0][i]}${code[1][j]}`)
            }
        }
        // 这一步最重要 把前两个的组合替换掉 前两个 然后在和下一个 在做循环
        code.splice(0, 2, tmp)
        if (code.length > 1) {
            // 接着个下一给做循环
            comb()
        } else {
            return tmp
        }
        return code[0]
    }
    return comb()
}
console.log(letterCombinations("234"))

卡牌分组

[1,2,2,1,2,2] 返回true 因为可以拆分成 [1, 1], [2, 2], [2, 2] 把 4个 2 拆分成两组

[1,1,2,2,1,2,2] 返回 false 因为可以拆分成 [1, 1, 1], [2, 2, 2], [2] 多出来一个 2

也就是说每个的长度都是最少的倍数, 也就是说求几个数的最大公约数 (什么是 最大公约数 我也有专门的一篇博客)

function hasGroupsSizeX(arr) {
    // sort 可以修改原数组
    arr.sort()
    let group = {}
    // 求每个数组数显的次数
    arr.map(((item) => {
        group[item] = group[item] ? ++group[item] : 1
    }))
    // 辗转相除法 求最大公约数
    function gcb (a, b) {
        if (b === 0) {
            return a
        } else {
            return gcb(b, a % b)
        }
    }
    // 这里已经转成数组
    group = Object.values(group)
    while(group.length > 1) {
        let [a, b] = [group.shift(), group.shift()]
        let val = gcb(a, b)
        if (val === 1) {
            return false
        } else {
            // 这里很重要, 把两个比较完的最大公约数 和 下一个在做比较
            group.unshift(val)
        }
    }
    // group 最后只会剩下一个
    return group.length ? group[0] > 1 : false
}
hasGroupsSizeX([1,2,2,1, 2, 2])

贪心算法

总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解

1、分发饼干

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

function findContentChildren(g, s) {
    // 一般情况下, 贪心算法首先需要排序
    g.sort()
    s.sort()
    let i = 0 , j = 0, result = 0
    // 大家想一个问题哈, 当 i j = 0 的时候 小孩是吃饱了, 当 i j = 1 的时候 小孩吃不饱
    // 所以 只有在小孩吃不饱的情况下, 小孩不能变, 饼干要变 
    while(i <= g.length && j <= s.length) {
        if (g[i] <= s[j++]) {
            i++
            result++
        }
    }
    return result
}
findContentChildren([1, 3], [1, 2, 3])

2、摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。

function wiggleMaxLength(arr) {
    if(arr.length <= 2) {
        return arr.length
    }
    let result = 1, flag = 0 // flag 如果等于0 说明还没有摆动
    let pre, cur
    for (let i = 1; i < arr.length; i++) {
        pre = arr[i - 1]
        cur = arr[i]
        // 这个很重要,比如 [2, 5, 3]
        // 2 - 5 < 0  这时候  走到 if 后边的判断  flag < 0
        // 5 - 3 > 0 flag < 0 走到 if 前边的判断  flag > 0  // result = 3
        if ((pre - cur > 0 && flag <= 0) || (pre-cur < 0 && flag >=0)) {
            // 每次都要更新 flag 的状态
            flag = pre - cur
            result++
        }
    }
    return result
}
wiggleMaxLength([1,17])

移掉K位数字

移除 k 个数字之后 得到一个最小的数

举个栗子: 1445219 去掉 3 位 445 得到最小数字 1219 按照思路是 删除高位比较大的数字, 第一次循环 删除4, 第二次循环删除4, 第三次循环删除5, 每次都要从 0 的位置从新删除, 当 数字足够大, 而且删除的位数够多, 那这样的效率是很慢的

把 小的数字 推入栈内, 当前数字和栈内所有的元素对比, 大于 栈内的 元素呢, 推入栈内, 小于呢 退出顶部栈内的元素

function removekdingits(num, k) {
    let n = num.length
    if (n <= k) return '0'
    let stack = []
    for (let i = 0; i < n; i++) {
        // 当前元素和栈内所有元素对比
        while ( k && stack.length && num[i] < stack[stack.length - 1]) {
            k--
            stack.pop()
        }
        if ( stack.length || num[i] !== '0' ) { // 如果是 空栈, '0'就不要加入了
            stack.push(num[i])
        }
    }
    // 如果 k 还有值, 需要把最后的删除掉
    while(k--) stack.pop()
    return stack.join('') || '0'
};
removekdingits('114659', 3)