背景:
目前c端用户下单,对应的单位是件。也就是一个用户A可能购买15件(只)口红,另外一个用户B购买5只口红。
然后后台B端进货的时候,对应的单位是箱,箱规可能是5件/箱,10件/箱,20件/箱。
为了确保用户购买到的商品全部来自原同一个箱规。即用户A的15只口红不可以来自5件/箱,10件/箱 这两种箱规的组合。
又要考虑到价格最低(前提:20件/箱 单规格价 一定低于 5件/箱的单规格价),所以A可以和B用户组成一箱购买20件/箱 这种规格。
算法思路:
需要将这一批用户的所有订单想象成一个长度为n的正整数无序列表list,箱规组合则为一个固定数a,
然后找出无序列表中所有的组合,组合中每个数只能用一次,这些组合的和是a的整数倍,并记录这些组合。
思路一:
求出无序列表中所有的子集,然后通过判断子集中的数没有被使用过,且子集的和可以整除a,并记录这些数字,以及组合。
求解的方法有:1、递归。2、利用二进制运算
优点:可以罗列所有的可能性,并尝试凑整,不会遗漏任何情况
弊端:一个列表的所有子集为2^n次方,如果用for循环遍历,当n大于64的时候,已经是一个非常大的数字,非常影响性能。
而且C端用户参与人数超过64这种场景感觉非常多,甚至可能超过1000人。如果超过1000人,系统无法运算。
代码:只展示核心思想,如何获取所有可能组合
public static List<List<Bean>> getAllList(List<Bean> list) {
List<List<Bean>> result = new ArrayList<>();
//子集的个数为2的n次
long n = (long) Math.pow(2, list.size());
List<Bean> combine;
for (long l = 0L; l < n; l++) {
combine = new ArrayList<Bean>();
for (int i = 0; i < list.size(); i++) {
if ((l >>> i & 1) == 1)
combine.add(list.get(i));
}
result.add(combine);
}
return result;
}
思路二:
通过取余的思路做。假设规格数为a,那么所有数就是a*n+b,b<a。然后通过b把所有的数放到一个map中。key为b,value为List,list保存商品数量。
1、当key1+key2=a时,让商品数量一一匹配。
2、然后剩余的数,即key相同的数,看他们能都自行匹配。
3、对剩下的数在用思路一解决。这时候数的数量应该是大大降低了
代码:
/**
*
* @param userOrderNumList
* @param specNumList key-规格数,value-库存
*/
public static void getResult(List<Integer> userOrderNumList, Map<Integer,Integer> specNumList){
//未完成的订单,浅拷贝
List<Integer> unFinishList = userOrderNumList;
//已完成的订单
List<Integer> finishList = new ArrayList<>();
for (int i = 0; i < specNumList.size()-1; i++) {
//获取规格数
Set<Integer> specNum = specNumList.keySet();
for (int j = 0; j < userOrderNumList.size()-1; j++) {
if (unFinishList.size()==0){
break;
}
//整除,两两相加整除
compareStart(unFinishList, finishList, userOrderNumList.get(j));
}
}
}
public static void compareStart(List<Integer> unFinishList,List<Integer> finishList,int specNum){
Map<Integer, List<Integer>> map = new HashMap<>();
for (int i = 0; i < unFinishList.size(); i++) {
Integer integer = unFinishList.get(i);
int b = integer % specNum;
setMap(map, b, integer);
}
//余数为0的都可以整除
finishList.addAll(map.get(0));
//余数不为0的,相互匹配
int times = (specNum - 1) / 2;
for (int i = 1; i <= times; i++) {
compare(map, i, specNum, finishList);
}
//剩余所有的数据匹配
compare2(map, specNum, finishList);
//取出剩余无法匹配的数据,塞到unFinishList
setUnFinishList(map, unFinishList);
}
public static void setUnFinishList(Map<Integer, List<Integer>> map, List<Integer> unFinishList){
unFinishList = new ArrayList<Integer>();
for(Map.Entry<Integer, List<Integer>> entry : map.entrySet()){
List<Integer> mapValue = entry.getValue();
unFinishList.addAll(mapValue);
}
}
//剩余所有的数据匹配
public static void compare2(Map<Integer, List<Integer>> map,int specNum, List<Integer> finishList){
for(Map.Entry<Integer, List<Integer>> entry : map.entrySet()){
Integer mapKey = entry.getKey();
List<Integer> mapValue = entry.getValue();
//TODO 对mapValue从大到小排序
int a = mapValue.size() / specNum;
int b = mapValue.size() % specNum;
if (a >0){
finishList.addAll(mapValue.subList(0, a*specNum-1));
if (b>0){
map.put(mapKey, mapValue.subList(a*specNum, mapValue.size()));
}
}
//保证剩下的数和 余数的乘积 是 规格的倍数
mapValue = map.get(mapKey);
a = mapValue.size() * mapKey / specNum;
b = mapValue.size() * mapKey % specNum;
// if (a>0 ){
// if (&& b==0)
// finishList.addAll(mapValue);
// map.remove(mapKey);
// }
//
// if ()
}
}
/**
* 取出无法匹配的数据
* @param map
* @param key
* @param specNum
* @param finishList
*/
public static void compare(Map<Integer, List<Integer>> map, Integer key,int specNum, List<Integer> finishList){
List<Integer> list1 = map.get(key);
List<Integer> list2 = map.get(specNum - key);
if (list1.size() == list2.size()){
finishList.addAll(list1);
finishList.addAll(list2);
map.remove(key);
map.remove(specNum - key);
return;
}
//TODO 对list1,list2做从大到小排序
if (list1.size() > list2.size()){
finishList.addAll(list2);
finishList.addAll(list1.subList(0, list2.size()-1));
map.remove(key);
map.put(specNum - key, list1.subList(list2.size()-1, list1.size()));
return;
}
if (list1.size() < list2.size()){
finishList.addAll(list1);
finishList.addAll(list2.subList(0, list1.size()-1));
map.remove(specNum - key);
map.put(key, list2.subList(list1.size()-1, list2.size()));
return;
}
}
public static void setMap(Map<Integer, List<Integer>> map, Integer value,Integer key){
if (null == map){
return;
}
List<Integer> list = map.get(key);
if (CollectionUtils.isEmpty(list)){
List<Integer> result = new ArrayList();
result.add(value);
map.put(key, result);
return;
}
List<Integer> result = map.get(key);
result.add(value);
map.put(key, result);
return;
}
思路三:
用递归思想,
1、让无序列表从第一个数开始依次加上下一个数,判断数的和能否整除规格a,如果不能整除则继续加下面一个数,如果能整除则在列表中剔除这几个数,然后从头开始循环,因为此时数的长度变动了,组合可能会变动。
2、如果一轮下来没有任何数满足条件,则开始第二轮循环,循环从第二个数开始,依次加上下一个数。判断数的和能否整除规格a,如果不能整除则继续加下面一个数,如果能整除则在列表中剔除这几个数,然后从头开始循环,此时的循环的第一个数重新回到列表的最前面那个数,因为此时列表的长度变动了,组合可能会变动。
3、每次想加前,先判断这个数是否被使用过。
4、最后对剩下的数用思路一解决。这时候数的数量应该是大大降低了
代码:
public List<List<Bean>> combinationSum(List<Bean> listOriginal, int specNum) {
if (listOriginal == null || listOriginal.size() == 0)
return list;
dfs(listOriginal, specNum);
// System.out.println(Arrays.toString(finishList.toArray()));
// System.out.println(Arrays.toString(list.toArray()));
for (int i = 0; i < list.size(); i++) {
List<Bean> beans = list.get(i);
System.err.println(Arrays.toString(beans.toArray()));
}
System.err.println("-----------");
for (int i = 0; i < listOriginal.size(); i++) {
Bean bean = listOriginal.get(i);
if (!finishList.contains(bean)){
System.err.println(bean.toString());
}
}
// System.out.println(Arrays.toString(listOriginal.toArray()));
return list;
}
private void dfs(List<Bean> listOriginal, int specNum) {
// int sum = getSum(ls);
// if (sum / specNum > 1 && sum % specNum == 0) {
// list.add(ls);
// return;
// }
for (int i = 0; i < listOriginal.size(); i++) {
if (checkExist(finishList, listOriginal.get(i))){
// System.err.println("1---exist" + listOriginal.get(i).getNum());
continue;
}
int index=i;
if (index == listOriginal.size()){
return;
}
boolean result = dfs(listOriginal, new ArrayList<>(), specNum, index);
while (result){
if (flag){
index = 0;
flag=false;
}
result = dfs(listOriginal, new ArrayList<>(), specNum, index);
}
}
}
private boolean dfs(List<Bean> listTemp, List<Bean> lastTotal, int specNum, int index){
for (int i = index; i < listTemp.size(); i++) {
if (checkExist(finishList, listTemp.get(i))){
// System.err.println("exist" + listTemp.get(i).getNum());
continue;
}
int sum = getSum(listTemp.get(i), lastTotal);
if (sum/specNum>0 && sum%specNum==0){
List<Bean> finishListList = new ArrayList<>();
lastTotal.add(listTemp.get(i));
finishListList.addAll(lastTotal);
list.add(finishListList);
finishList.addAll(finishListList);
flag=true;
return true;
}else if (sum/specNum == 0 || sum%specNum > 0){
if (i >= listTemp.size()){
return false;
}
lastTotal.add(listTemp.get(i));
return dfs(listTemp, lastTotal, specNum, i+1);
}
}
return false;
}
private int getSum(Bean now, List<Bean> lastTotal){
Integer integer = StreamUtils.sumInteger(lastTotal, Bean::getNum);
return integer+now.getNum();
}
private boolean checkExist(List<Bean> listTemp, Bean temp){
int id = temp.getId();
for (int i = 0; i < listTemp.size(); i++) {
int id1 = listTemp.get(i).getId();
if (id == id1){
return true;
}
}
return false;
}
bean代码:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Bean {
private int num;
private int id;
}