leetcode 956 最高广告牌 (求数组中和相等且最大的两个子数组)
你正在安装一个广告牌,并希望它高度最大。这块广告牌将有两个钢制支架,两边各一个。每个钢支架的高度必须相等。
你有一堆可以焊接在一起的钢筋 rods。举个例子,如果钢筋的长度为 1、2 和 3,则可以将它们焊接在一起形成长度为 6 的支架。
返回广告牌的最大可能安装高度。如果没法安装广告牌,请返回 0。
示例 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
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路:
将问题转化为求数组和为0时的组合
对任何一个数,可以用三种方式对待它,乘以1,-1或0,目标是求和为0时的最大正数和
例如,[1,2,3], 可以对1,2乘以1,3乘以-1,此时和为0, 最大正数和为1+2=3
用字典来存储每一步的结果,键和值分别是(k:v) 总和以及正数和,
初始化时dp={0:0},表示和为0时的最大长度为0
那么最后只需要求dp[0]的最大值就ok辣
遍历所有钢筋:
对每根钢筋都有三种处理方式:加,减,丢 (对应乘以1,-1或0)
如:[1,2,3]
第一步: 用钢筋1,对初始的0,操作
如果加,那么总和是1,正数是1;如果减,总和是-1,正数0;如果丢,维持不变;更新dp={0:0, 1:1, -1:0}
第二步: 用钢筋2,对第一步中dp的键0,1,-1的基础上分别进行“加,减,丢 ”的操作
在0:0基础上,如果加,也就是变为2:2;如果减,变为-2:0;如果丢,变成0:0
类似的,在1:1基础上,加减丢变为3:3,-1:1,1:1
类似的,在-1:0基础上,加减丢变为1:2,-3:0,-1:0
每个键取较大值,用粗体标识了,然后更新dp={0:0, 1:2, 2:2, -1:1, 3:3, -2:0, -3:0}
总和为1时,相比第一步时的正数和为1,第二步时正数和变为了2,将dp[1]修改为更大的2
总和为-1时,相比第一步时的正数和为0,第二步时正数和变为了1,将dp[-1]修改为更大的1
最后返回dp[0]
作者:wzNote
链接:https://www.jianshu.com/p/fffe353d45c7
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
以下为错误代码
# 我的代码如下,并不能ac,原因在于更新列表时候,要用最新的值(键要用老的)
class Solution:
def tallestBillboard(self, rods):
dp = {0: 0}
for i in rods:
dp1 = dp.copy()
for k in list(dp1.keys()):
dp[k - i] = max(dp1.get(k - i, 0), dp1[k])
dp[k + i] = max(dp1.get(k + i, 0), dp1[k] + i)
#print(dp)
#print()
return dp[0]
# 正确
print(Solution().tallestBillboard([1,2,3,6]))
#这个用例会错误
print(Solution().tallestBillboard([61, 45, 43, 54, 40, 53, 55, 47, 51, 59, 42]))
原因在于第三次迭代的时候dp数组结果为{0: 0, -61: 0, 61: 61, -45: 0, 45: 45, -106: 0, -16: 45, 16: 61, 106: 106, -43: 0, 43: 43, -104: 0, -18: 43, 18: 61, 104: 104, -88: 0, -2: 43, 2: 45, 88: 88, -149: 0, -63: 43, -59: 45, 27: 88, -27: 61, 59: 104, 63: 106, 149: 149}
可以看到有个键-106,106在前面,这个时候第四次迭代的值是54,会更新键-52:54,52:106 考虑这个时候碰到键-2,2的时候则更新为52:106, -52:54
我没有考虑这个-106 106(因为用的老值),所以遇到-2,2的时候是第一次更新,变为52:97,-52:45然后就错了
正确代码如下
class Solution:
def tallestBillboard(self, rods):
dp = {0: 0}
for i in rods:
# 浅拷贝和深拷贝请看https://www.cnblogs.com/alimy/p/10374923.html
dp1 = dp.copy()
for k in list(dp1.keys()):
# 把这里的dp1改成了dp,那么获取的就是最新值啦~
dp[k + i] = max(dp.get(k + i, 0), dp1[k] + i)
dp[k - i] = max(dp.get(k - i, 0), dp1[k])
return dp[0]
# 来自https://www.jianshu.com/p/fffe353d45c7
class Solution(object):
def tallestBillboard(self, rods):
dp = {0:0}
for r in rods:
# 注意python语言的特性,下面这一行的pre_sum, pos_sum就是上一次迭代dp的老值,不会随dp变了他就变了
for pre_sum, pos_sum in list(dp.items()):
dp[pre_sum-r] = max(dp.get(pre_sum-r, 0), pos_sum)
dp[pre_sum + r] = max(dp.get(pre_sum+r,0), pos_sum+r)
print(dp)
print()
return dp[0]
对python语言这个特性的解释如下
Python连续赋值需要注意的地方
在python中是可以使用连续赋值的方式来一次为多个变量进行赋值的,比如:
a = b = c = 1
a, b, c = 1, 1, 1
这些都可以完成变量的赋值,但是就有一个问题了,比如:
a = 3
a, b = 1, a
如果按照正常的思维逻辑,先进行a = 1,在进行b = a,最后b应该等于1,但是这里b应该等于3,因为在连续赋值语句中等式右边其实都是局部变量,而不是真正的变量值本身,比如,上面例子中右边的a,在python解析的时候,只是把变量a的指向的变量3赋给b,而不是a=1之后a的结果,这一点刚开始学python的人可能容易误解,再举一个Leetcode里链表的例子理解就更深了。 见链接,建议画图
假如要对一个链表进行翻转,就比如把1—>2->3->4转化为4->3->2->1
对于这个问题很简单,只要反转指针就可以了,假如链表结构为:
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
我们可以用很简单的三行代码完成这个过程:
def reverseList(self, head):
L = ListNode(float("-inf"))
while head:
L.next, head.next, head = head, L.next, head.next
return L.next
这里的L是指向一个新建的结点,因为python没有指针的概念,所以用一个额外的结点来代替头指针,这里的核心代码就是中间那一行三个变量的连续赋值,如果单独一句句来理解的话,最后肯定是想不通的,在这里,假设head结点是链表串’1->2->3->4’的头结点,先用新的L结点的next指针指向head的第一个结点‘1’,之后将L.next(第一次也就是空)赋给了head的next指针,之后再把head的next指针(注意,这里的next指针还是指向‘2’的,而不是空)赋给head,相当于next向前移一位,这一步相当于一个串变成了两个:
L:‘-inf’->‘1’
head:‘2’->‘3’->‘4’->‘5’
种一棵树最好的时间是十年前,其次是现在。