算法题题解记录——4. 数字分组求和

169 阅读4分钟

题目

www.marscode.cn/practice/r3…

image.png

关键词

动态规划滚动数组

思路

满足以下3个条件考虑用动态规划:

  1. 最优子结构:原问题的最优解可通过子问题的最优解获得;
  2. 无后效性:子问题的解一旦确定不会收到后续决策的影响;
  3. 子问题重叠:不同的子问题具有共同的子问题,每个子问题都是独立的;

显然该题满足以上条件。

动态规划的求解通常分为以下步骤:

  1. 将原问题划分为若干 阶段,每个阶段对应若干个子问题,提取这些子问题的特征(称之为 状态);
  2. 寻找每一个状态的可能 决策,或者说是各状态间的相互转移方式(用数学的语言描述就是 状态转移方程)。
  3. 按顺序求解每一个阶段的问题。

解: 记dp1[i]为前i项和为奇数的可能数,dp2[i]为前i项和为偶数的可能性。c1为当前项奇数个数,c2为当前项偶数个数。

则状态转移方程:

dp1[i]=dp1[i1]c2+dp2[i1]c1dp1[i] = dp1[i - 1] * c2 + dp2[i - 1] * c1
dp2[i]=dp2[i1]c2+dp1[i1]c1 dp2[i] = dp2[i - 1] * c2 + dp1[i - 1] * c1

代码

def solution(numbers):
    # Please write your code here
    dp1 = [0] * len(numbers)  # 前n项各取一个数和为奇数可能数
    dp2 = [0] * len(numbers)  # 前n项各取一个数和为偶数的可能数
    for i, element in enumerate(numbers):
        c1 = 0  # 当前数字中奇数个数
        c2 = 0  # 当前数字中偶数个数
        while element > 0:
            cur = element % 10
            if cur % 2 == 0:
                c2 += 1
            else:
                c1 += 1
            element //= 10

        if (i == 0):
            dp1[0] = c1
            dp2[0] = c2
        else:
            dp1[i] = dp1[i - 1] * c2 + dp2[i - 1] * c1
            dp2[i] = dp2[i - 1] * c2 + dp1[i - 1] * c1

    return dp2[len(dp2) - 1]


if __name__ == "__main__":
    #  You can add more test cases here
    print(solution([123, 456, 789]) == 14)
    print(solution([123456789]) == 4)
    print(solution([14329, 7568]) == 10)

复杂度

时间复杂度: O(n * m),其中 n 是 numbers 列表的长度,m 是每个数字字符串的平均长度。

空间复杂度: O(n) 使用了两个长度为 n 的数组 dp1 和 dp2

优化:利用滚动数组优化空间复杂度

观察到,dp1[i]dp2[i] 的值仅依赖于前一项(dp1[i-1]dp2[i-1])。因此,实际上我们并不需要存储所有的 dp 状态,而只需要保持上一项的状态即可。因此,我们可以将空间复杂度从 O(n) 降低到 O(1),只使用常量空间来保存前一项的状态。

代码

def solution(numbers):
    # Please write your code here
    dp1 = 0  # 前n项各取一个数和为奇数可能数
    dp2 = 0  # 前n项各取一个数和为偶数的可能数
    for i, element in enumerate(numbers):
        c1 = 0  # 当前数字中奇数个数
        c2 = 0  # 当前数字中偶数个数
        while element > 0:
            cur = element % 10
            if cur % 2 == 0:
                c2 += 1
            else:
                c1 += 1
            element //= 10

        if (i == 0):
            dp1 = c1
            dp2 = c2
        else:
            temDp1 = dp1 * c2 + dp2 * c1
            temDp2 = dp2 * c2 + dp1 * c1
            dp1 = temDp1
            dp2 = temDp2

    return dp2


if __name__ == "__main__":
    #  You can add more test cases here
    print(solution([123, 456, 789]) == 14)
    print(solution([123456789]) == 4)
    print(solution([14329, 7568]) == 10)

总结

  1. pythona // b(地板除)即js中的 Math.floor(a / b)//= (地板除赋值);
  2. pyhon中没有 ++(自增操作符) 和 --(自减操作符);
  3. python中, 通过索引赋值时要注意,如果索引不在数组范围内会报IndexError错误,因此往往需要提前声明好数组长度,或者使用 append 方法;
arr = []
arr[0] = 1 # IndexError: list assignment index out of range
  1. 滚动数组技术是一种在动态规划中优化空间复杂度的常用技巧。通过只保留当前状态和前一状态,可以有效地将空间复杂度从 O(n) 降低到 O(1)。这种技术常用于解决“前一项”依赖的动态规划问题,如斐波那契数列、最大子数组和、背包问题等。