脑洞大开的“小排列、大组合”算法

给定一个无重复元素的数组 candidates 和一个目标数 target,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。 !!!可以被重复选取

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。

示例 1:

输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
  [7],
  [2,2,3]
]

示例 2:

输入: candidates = [2,3,5], target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

在这里插入图片描述
咋一看这题,还挺简单,扳扳手指头就算出来了
要用代码写出这题,还真是有点不简单。。。

分析思路:

用 目标数 - 列表中的任意数, 然后判断结果是否等于0即可
假设
candidates = [2,3,6,7]
target = 7

黑色:继续下一轮计算
红色:目标数据
蓝色:剔除数据

轮次 2 3 6 7
A 7-2=5 7-3=4 7-6=1 7-7=0

因为可以被重复选取

轮次 2 3 6 7
A-2 5-2=3 5-3=2 5-6=-1 5-7=-2
A-3 4-2=2 4-3=1 4-6=-2 4-7=-3
A-6 1-2=-1 1-3=-2 1-6=-5 1-7=-6

凡是计算结果大于0的,继续下一轮计算

轮次 2 3 6 7
A-2-2 3-2=1 3-3=0 3-6=-3 3-7=-4
A-2-3/A-3-2 2-2=0 2-3=-1 2-6=-4 2-7=-5
A-3-3 1-2=-1 1-3=-2 1-6=-5 1-7=-6

最后一轮

轮次 2 3 6 7
A-2-2-2 1-2=-1 1-3=-2 1-6=-5 1-7=-6

结束~
通过红色轨迹标记,得到最终结果:
[7]
[2, 2, 3]
[2, 3, 2]
[3, 2, 2]
排序踢重之后结果
[7]
[2, 2, 3]

通过上面的分析,大家应该可以看出一些规律,有想法的小伙伴,可以不看最终代码,自己尝试写个看看?

完整版代码逻辑

import java.util.*;

/**
 * @author yanghao
 * @version LeetCodeTest.java, v 0.1 2019-10-11 19:13
 */
public class LeetCodeTest {

    public static void main(String[] args){
        LeetCodeTest test = new LeetCodeTest();

        int[] candidates = new int[]{7,3,2};
        int target = 18;
        System.out.println(test.leetCode39(candidates, target));

    }
    public List<List<Integer>> leetCode39(int[] candidates, int target) {

        //排序
        Arrays.sort(candidates);

        List<Integer> childList;
        List<List<Integer>> list = new ArrayList<>();

        //本轮要循环的数据
        Map<String, Integer> oldMap = new HashMap<>();
        //下一轮要循环的数据,下一轮开始之前,把数据赋值给oldMap,并清空
        Map<String, Integer> newMap = new HashMap<>();

        //第一轮
        for(int first : candidates){
            if(target - first == 0){
                //存放起来(对应列表红色数据)
                childList = new ArrayList<>();
                childList.add(first);
                list.add(childList);

            }else if(target - first > 0){
           		//放到下一轮(对应列表黑色数据)
                oldMap.put(first + "", target - first);

            }

        }

        //第二轮 到 结束
        while (oldMap.size() > 0){
            //每一轮循环结果大于0的
            for(Map.Entry<String, Integer> entry : oldMap.entrySet()) {
                //依次递减
                for(int index : candidates){

                    target = entry.getValue() - index;

                    if(target == 0){
                        //存放起来(对应列表红色数据)
                        childList = splitInt(entry.getKey() + "_" + index);
                        list.add(childList);

                    }else if(target > 0){
                        //放到下一轮(对应列表黑色数据)
                        newMap.put(entry.getKey() + "_" + index, target);

                    }else{
                        //因为排过序,遇到小于0之后,后面的减法都会小于0,就可以结束本次循环了
                        break;
                    }

                }


            }

            //newMap --> oldMap
            oldMap.clear();
            oldMap.putAll(newMap);
            newMap.clear();

        }

        //踢重
        Set<List<Integer>> setList = new HashSet<>(list);
        list = new ArrayList<>(setList);

        return list;

    }

    /**
     * 1_2_3  -->   [1,2,3]
     */
    private List<Integer> splitInt(String key){

        String[] strs = key.split("_");
        Integer[] ints = new Integer[strs.length];

        for(int i = 0; i < strs.length; i ++){
            ints[i] = Integer.valueOf(strs[i]);
        }

        //排序,方便后面踢重
        Arrays.sort(ints);

        List<Integer> list = Arrays.asList(ints);
        return list;

    }


}

运行结果:

[[2, 2, 2, 2, 3, 7], [3, 3, 3, 3, 3, 3], [2, 2, 2, 3, 3, 3, 3], [2, 2, 2, 2, 2, 2, 3, 3], [2, 2, 7, 7], [2, 3, 3, 3, 7], [2, 2, 2, 2, 2, 2, 2, 2, 2]]

此代码用了最常规思路去写的,效率和内存消耗都处于中等水平,还可以通过递归的代码去实现,小伙伴们可以自己去研究一下~

发布了39 篇原创文章 · 获赞 58 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43968234/article/details/102516079