这里的分割列表的最大长度不是指列表长度,而是指列表里面的数值总和。
如何理解分割成两个等和的列表子集?
为了确定是否能够得出两个等长木棍,我们就看在这些木棍中能否拼出总长度之和的一半这个数来,如果可以,这些木棍就能够分出两条等长的木棍。举个例子, 有4根木棍:4 2 9 2 木棍长度总和是4+2+9+2 = 17 。因为两根木棍长度之和必定是偶数,所以这些木棍要想拼出两根等长木棍是不可能的。但如果木棍长度是 4 2 9 3 长度总和是 18,就要看木棍列表中能否凑出18 / 2 = 9 这个数了。我们一眼望去可以发现,是能够凑出9这个数的,因此 4 2 9 3 这个木棍列表是能够分出两根的等长木棍的。
不知道我说到这里各位同学能否明白这道题的解决思路,如果不明白,我再举个例子。还是以 4 2 9 2 木棍为例,我们知道,这些木棍是不能分出两个等长木棍的。但如果我硬要得出两根等长木棍的最大长度是多少,该怎么办呢? 就要舍弃掉一些木棍了,舍弃谁好呢?我们一眼望去可以发现 舍弃了9后木棍列表变成 4 2 2 是可以凑成两根等长木棍的,木棍的长度是(4+2+2)/ 2 = 4。而4也是在 4 2 9 2 木棍列表中所能找到等长的单根的最大长度,所以4作为结果返回。
说到这里不知道同学们能不能区分开以上整体与部分的关系,如果可以我们继续,否则再倒回上面仔细琢磨一下。
=================================================================
现在我们就知道该干啥了,就是要判断木棍列表能否凑出长度之和的一半(sum(Stick_list) / 2)这个数。细心的同学看到前面的两个一眼发现都加粗显示了,因为,可能肉眼看起来容易找到,但如果想告诉电脑该怎么发现并不容易。
这里就要使用01背包思路来求解了。01背包是整道题的前置知识点。如果01背包不懂的朋友没关系,@代码随想录 在B站有非常详细的讲解视频,一次看不懂没关系,多看几遍,我也是看了四五遍才懂的,一定要理解每个步骤,理解完后会发现并没有你想的这么难。🔍🔍戳这里进入~
搞清楚01背包后,你会发现,判断木棍列表能否凑出长度之和一半这个数 还是不知道该如何下手。说实话让我想,我也想不出用01背包。
那你会不会说岂不是白学01背包了?当然不是,01背包应用是非常广的,你现在遇不到以后也会遇到,越早学越好。学了01背包,起码是不知道怎么做,如果告诉你思路你就能够做,但你学都没学的话,那跟你说了也不会。
我们看一下力扣这道题,可以说跟我们所要求的东西非常相似。
📮📮📮虽然我现在知道该怎么做,但我建议你还是跟着视频学好点,如果单纯看文字描述思考的话会比较枯燥而且较难理解。我是看leetcode官方题解视频学会的,也是看了四五遍才懂,所以推荐给大家。 🔑🔑leetcode官方视频题解
跟着他们学还是挺有意思的。
上面是分割线,当你看到这里的时候我默认当做你已经会了判断木棍列表能否凑出长度之和一半这个数。
如果初始的木棍列表能够凑出长度之和一半这个数,那么我们就能直接返回结果。但如果不能的话我们就要在这个列表中删掉一些木棍再来看是否能够分成两根等长木棍,最后在这些可能的情况中返回木棍长度的最大值作为结果。
接着我们思考一下,如何在删掉一些木棍的列表中找出能够分成两根等长木棍的最大长度?
我最开始的思路是使用BFS让木棍列表按字典序由小到大逐步弹出一个元素,每弹一个匹配一次,当第一次匹配到的木棍列表的时候长度必然 (我的想法而已)最大,因为我是按字典序从小到大弹出元素的,每次将小的弹出,再弹大的,那如果找到第一次能够划分等长木棍的列表,应该就是木棍的最大长度了。比如:假设木棍 1 2 3 4 凑不出两根等长的木棍,我的思路就是弹出1 变成 234 然后匹配看234能否凑出等长木棍,不行的话弹出2变成134匹配,不行再弹出3 变成124匹配等等等,然后弹出12 13 14 23 24 34 这样逐个弹出并匹配。
感觉挺完美的,但一提交结果就拿了个50分。想了半天,头发也都快抓没了,就是想不出来,因此我也改了名,我本来叫李帅哥的,哎,说多都是泪。但最后调试打印长度总和的时候终于看到了问题所在。
我们先看个优化前的数据截图
左边列表是用来判断是否能够拼成两根等长木棍的,右边的那个数是长度总和。注意又是假设啊,假设图中两个红框的列表才能分成两根等长的木棍,那么一定是先出现的列表长度就越大吗?答案是否定的,存有五个数的列表未必会大过存有三个数的列表,可能数多的列表元素值更小。
既然知道了不能够直接返回第一个能够分成两根等长木棍的列表,那么我们就要遍历搜索找出能够分割成等长木棍的最大值了。
我的做法是:先将木棍列表降序排序,之后使用DFS得到木棍列表的每个子集,然后分别判断这个子集能够否分成两根等长的木棍,如果可以,比较得出最大值,最后返回结果,期间需要剪枝。
剪枝还是挺有讲究的,没搞好分分钟超时。我们在遍历过程中需要让子集调用一个自定义的函数,这个函数是用来判断列表是否能够分出两根等长木棍的,但如果每有一个子集就要匹配一次的话,会消耗大量资源。因此在调用这个函数前,要进行一个判断:如果此列表总和大于已知的木棍之和最大值,以及这个列表总和是偶数(奇数的话必定不能分成两根等长木棍),才能调用那个函数。让列表降序排序再遍历是因为整体列表长度总和还是由高到低的,这样能较为容易的得到更大值,那么就能直接将子集中更小的值排除,而无需进行函数调用。
接着就是DFS的经典剪枝操作了,如果有数组used用来标记元素是否使用的话,就是,如果这个数使用过,可以直接跳过;如果这个数与前一个数相等且前一个数没有使用过的话,跳过。同时,要有个start标记起始位置,每走一层递归start+1。当然,这里不是排列而是组合(也就是说(2,1),(1,2)存在一个即可)可以不使用used数组,直接+1即可。
如果你看不懂我说的剪枝操作,没关系,做多一点回溯题即可,做多两条你就会发现,这类题型写法总是千变一律啊…
如果你想多了解回溯算法的剪枝思路,我还是推荐你看下@代码随想录的讲解视频,讲的确实不错。
忙乎了一天,终于把这篇文章给写完了,下面我们看下代码实现。
===================================================================
def canPartition(stick_list):
求木棍列表长度总和
s = sum(stick_list)
如果是奇数不可能分成两根等长木棍
if s % 2 != 0:
return False
我们是要看能否求出列表长度总和的一半这个值,如果可以就能够等分成两根木棍
target = int(s / 2)
length = len(stick_list)
target+1 是要从0开始到target的整个区间
dp = [[False for i in range(target + 1)] for j in range(length)]
if stick_list[0] <= target:
在第0行的对应位置标上能得到stick_list[0]长度的木棍
dp[0][stick_list[0]] = True
套用01背包模板
for i in range(1, length):
for j in range(target + 1):
i-1 区间能拼成那些数 第i个数就能拼成哪些数
dp[i][j] = dp[i - 1][j]
if stick_list[i] < j:
dp[i - 1][j - stick_list[i]] 就是看 有无一个值能够与stick_list[i]凑出j这个数
dp[i][j] = dp[i - 1][j] or dp[i - 1][j - stick_list[i]]
如果等于,表示能够拼出长度为j的木棍
elif stick_list[i] == j:
dp[i][j] = True
如果提前拼出木棍列表总长度一半的数,即(分成两根等长木棍)可提前返回结果
if dp[i][target]:
return True
返回最后位置target上的值,正确与否表示能否拼出木棍总长度一半这个数
return dp[length - 1][target]
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注:Python)
243c1008edf79.png)
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注:Python)