算法应对电商的各种满减活动

Posted JavaEdge.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法应对电商的各种满减活动相关的知识,希望对你有一定的参考价值。

动态规划roadmap

动态规划适于求解最优问题,如求最大值、最小值等。可显著降低时间复杂度,提高代码的执行效率。
难点和递归类似,求解问题的过程不太符合人类常规思维。

  • 本文会先通过两个非常经典的动态规划问题模型,展示动态规划存在的意义及动态规划解法是如何演化的,学会后即可举一反三。
  • 《动态规划理论》,我会总结动态规划适合解决的问题的特征,以及动态规划解题思路。除此之外,我还会将贪心、分治、回溯、动态规划这四种算法思想放在一起,对比分析它们各自的特点以及适用的场景
  • 《动态规划实战》,我会教你应用第二节讲的动态规划理论知识,实战解决三个非常经典的动态规划问题,加深你对理论的理解

经典案例

0-1背包

不同重量、不可分割的物品,选择一些装入背包。
在满足背包最大重量限制的前提下,求背包中物品总重量的max?

可用回溯穷举搜索所有可能的装法,然后找出最大值。但回溯算法时间复杂度太高,为指数级:

假设:

  • 背包最大承载重量9
  • 有5个不同物品
  • 每个物品重量2,2,4,6,3

回溯求解过程,用递归树:

每个节点表示一种状态(i, cw),如(2,2)决策第2个物品是否装入背包,在决策前,背包中物品的总重量是2。

发现有些子问题求解重复,如f(2, 2)和f(3,4)都被重复计算两次。
可借助“备忘录”,记录已计算好的f(i, cw),当再次计算到重复的f(i, cw)的时候,直接从备忘录中取出来用,避免冗余计算。

跟动态规划的执行效率基本无差。

把整个求解过程分为n个阶段:

  • 每个阶段会决策一个物品是否放到背包中
  • 每个物品决策(放入或者不放入背包)完后,背包中的物品的重量会有多种情况,即会达到多种不同的状态,对应到递归树中,就是不同节点。

把每层重复的状态(节点)合并,只记录不同状态,然后基于上一层状态集合,推导下一层状态集合。可通过合并每一层重复状态,保证每层不同状态的个数都不会超过w个(w表示背包的承载重量)。这就避免每层状态个数的指数级增长。

二维数组states[n][w+1],记录每层可达到的不同状态:

  • 第0个物品的重量是2,决策完后,对应背包两种状态,背包物品总重量0或2,即states[0][0]=truestates[0][2]=true
  • 第1个物品的重量也是2,基于之前背包状态,在这个物品决策完后,不同状态有3个,背包中物品总重量分别是0(0+0),2(0+2 or 2+0),4(2+2),即states[1][0]=truestates[1][2]=truestates[1][4]=true
  • 考察完所有物品后,states状态数组计算完毕

0表false,1表true,只需在最后一层,找一个值为true的最接近w(这里是9)的值,即背包中物品总重量的最大值。

动态规划这个名字的由来:把问题分解为多个阶段,每个阶段对应一个决策。记录每个阶段可达的状态集合(去掉重复的),然后通过当前阶段的状态集合,推导下一个阶段的状态集合,动态地往前推进。

回溯算法解决这个问题时间复杂度 O ( 2 n ) O(2^n) O(2n),那动态规划呢?
耗时最多就是代码中的两层for循环,所以时间复杂度 O ( n ∗ w ) O(n*w) O(nw)
理论上,指数级肯定要比O(n*w)高很多,如有10000个物品,重量分布1~15000,背包可承载总重量30000:

  • 回溯算法解决,用具体的数值表示出时间复杂度,就是 2^10000
  • 动态规划解决 10000 ∗ 30000 10000*30000 1000030000
    动态规划执行效率较高,但需额外申请一个 n ∗ ( w + 1 ) n*(w+1) n(w+1)二维数组,空间消耗较多。空间换时间。

实际上,只需一个大小为 w + 1 w+1 w+1 的一维数组。

0-1背包升级

引入物品价值。
对一组不同重量、不同价值、不可分割的物品,选择某些物品装入背包,在满足背包最大重量限制的前提下,背包中可装入物品的总价值最大是多少呢?

依旧可回溯:

递归树中,每个节点表示一个状态。现在需3个变量(counter, currentWeight, currentValue)表示一个状态。

有几个节点的counter和currentWeight完全相同,如f(2,2,4)和f(2,2,3)。
背包物品总重量一样,f(2,2,4)这种状态对应物品总价值更大,可舍弃f(2,2,3)这种状态,只需沿着f(2,2,4)继续往下决策。

即对于(counter, currentWeight)相同的不同状态,只需保留cv最大的继续递归处理。

用回溯算法,就没法再用“备忘录”了。需换思路,动态规划是不是可以呢?
把整个求解过程分为n个阶段:

  • 每个阶段会决策一个物品是否放到背包中
  • 每个阶段决策完后,背包中的物品的总重量以及总价值,会有多种情况,即会达到多种不同的状态

用一个二维数组states[n][w+1],记录每层可达到的不同状态,当前状态对应最大总价值。
每层中(i, cw)重复的状态(节点)合并,只记录cv值最大的状态,然后基于这些状态来推导下一层的状态。

  • 时间复杂度是O(nw)
  • 空间复杂度也是O(nw)
    空间复杂度也可优化

应对满减

凡电商,必有促销,如双十一最常见的“满200元减30元”促销活动。
现假设你的购物车有n个商品,希望从中选几个天命物品,凑足满减条件前提下,让选出的商品价格总和最大程度接近满减条件(200元),极限省钱。

要高效解决此类问题,就需动态规划(Dynamic Programming)。

类似0-1背包,只是“重量”换成了“价格”。

购物车中有n个商品。针对每个商品都决策是否购买。每次决策之后,对应不同的状态集合。
用一个二维数组states[n][x],记录每次决策之后所有可达的状态。

0-1背包找的是≤w的最大值,x就是背包的最大承载重量w+1。
现在要找的是≥200(满减条件)的值中最小的,所以不能设置为200+1。
若要购买的物品的总价格超过200太多,如1000,那这个羊毛“薅”得就没有太大意义了,可限定x值为1001。

不过,这个问题不仅要求≥200的总价格中的最小的,还要找出这个最小总价格对应都要购买哪些商品。可利用states数组,倒推出这个被选择的商品序列。

重点看后半部分,如何打印出选择购买哪些商品呢?
状态(i, j)只可能从

  • (i-1, j)
    states[i-1][j]==true,可达,说明没有购买第i个商品
  • (i-1, j-value[i])
    states[i-1][j-value[i]]==true,说明购买了第i个商品。从中选择一个可达的状态(如果两个都可达,就随意选择一个),然后,继续迭代地考察其他商品是否有选择购买。

以上是关于算法应对电商的各种满减活动的主要内容,如果未能解决你的问题,请参考以下文章

拼多多为何没有购物车功能

开发经验电商满减金额不准解决方案

开发经验电商满减金额不准解决方案

算法题解:动态规划解0-1背包问题

图解算法-怎么用动态规划解决0-1背包问题

从算法看背包问题