天天看点

重识动态规划前言场景第二天

前言

对动态规划最初的认识:是能解决很难的问题,动态规划是一个很厉害的方法论。

场景

前天做力扣上做一个题目,等级是困难,理解完问题以后,觉得可以使用动态规划来做,但是自己已经记不清如何使用了。就按照自己能想到的方法开始写代码。

题目如下:

你正在安装一个广告牌,并希望它高度最大。这块广告牌将有两个钢制支架,两边各一个。每个钢支架的高度必须相等。

你有一堆可以焊接在一起的钢筋 rods。举个例子,如果钢筋的长度为 1、2 和 3,则可以将它们焊接在一起形成长度为 6 的支架。

返回广告牌的最大可能安装高度。如果没法安装广告牌,请返回 0。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/tallest-billboard
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例 1:

输入:[1,2,3,6]
输出:6
解释:我们有两个不相交的子集 {1,2,3} 和 {6},它们具有相同的和 sum = 6。
示例 2:

输入:[1,2,3,4,5,6]
输出:10
解释:我们有两个不相交的子集 {2,3,5} 和 {4,6},它们具有相同的和 sum = 10。
示例 3:

输入:[1,2]
输出:0
解释:没法安装广告牌,所以返回 0。

提示:

0 <= rods.length <= 20
1 <= rods[i] <= 1000
钢筋的长度总和最多为 5000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/tallest-billboard
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

           

我的解体思路是假设每次能用完rods里的所有钢筋,那么rods里的钢筋长度加起来需要是偶数total,如果不是偶数,则需要去掉rods里一根钢筋再尝试。如果是偶数,这一堆钢筋里能找到一些钢筋加起来等于total/2,如果不能,再去掉一根钢筋进行尝试。

算法采用了递归。并通过下面几种措施减少算的次数:

1、每次算的时候检查总的高度是否小于已经算出的高度

2、总的高度如果计算过了,就不用再算

3、 寻找total/2的时候,避免重复计算,缓存了计算的值

实现代码如下:

/**
 * @param {number[]} rods
 * @return {number}
 */
var tallestBillboard = function(rods) {
    const sortrods = rods.sort((a, b) => {
        if (a > b) {
            return 1
        } else if (a === b) {
            return 0
        } else {
            return -1
        }
    })
    let height = 0
    const halfMap = {}
    // 找到rods里的数加起来,可以等于half
    const findAggregateToHalf = (rods, half) => {
        let key = rods.reduce((total, item) => `${total}${item}`)
        key += `${half}`
        if (halfMap[key] !== undefined) {
            return halfMap[key]
        }
        if (rods.length === 1) {
            halfMap[key] = 0
            return 0
        }
        if (rods.length === 2) {
            if (rods[0] === half || rods[1] === half) {
                halfMap[key] = half
                return half
            }
            return 0
        }
        for (let i = rods.length - 1; i >= 0; i--) {
            if (rods[i] > half) {
                halfMap[key] = 0
                return 0
            }
            const newRods = [].concat(rods)
            const left = newRods.splice(i, 1)[0]
            if (half < left) {
                continue
            }
            if (half === left) {
                halfMap[key] = half
                return half
            }
            if (findAggregateToHalf(newRods, half - left)) {
                halfMap[key] = half
                return half
            }
        }
        halfMap[key] = 0
        return 0
    }
    let max = 0
    const getMaxTall = (rods) => {
        if (rods.length < 2) {
            return 0
        }
        if (rods.length === 2) {
            if (rods[0] === rods[1]) {
                return rods[0]
            } else {
                return 0
            }
        }
        const total = rods.reduce((total, item) => total + item)
        if (halfMap[total]) {
            return halfMap[total]
        }
        if (total < (max * 2)) {
            return 0
        }
        if (total % 2 === 0) {
            const half = total / 2
            if (findAggregateToHalf(rods, half)) {
                if (half > max) {
                    max = half
                }
                halfMap[total] = half
                return half
            } else {
                // 去掉偶数相等的值,如果有值大于剩下
                while(rods[rods.length - 1] > half) {
                    rods.pop()
                }
                for (let i = 0; i < rods.length; i++) {
                    const newrods = [].concat(rods)
                    newrods.splice(i, 1)
                    const result = getMaxTall(newrods)
                    if (result > max) {
                        max = result
                    }
                }
                halfMap[total] = max
                return max
            }
        } else {
            for (let i = 0; i < rods.length; i++) {
                const newrods = [].concat(rods)
                newrods.splice(i, 1)
                const result = getMaxTall(newrods)
                if (result > max) {
                    max = result
                }
            }
            halfMap[total] = max
            return max
        }
    }
    let data2 = getMaxTall(sortrods)
    return data2
};
           

虽然通过了测试,但是时间和空间消耗都比较悲催

重识动态规划前言场景第二天

而且代码可读性也比较查,应该多个地方含有布丁代码。而且这个代码历时5小时才最终通过了所有测试用例。

第二天

看了官方对题解,第一个方法就是动态规划,看第一眼很熟悉,但是仔细看,发现没能看懂官方的方法和状态转移方程。

整天都在思考🤔这个解题思路是咋回事,看了其他人的解题思路后,逐渐有了一点理解,于是开始写自己的版本,官方使用了递归来实现,我没有使用递归,而是循环从最小开始往大了找。

大概的是,在 rods里每增加一根钢筋,有三种情况,第一种是这个钢筋用不上,第二种是这跟钢筋放在左边的支架,第三种是这个钢筋放在右边的支架。那么增加一根钢筋后,最大值就在这三种情况里选了。

这里有一个细节,就是要记录每一次放了钢筋,左边支架和右边支架高度还差多少h,已经钢筋左边的值。我们使用一个map来记录,map的key表示h,map[h]表示钢筋左边的值。这样最后的map['0']就是最后的解。

代码如下:

/**
 * @param {number[]} rods
 * @return {number}
 */
var tallestBillboard = function(rods) {
    const dp = []
    let map = {}
    for (let i = 0; i < rods.length; i++) {
        if (i === 0) {
            map['0'] = 0
            map[`${-rods[i]}`] = 0
            map[`${rods[i]}`] = rods[i]
            continue
        }
        const r = rods[i]
        const nMap = {}
        for (let key in map) {
            nMap[key] = map[key]
        }
        for (let key in map) {
            const value = map[key]
            const kn = Number(key)
            nMap[`${kn + r}`] = Math.max(value + r, nMap[`${kn + r}`] || 0)
            nMap[`${kn - r}`] = Math.max(value, nMap[`${kn - r}`] || 0)
        }
        map = nMap
    }
    return map['0'] || 0
};
           

比第一天的代码清晰了许多,并且更简单,当然依然有一些不足的地方。不过时间和空间复杂度得到了比较好的改善。

重识动态规划前言场景第二天

然后我重新看了一下wiki百科上对动态规划的描述,总结了两点:

1、动态规划用于解决有重叠子问题和最优子结构性质的问题。

2、动态规划通过把问题分解为子问题,且每个子问题有最优解,通过缓存中间子问题的结果来计算最终的结果,这通常需要推导出状态转移方程。

所以下次遇到算法问题,具有重叠子问题和最优子结构问题的时候,就可以考虑使用动态规划了。

比如下面这个问题:

问题描述
给定一个数字三角形,找到从顶部到底部的最小路径和。每一步可以移动到下面一行的相邻数字上。

[2],
[3,4],
[6,5,7],
[4,1,8,3]

从顶到底部的最小路径和为11 ( 2 + 3 + 5 + 1 = 11)。

输入:
[[2], [3,4], [6,5,7], [4,1,8,3]]
输出:
11