LintCode 1834: Grouping Options (DP经典题)

本文探讨了一种将n个人分成m个连续组的算法问题,其中每个组的大小至少与左侧组相同。提供了三种不同的动态规划解法,详细解释了每种方法的时间复杂度和实现细节,帮助读者深入理解并解决此类问题。
  1. Grouping Options

There are n people in a row. They must all be divided into m contiguous groups from left to right. If each group must be at least as large as the group to its left, determine how many distinct ways groups can be formed. For a group to be distinct, it must differ in size for at least one group when sorted ascending. For example, [1, 1, 1, 3] is distinct from [1, 1, 1, 2] but not from [1, 3, 1, 1].

Example
Input:
8
4
Output: 5
Explanation: [1, 1, 1, 5], [1, 1, 2, 4], [1, 1, 3, 3], [1, 2, 2, 3], [2, 2, 2, 2]
Notice
1≤n,m≤200

解法1:
这个思路其实不太好想。参考的网上的答案。时间复杂度O(m^2 * n)。
dp[i][j] 表示前i个人分成j组的分法。
那么dp[i][i], i=1…n肯定是1。
对于dp[i][j]而言,我们可以把这j个组每个组分一个,那么剩下i-j个人,这i-j个人可以全部分配到最后1个组(对应dp[i-j][1]),也可以全部分配到最后2个组(对应dp[i-j][2]),…,也可以全部分配到全部的j个组(对应dp[i-j][j])。所以要用再用一个循环把这些方案都加起来就是dp[i][j]。
注意:

  1. for i的循环可以从1开始,也可以从2开始。但从1开始没有必要,因为j<i,那么j==0。
  2. for j的循环可以是j<=i,因为我们知道dp[i][i]==1,所以用j<i就可以了。
class Solution {
public:
    /**
     * @param n: the number of people
     * @param m: the number of groups
     * @return: the number of grouping options
     */
    long long groupingOptions(int n, int m) {
        if (n < m) return 0;
        //dp[i][j] is the number of options that i people are divided into j groups    
        vector<vector<long long>> dp(n + 1, vector<long long>(m + 1));
        for (int i = 1; i <= m; ++i) {
            dp[i][i] = 1;
        }

        for (int i = 2; i <= n; ++i) {
            for (int j = 1; j <= min(i, m); ++j) { 
                for (int k = 1; k <= j; ++k) {
                    dp[i][j] += dp[i - j][k];                
                }
            }
        }
        
        return dp[n][m];
    }
};

解法2:DP。时间复杂度O(m+n)。
思路是dp[i][j]可以分为2部分: 第1个数是1的和第1个数不是1的。
比如说n=8, m=4,那第1个数是1的情况就包括{1,1,1,5}, {1,1,2,4},{1,1,3,3},{1,2,2,3},第1个数不是1的情况就是{2,2,2,2}。
那实际上
1)第1个数是1的情况就等价于dp[7][3],即把第1个数拿掉后的情况,{1,1,5}, {1,2,4}, {1,3,3}, {2,2,3}。
2)第1个数不是1的情况就是将8分为4个数,但第1个数不是1的情况。等价于dp[4][4],即因为每个元素都>1,所以每个元素减去1,然后求dp[4][4]即可。
怎么理解呢?比如说第2种情况是{2,3,4,5},n=14,那么,我们可以给4个数每个数先分配一个1,剩下10在4个数里面分配,这就是dp[10][4]。

class Solution {
public:
    /**
     * @param n: the number of people
     * @param m: the number of groups
     * @return: the number of grouping options
     */
    long long groupingOptions(int n, int m) {
        if (n < m) return 0;
        //dp[i][j] is the number of options that i people are divided into j groups    
        vector<vector<long long>> dp(n + 1, vector<long long>(m + 1));
        for (int i = 1; i <= m; ++i) {
            dp[i][i] = 1;
        }
        for (int i = 2; i <= n; ++i) {
            for (int j = 1; j <= min(i, m); ++j) { 
                dp[i][j] = dp[i - 1][j - 1] + dp[i - j][j];
            }
        }
        return dp[n][m];
    }
};

解法3:还是DP。
dp[i][j][k] 表示i拆除j个数,最后一个数是k有多少种方法
dp[i][j][k] = sum(dp[i - k][j - 1][x]), 1<=x<=k。
时间复杂度O(n^3 * m),非常慢。
时间复杂度计算如下:i from 1 to n, j from 1 to m, k from 1 to n, x from 1 to k。
TBD

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值