前端就该用 JS 刷算法7

261 阅读4分钟

每日一题 -- 树

恢复二叉搜索树

O(n) 空间复杂度的解法

  • 题目首先已经说了,如果用空间复杂度为 O(n) 的解法很简单,那么明显提示可以先中序遍历保存数组,根据数组来解决的方式更简单
  • 中序遍历一课 BST, 得到的应该是一个单增的数组,既然题目已知只改动了两个节点,那就是改成如果在数组中移动两个值,使得数组单增
  • 我是直接排序出另外一个单增数组,然后直接两个数组作比较得到两个改变值;既然暴力了,就暴力到底
  • 然后就是再次中序遍历树,这次只有找到改变值,然后将另外的值赋值过去即可。
  • 至于其他更高效的办法,等我级别更高再说吧,毕竟我现在是举三反一,先多做,后面再来一题多解,加油

// https://leetcode-cn.com/problems/recover-binary-search-tree/

/**
 * 暴力法
 * @分析
 * 题目说用 O(n) 空间复杂度 的方式很容易解决,那我们就用简单暴力的先搞一遍
 * 
 * @注意
 * 1. 排序的时候记得要深拷贝,不然原地排序就 gg 了
 * 
 * @时间复杂度分析 
 * 1. 遍历一课二叉搜索树,$O(h)$
 * 2. sort 排序 $O(logn)$
 * 3. 遍历比较 $O(n)$
 * 4. 中序遍历修改 $O(h)$
 * 所以是 $O(n)$
 * 
 * @空间复杂度
 * 1. 这里中序遍历保存值了,所以就是题目说的 O(n) ,确实不算难,单谁让我是菜鸡呢
 * 2. 要不要继续
 */

var recoverTree = function(root) {
    const res = []
    const dfs = root => {
        if(!root) return 
        dfs(root.left)
        res.push(root.val)
        dfs(root.right)
    }
    dfs(root)
    // 中序遍历搞一波,根据找到不递增的两个值
    // 题目变为移动数组两个值,使得数组单增,求这两个值
    const sorted = [...res].sort((a,b) => a-b)
    const changedArr=[]
    for(let i =0;i<res.length;i++){
        if(res[i]!==sorted[i]){
            changedArr.push(res[i])
        }
    }
    // 这里有且仅有一个长度为 2 的数组
    // 然后重新遍历 root,找到对应的值,改成另外值即可
    const [a,b] = changedArr
    const dfsToChange = root => {
        if(!root) return 
        dfsToChange(root.left)
        if(root.val === a){
            root.val = b
        }else if(root.val === b){
            root.val = a
        }
        dfsToChange(root.right)
    }
    dfsToChange(root)
    return root
};

91 算法 -- 滑动窗口

定长子串中元音的最大数目

复杂度分析

时间复杂度

  • 遍历 s 了, O(n)O(n)
  • 判断是否为元音字母,用的是对象查找 O(1)O(1)
  • 所以是 O(N)O(N),n 为 s 的长度

空间复杂度

  • O(1)O(1)

// https://leetcode-cn.com/problems/maximum-number-of-vowels-in-a-substring-of-given-length/

/**
 * 滑动窗口专题
 * @分析
 * 1. 先将原因存起来,用于比较
 * 2. 这里是定长的子串,移动过程中增删同时
 */
var maxVowels = function (s, k) {
    if (s.length < k) return 0
    let max = 0 // 最多
    let temp = 0 // 当前窗口的元音数量
    for (let i = 0; i < s.length; i++) {
        if(i>=k){
            // 开始滑动窗口
            if(isVowels(s[i-k])) temp-- // 窗口划走了
        }
        if(isVowels(s[i])) temp++
        max = max > temp ? max : temp
    }
    return max

};


// 判断是否是元音
const isVowels = (word) => {
    const map = {
        'a': 1,
        'e': 1,
        'i': 1,
        'o': 1,
        'u': 1
    }
    if (map[word]) return true
    return false
}


LC 的每日一题

547. 省份数量

分析

构建简单的并查集类

  • 先构建一个简化版的的并查集类,在这里只需要能够实现合并集合就可以了, rank什么都不要
  • 合并的时候也不用考虑是 a 指向 b 还是 b 指向 a,只要能减少size 就好

执行

  • 直接两个循环查看二维数组中的值
  • 这个数组肯定是长宽一致,且对角线对称的二维数组,不然连线就混乱了,所以只需要判断一半的值即可
  • 每当 M[i][j] === 1 ,则这两个城市是一个省份,所以就把这两个集合并成一个
  • 初始化是将每个城市都是一个集合,当遍历结束了,那么同一个省的城市已经合并成了一个集合,最后剩下来的集合就是省份隔离,就是省份的数量了。
// https://leetcode-cn.com/problems/number-of-provinces/

class UnionFind {
    constructor(size) {
        this.parents = Array(size).fill(0).map((_, i) => i) // [1,2,3]
        this.numberOfSets = size // set 的个数
    }

    // 求还有多少个集合
    size() {
        return this.numberOfSets
    }

    findSet(x) {
        // 如果传入的值和在 this.parents[x] 不一样,代表已经指向另外的集合了,所以需要递归的去查找
        if (x !== this.parents[x]) {
            this.parents[x] = this.findSet(this.parents[x])
        }
        // 在没有合并过的集合中,x === this.parents[x]
        return this.parents[x]
    }

    unionSet(x, y) {
        const px = this.findSet(x)
        const py = this.findSet(y)
        if (px === py) return // 在同一个集合中
        // 如果不是,则其中一个集合的值指向另外一个集合
        this.parents[py] = px
        this.numberOfSets--
    }
}

const findCircleNum = function(M){
    // 有 len 个城市,也是最大的省数
    const len = M.length
    // 创建一个长度为 len 的并查集
    const uf = new UnionFind(len)
    for(let i=0;i<len;i++){
        for(let j = i+1;j<len;j++){
            // 当值为 1 时,证明这两个城市是连接的,所以需要合并这两个集合
            M[i][j] === 1 && uf.unionSet(i,j)
        }
    }
    // 最后只需要看看还剩多少个集合,即有多少个城市
    return uf.size()
}