算法学习记录(五十四)

131 阅读3分钟

问:

  1. 一个字符串可以分解成多种二叉树结构,如果长度为1则不可继续分解。如果str长度为N,左边长度可以为[1,N-1],剩下的为右部分长度。左右部分都可以按照同样逻辑继续分解。当分解完毕后,假如允许从根节点开始每一层节点都可以互换位置(也可以不换)。换完后将返回到上一层。这样最终会形成一个新的字符串。这个新的字符串称为str的旋变串。给定str1和str2,判断它们是否互为旋变串

  2. 给定字符串str1和str2。求str1的子串中含有str2所有字符(顺序不作要求)的最小子串长度。

解:

  1. 递归含义为str1以left1开头,str2以left2开头,长度为length时是否互为旋变串。
function isMatch(str1, str2) {
    if (str1.length !== str2.length) return false
    function getRes(left1, left2, length) {
        // 当长度为1时,字符相等就是旋变串
        if (length === 1) {
            return str1[left1] === str2[left2]
        }
        // 假设主函数来到了getRes(1, 2, 5)
        // str1 = [a, b, c, d, e, f ,g]
        // str2 = [h, i, j, k, l, m, n]
        //         0  1  2  3  4  5  6
        // 也就是对比str1的[b~f]和str2的[j~n]
        // 遍历尝试左边应该分几个。
        // 假设左边分4个
        // 那么有以下几种情况
        // str1的[b~c]和str2的[j~k]互为旋变串并且str1的[d~f]和str2的[l~n]互为旋变串
        // str1的[e~f]和str2的[j~k]互为旋变串并且str1的[b~d]和str2的[l~n]互为旋变串
        // 这两种情况下,都可以互换位置使得递归含义为true
        for (let i = length - 1; i >= 1; i--) {
            if (getRes(left1, left2, i) && getRes(left1 + i, left2 + i, length - i)
                || getRes(left1, left2 + length - i, i) && getRes(left1 + i , left2, length - i)
            ) {
                return true
            }
        }
        return false

    }
    return getRes(0, 0, str1.length)
}
  1. 滑动窗口
function getMinLength(str1, str2) {
    // str2的词频表
    const hashMap = new Map()
    for (let i of str2) {
        hashMap.set(i, (hashMap.get(i) ?? 0) + 1)
    }
    let all = str2.length
    // 词频表和all的含义为
    // 假设词频为 a: 2  b: 2  c:1
    // 代表窗口中还需要2个a,2个b和1个c, all为窗口中需要的字符个数,初始为5
    // 当窗口右扩时,找到词频表中的字符时,词频--,all--
    // 当某个词频到0时,表明窗口中已经不缺少这个字符了
    // 当某个词频减少到负数时,表明窗口中这个字符多出来了,词频--,all不需要减,因为这不是窗口需要的字符
    // 当all减到0时,可以结算一次,因为此时窗口刚好拥有所有需要的字符,结算完毕,窗口左侧后移一位。
    let left = 0
    let right = 0
    let minLength = Infinity
    while (left < str1.length && right < str1.length) {
        if (left > right) right = left
        const temp = hashMap.get(str1[right])
        // 如果窗口右边压中词频表中的字符
        if (temp !== undefined) {
            // 词频表对应字符--
            hashMap.set(str1[right], temp - 1)
            // 如果词频原本是大于1的,那么是有效词
            if (temp >= 1) {
                all--
            }
            // 代表窗口中此时包含str2的所有字符,结算一次
            if (all === 0) {
                minLength = Math.min(minLength, right - left + 1)
                // 如果窗口左边框的字符在词频表中,词频+1
                if (hashMap.get(str1[left]) !== undefined) {
                    hashMap.set(str1[left], hashMap.get(str1[left]) + 1)
                    // 如果此时这个字符词频 > 0,那么表明窗口中缺少了这个字符
                    if (hashMap.get(str1[left]) > 0) all++
                }
                left++
                continue
            }
        }
        right++
    }
    return minLength === Infinity ? -1 : minLength
}