代码随想录算法训练营第二十四天|77.组合

LeetCode77.组合

        基本思路:回溯法的三个步骤:

        1,递归函数的返回值以及参数:在这里要定义两个全局变量,一个用来存放符合条件单一结果,一个用来存放符合条件结果的集合。

        2,回溯函数终止条件:什么时候到达所谓的叶子节点了呢?path这个数组的大小如果达到k,说明我们找到了一个子集大小为k的组合了,在图中path存的就是根节点到叶子节点的路径。

 代码随想录算法训练营第二十四天|77.组合_第1张图片

 此时用result二维数组,把path保存起来,并终止本层递归。

        3,单层搜索的过程:回溯法的搜索过程就是一个树型结构的遍历过程,在如下图中,可以看出for循环用来横向遍历,递归的过程是纵向遍历。代码随想录算法训练营第二十四天|77.组合_第2张图片

如此我们才遍历完图中的这棵树。for循环每次从startIndex开始遍历,然后用path保存取到的节点i。

回溯的基本模板:

代码随想录算法训练营第二十四天|77.组合_第3张图片

 剪枝优化

        来举一个例子,n = 4,k = 4的话,那么第一层for循环的时候,从元素2开始的遍历都没有意义了。 在第二层for循环,从元素3开始的遍历都没有意义了。

代码随想录算法训练营第二十四天|77.组合_第4张图片

图中每一个节点(图中为矩形),就代表本层的一个for循环,那么每一层的for循环从第二个数开始遍历的话,都没有意义,都是无效遍历。

所以,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置

如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了

接下来看一下优化过程如下:

  1. 已经选择的元素个数:path.size();

  2. 还需要的元素个数为: k - path.size();

  3. 在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历

为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。

举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。

从2开始搜索都是合理的,可以是组合[2, 3, 4]。

Java代码如下:

    //LeetCode 77 组合
    List> res = new ArrayList<>();
    LinkedList path = new LinkedList<>();
    public List> combine(int n, int k) {
    	combineHelper(n,k,1);
    	return res;
    }
    
    private void combineHelper(int n,int k,int startIndex) 
    {
    	if(path.size() == k) 
    	{
    		res.add(new ArrayList<>(path));
    		return;
    	}
    	for(int i = startIndex; i <= n - (k - path.size()) + 1 ; i++) 
    	{
    		path.add(i);
    		combineHelper(n,k,i + 1);
    		path.removeLast();
    	}
    }

代码随想录算法训练营第二十四天|77.组合_第5张图片

你可能感兴趣的:(算法)