返回

算法题每日一练---第85天:组合

前端

问题

给定两个整数n和k,你的任务是找到所有可能的k个数字组合,其中每个数字都来自范围[1, n]。你可以以任何顺序返回答案。

递归回溯法:

递归回溯法是一种经典的算法,可以用来解决很多组合问题。它的基本思想是:在每次递归调用中,从当前状态出发,枚举所有可能的下一步,然后分别对这些下一步进行递归调用。当到达终止条件时,返回当前状态对应的答案。

在这个问题中,我们可以在每个递归调用中,从当前的数字开始,枚举所有可能的下一个数字。如果下一个数字合法(即在范围[1, n]内且没有重复),就将它添加到当前的组合中,然后进行下一次递归调用。当组合的长度达到k时,将它添加到答案中,然后返回。

动态规划法:

动态规划法是一种自顶向下的算法,可以用来解决很多优化问题。它的基本思想是:将问题分解成多个子问题,然后分别解决这些子问题,并保存子问题的解,以备后用。当需要解决某个子问题时,如果它的解已经存在,则直接返回;否则,就计算它的解,并保存起来,以便以后使用。

在这个问题中,我们可以定义一个二维数组dp,其中dp[i][j]表示从1到i的所有数字中选出j个数字的所有可能的组合。我们可以使用动态规划法来计算dp数组,具体步骤如下:

  1. 初始化:dp[i][0] = 1(表示从1到i的所有数字中选出0个数字的所有可能的组合只有空集)。
  2. 递推:对于i从1到n,j从1到k,我们可以使用以下公式计算dp[i][j]:
dp[i][j] = dp[i-1][j] + dp[i-1][j-1]

其中,dp[i-1][j]表示从1到i-1的所有数字中选出j个数字的所有可能的组合,dp[i-1][j-1]表示从1到i-1的所有数字中选出j-1个数字的所有可能的组合。

  1. 结果:当i = n且j = k时,dp[n][k]就等于所有可能的k个数字组合的数目。

复杂度分析:

  • 递归回溯法的时间复杂度为O(k * 2^n),因为在每个递归调用中,我们需要枚举所有可能的下一步,而下一步的个数最多为2^n。
  • 动态规划法的时间复杂度为O(n * k),因为我们需要计算dp数组中的所有元素,而dp数组的元素个数为n * k。

编程实现:

#include <vector>

using namespace std;

class Solution {
public:
    // 递归回溯法
    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> result;
        vector<int> combination;
        backtrack(1, n, k, combination, result);
        return result;
    }

    void backtrack(int start, int n, int k, vector<int>& combination, vector<vector<int>>& result) {
        if (combination.size() == k) {
            result.push_back(combination);
            return;
        }

        for (int i = start; i <= n; i++) {
            combination.push_back(i);
            backtrack(i + 1, n, k, combination, result);
            combination.pop_back();
        }
    }

    // 动态规划法
    vector<vector<int>> combine2(int n, int k) {
        vector<vector<int>> dp(n + 1, vector<vector<int>>());
        for (int i = 1; i <= n; i++) {
            dp[i].push_back({});
        }

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= k; j++) {
                dp[i][j] = dp[i-1][j];
                for (int num : dp[i-1][j-1]) {
                    if (num < i) {
                        dp[i][j].push_back(num);
                    }
                }
            }
        }

        return dp[n][k];
    }
};

总结:

组合问题是一个经典的算法问题,可以使用递归回溯法或动态规划法来解决。递归回溯法的时间复杂度为O(k * 2^n),动态规划法的时间复杂度为O(n * k)。