回溯算法以及剪枝问题理解

回溯算法以及剪枝问题理解

  • 关于递归的简单回顾
    • 关于力扣第77题:组合

关于递归的简单回顾

程序调用自身的编程技巧称为递归。

举例说明一下:

public void Recursion(int n){
	if(n>0){
		Recursion(n-1);
        printf("%d\n",n);
	}
}

当n=2的时候首先执行Recursion(2),接着执行Recursion(1),最后结果就是1和2。
递归的调用实质就像是一个堆栈,当我们执行Recursion(2)的时候发现无法得到结果并且接着执行Recursion(1),此时就相当于把Recursion(2)存入栈中,然后执行完Recursion(1)的再从栈中取出Recursion(2)执行得到结果。

如果是for循环加上递归呢

public void Recursion(int n){
	for(int i=1;i<=3;i++)
    {
        if(n>0)
        {
             Recursion(n-1);
             printf("%d\n",n);
        }
    }
}

对于这种问题,我个人认为一个很好的办法就是去分层理解。

Recursion(2)-> 输出次数:3^1
调用3次Recursion(1)
输出三次2

每一次的Recursion(1)-> 输出次数:3^2
调用3次Recursion(0)
输出三次1
回溯算法以及剪枝问题理解_第1张图片输出结果为111211121112

关于力扣第77题:组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

分析:
首先可以看出是一个组合问题
对于例题来看
1.如果组合里有 1 ,那么需要在 [2, 3, 4] 里再找 1 个数;
2.如果组合里有 2 ,那么需要在 [3, 4] 里再找 1数。注意:这里不能再考虑 1,因为包含 1 的组合,在第 1 种情况中已经包含。

所以这里使用回溯算法,首先画出递归树
回溯算法以及剪枝问题理解_第2张图片根据树分析我们可以先在一个循环中依次取出1.2.3.4
然后通过递归操作继续取数,只需注意在取下一个数时要舍去不需要的数

public class Solution {

    public List> combine(int n, int k) {
        List> res = new ArrayList<>();
        if (k <= 0 || n < k) {
            return res;
        }
        // 我们取数是从1开始取
        Deque path = new ArrayDeque<>();
        dfs(n, k, 1, path, res);
        return res;
    }

    private void dfs(int n, int k, int begin, Deque path, List> res) {
        // 当path的值等于k时说明当前以及取满递归结束
        if (path.size() == k) {
            res.add(new ArrayList<>(path));
            return;
        }

        // 从1开始取数
        for (int i = begin; i <= n; i++) {
            // 将取得的第一个数存入path
            path.addLast(i);
            // 开始第二轮取数,取数从i+1开始取
            dfs(n, k, i + 1, path, res);
            // 假如当我们取得第一组数1.2时后第一层递归结束,开始下一层获取3来组成1.3。但是此时path里面以及有1.2所以要删除2这个数据。
            path.removeLast();
        }
    }
}

初步完成后发现代码可以进行优化(剪枝操作)
例如这个立体中当我们取的第一个数为4的时候以及没有意义了,因为第二个数无论取什么都已经重复了。

继续分析:如果n=8,k=5时,从5开始搜索就没有意义了,因为把5选上后面6,7,8也组不成5个数了。
所以:
当path.size()==1时,还需要选4个数,最大搜索起点为5
当path.size()==2时,还需要选3个数,最大搜索起点为6
当path.size()==3时,还需要选2个数,最大搜索起点为7
当path.size()==4时,还需要选1个数,最大搜索起点为8

根据上面可以看出n = 还需要选择的个数 + 最大搜索起点 + 1
所以我们可以把循环的上界限制为n - 还需选择的个数 + 1
即 for循环修改为i < n - (k - path.size()) + 1;

for (int i = begin; i <= n - (k - path.size()) + 1; i++) {
            // 将取得的第一个数存入path
            path.addLast(i);
            // 开始第二轮取数,取数从i+1开始取
            dfs(n, k, i + 1, path, res);
            // 假如当我们取得第一组数1.2时后第一层递归结束,开始下一层获取3来组成1.3。但是此时path里面以及有1.2所以要删除2这个数据。
            path.removeLast();
        }

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