算法学习记录(五十三)

89 阅读2分钟

问:

  1. 给定一个整数数组,其中可能有正有负有0,求子数组的最大异或和
  2. 戳气球
  3. 汉诺塔问题中,假设一个数组arr=['右','右','中','左'],其含义为第i个圆盘在第arr[i]柱上。那么请返回这个arr代表的圆盘状态在汉诺塔最优运行策略中是第几步达到的。如果不是汉诺塔最优运行策略中的某一状态则返回-1。

解:

  1. 暴力解,遍历数组,以arr[i]作为子数组的结尾时,再遍历0~i,以arr[j]作为子数组的开头时,每一种情况都异或一遍,找到最大情况。
// 暴力解
function getMaxEor(arr) {
    if (!arr.length) return 0
    let eorI = 0
    let maxEor = 0
    // 以arr[i]结尾
    for (let i = 0; i < arr.length; i++) {
        // 0 ~ i的异或和
        eorI ^= arr[i]
        // 以arr[j]开头
        for (let j = 0; j <= i; j++) {
            let eorPre = 0
            for (let k = 0; k <= j - 1; k++) {
                // 0 ~ j - 1的异或和
                eorPre ^= arr[k]
            }
            // [j...i] 的异或和 = [0..i]的异或和  ^  [j-1...i]的异或和
            const curEor = eorI ^ eorPre
            maxEor = Math.max(maxEor, curEor)
        }
    }
    return maxEor
}

// 预处理生成前缀异或和数组。省去第三层遍历
function getMaxEor(arr) {
    if (!arr.length) return 0
    let maxEor = 0
    const preEor = []
    for (let i = 0; i < arr.length; i++) {
        preEor[i] = (preEor[i - 1] ?? 0) ^ arr[i]
    }
    // 以arr[i]结尾
    for (let i = 0; i < arr.length; i++) {
        // 以arr[j]开头
        for (let j = 0; j <= i; j++) {
            // j ~ i 的异或和 = 0~i的异或和 ^  j-1~i的异或和
            const needEor = preEor[i] ^ (preEor[j - 1] ?? 0)
            maxEor = Math.max(maxEor, needEor)
        }
    }
    return maxEor
}
// TODO 前缀树解法。比较麻烦以后再写
// 范围上尝试模型,尝试left~right范围上,每一个位置最后打爆的情况。
// 暴力递归
function maxCoins(arr) {
    function getRes(left, right) {
        if (left === right) return (arr[left - 1] ?? 1) * arr[left] * (arr[right + 1] ?? 1)
        if (left > right) return 0
        let res = -Infinity
        for (let i = left; i <= right; i++) {
            let psb
            if (i === left) {
                psb = (arr[left -1] ?? 1 * arr[i] * (arr[right + 1] ?? 1)) + getRes(left + 1, right)
            } else if (i === right) {
                psb = (arr[left -1] ?? 1 * arr[i] * (arr[right + 1] ?? 1)) + getRes(left, right - 1)
            } else {
                psb = getRes(left, i - 1) + getRes(i + 1, right) + (arr[left -1] ?? 1 * arr[i] * (arr[right + 1] ?? 1))
            }
            res = Math.max(res, psb)
        }
        return res
    }
    return getRes(0, arr.length - 1)
}
// 递归改dp
function maxCoins(arr) {
    const dp = []
    for (let i = 0; i < arr.length; i++) {
        dp[i] = []
        for (let j = 0; j < arr.length; j++) {
            if (i === j) {
                dp[i][j] = (arr[i - 1] ?? 1) * arr[i] * (arr[j + 1] ?? 1)
            }
            if (i > j) {
                dp[i][j] = 0
            }
        }
    }
    for (let i = arr.length - 2; i >= 0; i--) {
        for (let j = i + 1; j < arr.length; j++) {
            dp[i][j] = -Infinity
            for (let k = i; k <= j; k++) {
                let pro
                if (k === i) {
                    pro = ((arr[i -1] ?? 1) * arr[k] * (arr[j + 1] ?? 1)) + dp[i + 1][j]
                } else if (k === j) {
                    pro = ((arr[i - 1] ?? 1) * arr[k] * (arr[j + 1] ?? 1)) + dp[i][j - 1]
                } else {
                    pro = dp[i][k - 1] + dp[k + 1][j] + ((arr[i - 1] ?? 1) * arr[k] * (arr[j + 1] ?? 1))
                }
                dp[i][j] = Math.max(dp[i][j], pro)
            }
        }
    }
    return dp[0][arr.length - 1]
}
function getStep(arr) {
    function getRes(n, from, to, other) {
        if (n === -1) return 0
        // 判断第n个盘子现在在哪
        // 最优轨迹中没有需要 当前圆盘 去other的时候
        if (arr[n] === other) return -1
        if (arr[n] === from) {
            // 代表把 前n-1 个圆盘全部挪到 other 上的过程还没走完,所以去看前n-1挪到哪了
            return getRes(n - 1, from, other, to)
        }
        if (arr[n] === to) {
            // 代表已经把第 n 个圆盘挪到to上了
            // 继续去看 第n-1个盘子的情况
            const preStep = getRes(n - 1, other, to, from)
            if (preStep === -1) return -1
            // 汉诺塔问题中 第 n 个盘 走完需要 2的n次方步。
            return 2 ** (n) + preStep
        }
    }
    return getRes(arr.length - 1,'左', '右', '中')
}