前端算法入门之路(十一)(哈希表与布隆过滤器)

144 阅读3分钟

哈希表

  1. 若关键字为k,则其值存放在f(k)的存储位置上
  2. 对不同的关键字可能得到同一散列地址,即k1≠k2,而f(k1)=f(k2),这种现象称为哈希冲突

哈希冲突处理

  1. 开放定址法: 产生冲突之后去寻找下一个空闲的空间
  2. 再哈希法:产生冲突之后再进行哈希,直到没有冲突
  3. 建立公共溢出区:将冲突的元素放入公共溢出区
  4. 拉链法:冲突的元素位置处理成链表往下接

LeetCode肝题

    1. 设计哈希集合
// ?
var MyHashSet = function() {
    this.data = {}
};
MyHashSet.prototype.add = function(key) {
    this.data[key] = true
};
MyHashSet.prototype.remove = function(key) {
    this.data[key] = false
};
MyHashSet.prototype.contains = function(key) {
    return !!this.data[key]
};
    1. 设计哈希映射
// ??
var MyHashMap = function() {
    this.data = {}
};

MyHashMap.prototype.put = function(key, value) {
    this.data[key] = value
};

MyHashMap.prototype.get = function(key) {
    return this.data[key] == undefined ? -1 : this.data[key]
};

MyHashMap.prototype.remove = function(key) {
    this.data[key] = -1
};
  1. 面试题 16.25. LRU 缓存
// 使用Map结构简单实现
var LRUCache = function(capacity) {
    this.limit = capacity
    this.data = new Map()
};

LRUCache.prototype.get = function(key) {
    const flag = this.data.has(key)
    if (!flag) return -1
    const value = this.data.get(key)
    this.data.delete(key)
    this.data.set(key, value)
    return value
};

LRUCache.prototype.put = function(key, value) {
    const flag = this.data.has(key)
    if (!flag) {
        if (this.data.size == this.limit) {
            const first = this.data.keys().next().value
            this.data.delete(first)
            // this.data.delete([...this.data][0][0])
        }
    } else this.data.delete(key)
    this.data.set(key, value)
};
    1. TinyURL 的加密与解密
// 做一个时间戳对网址的映射即可(注释为骚操作)
let data = new Map()
var encode = function(longUrl) {
    const s = new Date().getTime
    data.set(s, longUrl)
    return s
    // return longUrl
};

/**
 * Decodes a shortened URL to its original URL.
 *
 * @param {string} shortUrl
 * @return {string}
 */
var decode = function(shortUrl) {
    return data.get(shortUrl)
    // return shortUrl
};
    1. 重复的DNA序列
// 遍历字符串,每次截取10位放入map中,如果没有出现过记录为1,出现过的话将值+1
var findRepeatedDnaSequences = function(s) {
    if (s.length < 11) return [];
    let obj = {}, ans = []
    for(let i = 0; i < s.length - 9; i++) {
        let str = s.slice(i, 10 + i)
        obj[str] ? obj[str] += 1 : obj[str] = 1
    }
    for(let item of Object.keys(obj)) {
        if (obj[item] > 1) ans.push(item)
    }
    return ans
};
    1. 最大单词长度乘积
// 暴力解法判断哪是否有字母重复
var isRepeat = function(str1, str2) {
    for(let i of str1) {
        if (str2.includes(i)) return true
    }
    return false
}
var maxProduct = function(words) {
    let ans = 0
    for(let i = 0; i < words.length; i++) {
        for(let j = i + 1; j < words.length; j++) {
            if (isRepeat(words[i], words[j])) continue
            ans = Math.max(ans, words[i].length * words[j].length)
        }
    }
    return ans
};
    1. 搜索二维矩阵 II
// 这种矩阵有两个特殊点,右上角和左下角,从右上角开始搜索,如果比target大则到下一行,比target小则到前一列
var searchMatrix = function(matrix, target) {
    let i = 0, j = matrix[0].length - 1
    while(i < matrix.length && j >= 0) {
        if (target == matrix[i][j]) return true
        if (target > matrix[i][j]) i++
        else j--
    }
    return false
};
    1. 在二叉树中分配硬币
// 赋予递归函数一个明确的意义:寻找当前节点需要拿入/拿出的硬币数量
// 等于root.val - 1 加上左子树需要拿入/拿出的硬币数量加上右子树需要拿入/拿出的硬币数量
var distributeCoins = function(root) {
    var getResult = function(root) {
        if (!root) return 0
        let l = getResult(root.left)
        let r = getResult(root.right)
        ans += Math.abs(l)
        ans += Math.abs(r)
        return root.val - 1 + l + r
    }
    let ans = 0
    getResult(root)
    return ans
};

9.430. 扁平化多级双向链表

// 方法一:栈加迭代
var flatten = function(head) {
    let p = head, q = []
    while(p) {
        // 当到子链表最后一个节点,从q中取出节点接上,指针继续往后走
        if(!p.next && !p.child && q.length) {
            let a = q.pop()
            p.next = a
            a.prev = p
            p = p.next
            continue
        }
        // 没有子节点指针往后走
        if (!p.child) {
            p = p.next
            continue
        }
        // 处理有子节点的情况
        if (p.next) q.push(p.next)
        p.next = p.child
        p.next.prev = p
        p.child = null
        p = p.next
    }
    return head
};
// 方法二:递归
// 赋予递归函数一个明确的意义:将链表中的child节点添加到当前节点后面
var flatten = function(head) {
    let p = head, q
    while(p) {
        // 如果有子节点,递归处理子节点,将子节点接到p的后面,将指针移动到子链表的最后一个节点与q接上
        if(p.child) {
            q = p.next
            p.next = flatten(p.child)
            p.child = null
            p.next.prev = p
            while(p.next) p = p.next
            p.next = q
            if (q) q.prev = p
        }
        p = p.next
    }
    return head
};
    1. 二叉树中所有距离为 K 的结点
// 首先找到目标节点,过程中给经过的节点加上parent属性,方便回溯
// 从目标节点出发,相当于从三个方向left、right、parent找距离为k的节点,每经过一个节点把路径记录到path里,防止重复
var distanceK = function(root, target, k) {
    let ans = [], targetRoot, path = {}
    let findTarget = (root) => {
        if (!root) return
        if (root.val == target.val) {
            targetRoot = root
            return
        }
        if (root.left) {
            root.left.parent = root
            findTarget(root.left)
        }
        if (root.right) {
            root.right.parent = root
            findTarget(root.right)
        }
    }
    findTarget(root)
    let findPath = (root, k) => {
        if (!root) return
        if (!path[root.val] && k == 0) ans.push(root.val)
        path[root.val] = true
        findPath(root.left, k - 1)
        findPath(root.right, k - 1)
    }
    findPath(targetRoot, k)
    while(targetRoot.parent && k) {
        findPath(targetRoot.parent, --k)
        targetRoot = targetRoot.parent
    }
    return ans
};