六六力扣刷题回溯之组合总和

“持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

前言

之前小六六一直觉得自己的算法比较菜,算是一个短板吧,以前刷题也还真是三天打鱼,两台晒网,刷几天,然后就慢慢的不坚持了,所以这次,借助平台的活动,打算慢慢的开始开刷,并且自己还会给刷的题总结下,谈谈自己的一些思考,和自己的思路等等,希望对小伙伴能有所帮助吧,也可以借此机会把自己短板补一补,希望自己能坚持下去呀

题目

组合总和 III 216题

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

只使用数字1到9 每个数字 最多使用一次  返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。
复制代码

分析

其实大家可以看看这题,也是用回溯算法解决的,最近几天,六六分享的都是回溯算法的,关于回溯算法的定义啥的,可以看上面那篇文章,这边我们就直接把模版拿出来,大家就像填词一样,往里面填代码试试,

模版

回溯三部曲

  • 1.确定回溯单层逻辑

通过for循环控制当前位置(也就是当前递归层)的数字可以从1选到9,每选择一个数字(for每遍历到一个数字)我们就从目标总和中减去它的值,同时把他加入记录数字组合的集合中; 通过上一步我们就确定了第一位数字,接下来通过递归确定下一位数字,要注意的是需要更新下一层递归选择下一位数字时开始的位置,因为上一层选过的数字下一层就不能再选了; 当递归到某层时,我们发现此时已经确定了k个数字了,需要判断这k个数的和是否等于n,如果等于n,则把这次收集到的组合加入最终的结果集并终止当前层递归,准备回溯;反之,直接终止递归,准备回溯; 当我们从上一层递归中出来后,就该回溯了,即删掉最后一位的数字重新选择一个新数字;

  • 2.确定回溯函数参数以及返回值

根据上述分析,可以得知我们在每层的递归中都会更新目标总和的值、判断当前是否选够了k个数字、以及我们必须直到当前这层递归中选择数字的起点在哪里 因此参数需要:目标总和、题目给出的k、当前层递归选择数字的起始位置 因为我们将用两个全局变量记录当前组合以及最终结果集,所以不需要返回值

  • 3.确定回溯函数终止条件

在分析单层逻辑时,我们就已经得到:当我们选取了k个数字后们就一个终止当前递归返回了

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

复制代码

题解

package com.six.finger.leetcode.five;


/*
找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

只使用数字1到9
每个数字 最多使用一次 
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。
 */

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class fifteen {


    public static void main(String[] args) {

        combinationsum(9, 45);

    }

    public static List<List<Integer>> combinationsum(int k, int n) {
        List<List<Integer>> res = new ArrayList<>();

        LinkedList<Integer> path = new LinkedList<>();

        backtracking(res, path, k, n, 1);

        return res;

    }

    private static void backtracking(List<List<Integer>> res, LinkedList<Integer> path, int k, int n, int i) {

        int sum = 0;
        //第一步,递归的退出条件
        for (Integer integer : path) {
            sum = sum + integer;
        }

        if (sum > n) {
            return;
        }

        if (sum == n && path.size() == k) {
            res.add(new ArrayList<>(path));
            return;
        }
        //第二步 树的横向长度,和减枝活动

        for (int j = i; j <= 9 - (k - path.size()) + 1; j++) {
            path.add(j);
            backtracking(res, path, k, n, j + 1);
            path.removeLast();
        }


    }
}
复制代码

image.png

image.png

一看提交记录,我去太菜了,想想怎么优化下

优化

其实就是,我为啥每次都要计算path的和,改下,把这样计算

for (int j = i; j <= 9 - (k - path.size()) + 1; j++) {
    sum=sum+i;
    path.add(j);
    backtracking(res, path, k, n, j + 1,sum);
    path.removeLast();
    sum=sum-i;
}
复制代码

image.png

然后并没有什么用哦

结束

好了,今天这题就结束了,我发现连续的去练一种类型,其实这种题就慢慢的没那么难了,以前我总觉得算法很难,但是其实万事万物,都有其自然的规律,我们只能善于总结,虽然创造不出什么新的东西,但是还是可以把别人的经验学习到的,加油!!

猜你喜欢

转载自juejin.im/post/7103507614603935780