上课老师出了一道题,任意交换两个列表的元素,使差值最小,是一道华为的面试题。
我想不出来, 百度了一下,本质上,是背包问题的一个变种,然后我看懂了背包问题的原理,并阅读了别人的C++代码,然后按照自己的理解,写出了Python的代码。
关于背包问题的理解,我推荐一个作者,写的极好,我就是在这篇文章的指导下,花了5个小时,理解的。写出我理解的时间,是给那些看了1个小时,就垂头丧气的人,一点信心,坚持住,一定能理解的。
很多人或许会觉得理解这道题,话这么久不值得,而我认为,虽然这道题我花了5个小时,但是下一次,再遇到同等难度的题,我只要一两个小时了,这是逻辑上的提升,终生受用。
再说说我理解上的瓶颈,m[ i ][ j ] = m[ i - 1][ j - w[ i ] ] + v [ i ],这句话理解时,一定要把m[ i - 1][ j - w[ i ] ]的意思读出来,大声读出来,叫做面对第 i-1 个变量时,容量为 j - w[ i ]的背包,所有拥有的最大价值。
"""
整体思路:
要使两个列表的差最小,也就是两个列表最接近;
首先,将两个列表合并为一个列表li,总值为sum;
从li中取出一些元素,组成表li1,保证sum(li1)的值最接近sum/2,剩下元素组成列表li2,这样
sum(li2)也最接近sum/2,这样就使得两个表最接近;
"""
li1, li2 = [1,2,3,4,5,6,7,9], [2,3,4,5,6,10]
li = li1 + li2
# 在开头加一个0,保持索引的对应性,即索引为1时,拿到的是第一个元素
li.insert(0,0)
# 求得两个表的总值
SUM = sum(li1) + sum(li2)
# c为待接近的sum/2的值,这里用int,因为列表里全是整数,小数没有意义
# 列表里总元素个数为n,这里没有算上插入的0
c = int(SUM/2)
n = len(li)-1
# li1最开始的容量为sum/2,每加入一个元素,容量要减去对应的值
# 建立一个二层列表,m[i][j]表示,面对li中第i个元素,容量为j时,可以取得的最大值
m = []
for i in range(n+1):
m.append([])
for j in range(c+1):
m[i].append(0)
# 面对第i个元素
# 容量j<li[i]时,无法放入该元素,只能选择不放,m[i][j] = m[i-1][j]
# 容量j>li[i]时,可以考虑是否放入该元素
# 如果放入,则 m[i][j] = m[i-1][j - li[i]] + li[i]
# 如果不放入,则 m[i][j] = m[i-1][j]
# 根据m的特性,选择其中较大的那一个 max(m[i-1][j], m[i-1][j-li[i]] + li[i])
for i in range(1,n+1):
for j in range(1,c+1):
if j >= li[i]:
m[i][j] = max(m[i-1][j], m[i-1][j-li[i]] + li[i])
else:
m[i][j] = m[i-1][j]
# 需要根据m[n][c]回溯,寻找li1中取了哪些元素
# x: 如果第i个元素放入了li1,则x[i] = 1,否则x[i] = 0
# y: 是一个从n 到 0 的序列,用于回溯
# w: w是c的复制,保持不修改原c
x, y ,w = [], [], c
for i in range(n+1):
x.append(0)
y.append(i)
y.reverse()
for i in y:
if m[i][w] == m[i-1][w]:
x[i] = 0
else:
x[i] = 1
w -= li[i]
# 将对应的元素加入li1表和li2表,并打印结果
li1 = []
li2 = []
for i in range(1, n+1):
if x[i] == 1:
li1.append(li[i])
else:
li2.append(li[i])
print(li1)
print(sum(li1))
print(li2)
print(sum(li2))