回朔法的思想: 回朔法的重要思想在于: 通过枚举法,对所有可能性进行遍历。 但是枚举的顺序是 一条路走到黑,发现黑之后,退一步,再向前尝试没走过的路。直到所有路都试过。因此回朔法可以简单的理解为: 走不通就退一步的方枚举法就叫回朔法。而这里回退点也叫做回朔点。
回朔关键点 通过分析发现,回朔法实现的三大技术关键点分别是:
一条路走到黑
回退一步
另寻他路
关键点的实现 那么如何才能用代码实现上述三个关键点呢?
for 循环
递归
解释如下
for循环的作用在于另寻他路: 你可以用for循环可以实现一个路径选择器的功能,该路径选择器可以逐个选择当前节点下的所有可能往下走下去的分支路径。 例如: 现在你走到了节点a,a就像个十字路口,你从上面来到达了a,可以继续向下走。若此时向下走的路有i条,那么你肯定要逐个的把这i条都试一遍才行。而for的作用就是可以让你逐个把所有向下的i个路径既不重复,也不缺失的都试一遍
递归可以实现一条路走到黑和回退一步: 一条路走到黑: 递归意味着继续向着for给出的路径向下走一步。 如果我们把递归放在for循环内部,那么for每一次的循环,都在给出一个路径之后,进入递归,也就继续向下走了。直到递归出口(走无可走)为止。 那么这就是一条路走到黑的实现方法。 递归从递归出口出来之后,就会实现回退一步。
因此for循环和递归配合可以实现回朔: 当递归从递归出口出来之后。上一层的for循环就会继续执行了。而for循环的继续执行就会给出当前节点下的下一条可行路径。而后递归调用,就顺着这条从未走过的路径又向下走一步。这就是回朔
说了这么多,回朔法的通常模板是什么呢? 递归和for又是如何配合的呢?
回朔代码模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def backward (): if (回朔点): 保存该结果 return else : for route in all_route_set : 逐步选择当前节点下的所有可能route if 剪枝条件: 剪枝前的操作 return else : 保存当前数据 self.backward() 回朔清理
这里剪枝操作指的是: 对于有些问题,你走着走着,若某种情况发生了,你就已经直到不能继续往下走了,再走也没有用了。而这个情况就被称之为剪枝条件。
而DFS就是一个最典型的回朔法的应用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution : def combinationSum (self, candidates: List [int ], target: int ) -> List [List [int ]]: if len (candidates) == 0 : return [] candidates.sort() path = [] res = [] ''' !!!重点!!! 在python中,如果传参是mutable var, 那么传参相当于引用,因此调用后,如果调用函数的内部对该传入变量进行修改,就会导致直接改变原始对象。这就是典型的privacy leak!!发生了。 例如在这个,list就是该mutable var,而如果以path或res 为传参,放在__DFS 中, 那么就相当于在__DFS内部,实际上用的都是一个物理地址下的res和path,类似于全局变量。 因此combinationSum下的局部变量path和res也在——DFS运行的过程中发生了改变。 利用这个性质,我们可以把mutable var当成传入参数,从而实现全局变量的效果。 ''' self.__DFS(candidates, target, 0 , path, res) return res ''' DFS的实现 ''' def __DFS (self, candidates, target, begin, path, res ): path = path.copy() if target == 0 : res.append(path) return for i in range (begin, len (candidates)): if target - candidates[i] < 0 : return path.append(candidates[i]) self.__DFS(candidates, target - candidates[i], i, path, res) path.pop()
排列和组合 举例子:[1,2] [2,1]
对于排列来说,这俩不同,所以下标i从0开始
对于组合来说,这俩是相同的,下标从startIndex(i+1)
去重逻辑 如果给出的数组内有重复元素,那么需要去重
先定义一个用来标识的int[] 或者 boolen[]数组,进行数层去重,统一格式!!!!注意!!!!一定要对数组进行排序Arrays.sort(nums)!!!
1 2 3 if(i>0 && nums==nums &&flag==0){ continue; }
排列注意点 跳过上一次使用过的
1 2 3 4 //上一步已经用过的直接跳过 if(flag[i]= = 1 ){ continue }
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { List<List<Integer >> res = new ArrayList<>(); LinkedList<Integer > path = new LinkedList<>(); int [] flag = new int [20 ]; public List<List<Integer >> permuteUnique(int [] nums) { Arrays.sort(nums); permuteHelper(nums); return res; } public void permuteHelper(int [] nums){ if (path .size() == nums.length){ res.add (new ArrayList<>(path )); return ; } for (int i = 0 ;i<nums.length;i++){ //去重重复元素 if (i>0 && nums[i-1 ]==nums[i] &&flag[i-1 ]==0 ){ continue ; } //上一步已经用过的直接跳过 if (flag[i]==1 ){ continue ; } path .add (nums[i]); flag[i] = 1 ; permuteHelper(nums); path .removeLast(); flag[i] = 0 ; } } }