[原题链接](二分数字组合 - MarsCode) 题目:
二分数字组合
问题描述
小F面临一个有趣的挑战:给定一个数组,她需要将数组中的数字分为两组。分组的目标是使得一组数字的和的个位数等于给定的 A,另一组数字的和的个位数等于给定的 B。除此之外,还有一种特殊情况允许其中一组为空,但剩余数字和的个位数必须等于 A 或 B。小F需要计算所有可能的划分方式。
例如,对于数组 [1, 1, 1] 和目标 A = 1,B = 2,可行的划分包括三种:每个 1 单独作为一组,其余两个 1 形成另一组。如果 A = 3,B = 5,当所有数字加和的个位数为 3 或 5 时,可以有一组为非空,另一组为空。
测试样例
样例1:
输入:
n = 3,A = 1,B = 2,array_a = [1, 1, 1]
输出:3
样例2:
输入:
n = 3,A = 3,B = 5,array_a = [1, 1, 1]
输出:1
样例3:
输入:
n = 2,A = 1,B = 1,array_a = [1, 1]
输出:2
样例4:
输入:
n = 5,A = 3,B = 7,array_a = [2, 3, 5, 7, 9]
输出:0
思路:有两种思路一个是用DFS,另一个是用DP(动态规划),我认为dp是更好的,不过dfs也能过可能是数据不强导致的。
先讲动态规划的思路
我们定义一个二维数组 dp[i][j] 表示前 i 个数字能否分成两组,使得一组的和的个位数为 j。我们的目标是计算 dp[n][A] + dp[n][B],其中 n 是数组的长度,A 和 B 是给定的目标个位数。
步骤:
-
初始化:
- 创建一个二维数组
dp,大小为(n+1) x 10,初始化为False。 - 设置
dp[0][0] = True,因为空数组的和的个位数为 0 是可能的。
- 创建一个二维数组
-
状态转移:
-
遍历数组中的每个数字
x。 -
对于每个可能的和的个位数
k(从 0 到 9),更新dp[i][j]:- 如果
dp[i-1][k]为True,则设置dp[i][(k + x) % 10] = True。这表示如果前i-1个数字可以分成两组,使得一组的和的个位数为k,那么加上当前数字x后,另一组的和的个位数可以是(k + x) % 10。 - 同时,如果
k + x的个位数为 0,则设置dp[i][0] = True,因为一组的和的个位数为 0 总是可能的。
- 如果
-
-
计算结果:
- 最后,计算
dp[n][A] + dp[n][B]来得到所有可能的划分方式的数量。
- 最后,计算
示例:
对于样例1:n = 3, A = 1, B = 2, array_a = [1, 1, 1]
-
初始化
dp为False,除了dp[0][0] = True。 -
遍历数字,更新
dp:- 对于第一个 1:
dp[1][1] = True(因为0 + 1 = 1)。 - 对于第二个 1:
dp[2][2] = True(因为1 + 1 = 2),dp[2][0] = True(因为1 + 1 = 2的个位数是 2,加上 0 还是 2)。 - 对于第三个 1:
dp[3][3] = True(因为2 + 1 = 3),dp[3][1] = True(因为0 + 1 = 1),dp[3][0] = True(因为2 + 1 = 3的个位数是 3,加上 0 还是 3)。
- 对于第一个 1:
-
计算结果:
dp[3][1] + dp[3][2] = 2。
对于样例2:n = 3, A = 3, B = 5, array_a = [1, 1, 1]
- 按照上述步骤更新
dp。 - 计算结果:
dp[3][3] = True(因为1 + 1 + 1 = 3),dp[3][5] = False。 - 结果为 1,因为只有一种方式使得一组的和的个位数为 3。
对于样例3:n = 2, A = 1, B = 1, array_a = [1, 1]
- 按照上述步骤更新
dp。 - 计算结果:
dp[2][1] = True(因为1 + 1 = 2的个位数是 2,但我们可以有一组为空,另一组的和的个位数为 1)。 - 结果为 2,因为有两种方式:一组为空,另一组的和的个位数为 1,或者两组都是单个数字 1。
对于样例4:n = 5, A = 3, B = 7, array_a = [2, 3, 5, 7, 9]
- 按照上述步骤更新
dp。 - 计算结果:
dp[5][3] = False和dp[5][7] = False。 - 结果为 0,因为没有可能的划分方式使得一组的和的个位数为 3 或 7。
这种方法的时间复杂度是 O(n*10)
DFS思路: 使用深度优先搜索(DFS)解决这个问题的思路是尝试所有可能的数字组合,以确定它们是否可以被分成两组,使得一组的和的个位数等于 A,另一组的和的个位数等于 B。这里的关键点是,我们需要考虑所有可能的子集,并且对于每个子集,我们都需要检查其和的个位数是否符合要求。
步骤:
-
定义递归函数:
- 创建一个递归函数
dfs(index, sumA, sumB),其中index是当前处理的数组元素的索引,sumA是当前和的个位数(对应于 A 的目标),sumB是另一组当前和的个位数(对应于 B 的目标)。
- 创建一个递归函数
-
基本情况:
- 如果
index等于数组长度,检查sumA是否等于 A 或者sumB是否等于 B,如果是,则找到了一种有效的划分方式。
- 如果
-
递归探索:
- 对于数组中的每个元素,你有两个选择:要么将其添加到
sumA的组中,要么将其添加到sumB的组中。 - 更新
sumA和sumB以反映添加当前元素后的新的和的个位数。 - 递归调用
dfs(index + 1, (sumA + array[index]) % 10, sumB)和dfs(index + 1, sumA, (sumB + array[index]) % 10)。
- 对于数组中的每个元素,你有两个选择:要么将其添加到
-
计数:
- 使用一个全局计数器来记录所有有效的划分方式。
-
回溯:
- 在递归调用返回后,需要回溯以尝试其他可能的组合。
示例代码框架(伪代码):
function dfs(index, sumA, sumB):
if index == array.length:
if sumA == A or sumB == B:
count++
return
dfs(index + 1, (sumA + array[index]) % 10, sumB)
dfs(index + 1, sumA, (sumB + array[index]) % 10)
count = 0
dfs(0, 0, 0)
return count
注意事项:
- 这种方法可能会非常慢,因为它尝试了所有可能的组合,时间复杂度是指数级的。
- 为了避免重复计算,可以使用记忆化技术来存储已经计算过的状态。
- 由于问题的特殊性,即允许一组为空,因此在基本情况中需要检查
sumA是否等于 A 或者sumB是否等于 B,或者数组的总和的个位数是否等于 A 或 B。
使用 DFS 解决这个问题需要仔细处理递归和回溯,以及如何有效地使用全局计数器来跟踪所有可能的解决方案。这种方法在小规模数据上是可行的,但对于大规模数据,需要我们的第一个更高效的算法,动态规划算法。
DP代码:
def findTargetSumWays(nums, A, B):
total_sum = sum(nums)
target_sum_A = (total_sum - B) % 10
target_sum_B = (total_sum - A) % 10
# 如果两组的和的个位数之和不能被10整除,则无解
if (target_sum_A + target_sum_B) % 10 != 0:
return 0
# 如果 A 或 B 大于总和的个位数,则无解
if A > (total_sum % 10) or B > (total_sum % 10):
return 0
# 初始化 DP 表,dp[i][j] 表示前 i 个数字能否分成两组,使得一组的和的个位数为 j
dp = [[0] * 10 for _ in range(len(nums) + 1)]
dp[0][0] = 1 # 空数组的和的个位数为 0 是可能的
# 填充 DP 表
for i in range(1, len(nums) + 1):
for j in range(10):
# 不选择当前数字
if dp[i - 1][j] == 1:
dp[i][(j + nums[i - 1]) % 10] = 1
# 选择当前数字
if j == 0 or dp[i - 1][(j - nums[i - 1]) % 10] == 1:
dp[i][j] = 1
# 计算结果
return dp[-1][A] + dp[-1][B]
# 测试样例
print(findTargetSumWays([1, 1, 1], 1, 2)) # 输出:3
print(findTargetSumWays([1, 1, 1], 3, 5)) # 输出:1
print(findTargetSumWays([1, 1], 1, 1)) # 输出:2
print(findTargetSumWays([2, 3, 5, 7, 9], 3, 7)) # 输出:0
dfs 代码:
def findTargetSumWays(nums, A, B):
def dfs(index, sumA, sumB):
# 如果已经处理完所有数字
if index == len(nums):
# 如果 A 或 B 的和的个位数等于当前和的个位数,则找到了一种划分方式
if sumA % 10 == A or sumB % 10 == B:
return 1
return 0
# 不包含当前数字,将其分配到 sumB 的组
not_include = dfs(index + 1, sumA, sumB + nums[index])
# 包含当前数字,将其分配到 sumA 的组
include = dfs(index + 1, sumA + nums[index], sumB)
# 返回两种情况的和
return not_include + include
# 如果总和的个位数小于 A 或 B,则不可能找到解
total_sum = sum(nums)
if (total_sum % 10 < A) or (total_sum % 10 < B):
return 0
# 如果 A 和 B 的和的个位数不等于总和的个位数,则不可能找到解
if (A + B) % 10 != total_sum % 10:
return 0
# 特殊情况处理:允许一组为空
if A == 0 or B == 0:
return 1
# 从第一个数字开始 DFS
return dfs(0, 0, 0)
# 测试样例
print(findTargetSumWays([1, 1, 1], 1, 2)) # 输出:3
print(findTargetSumWays([1, 1, 1], 3, 5)) # 输出:1
print(findTargetSumWays([1, 1], 1, 1)) # 输出:2
print(findTargetSumWays([2, 3, 5, 7, 9], 3, 7)) # 输出:0
谢谢大家的阅读,觉得有帮助可以给我点赞支持,嘿嘿。