华为OD机试2023 - 等和子数组最小和
题目描述
给定一个数组nums,将元素分为若干个组,使得每组和相等,求出满足条件的所有分组中,组内元素和的最小值。输入描述第一行输入m接着输入m个数,表示此数组nums数据范围:1<=m<=50,1<=nums[i]<=50
输出描述
最小拆分数组和
用例
输入
7
4 3 2 3 5 2 1
输出
5
说明
可以等分的情况有:4个子集(5),(1,4), (2,3), (2,3);
2个子集(5,1,4),(2,3,2,3)但最小的为5。
解题思路
这个题可以用回溯法或者动态规划, 回溯法时间复杂度太高, 可达O(sqrt(sum(nums))*2^m), 超过2^50的时间, 太慢了!
可以用动态规划来优化:
- 先把所有数求和得到一个sum, 对这个sum进行因子分解, 分解成k 个 相同的子数组, 每个子数组的和都是x
- 利用动态规划从小到大对因子x, 检查是否可以分成k个和为x的因子, 且k*x=sum
代码实现
package main import "sort" /* 给定一个数组nums,将元素分为若干个组,使得每组和相等,求出满足条件的所有分组中,组内元素和的最小值 输入描述: 第一行输入 m 接着输入m个数,表示此数组 数据范围:1<=M<=50, 1<=nums[i]<=50 输出描述: 最小拆分数组和。 示例: 输入: 7 4 3 2 3 5 2 1 输出: 5 说明:可以等分的情况有: 4 个子集(5),(1,4),(2,3),(2,3) 2 个子集(5, 1, 4),(2,3, 2,3) 但最小的为5。 动态规划 定义状态 $dp[i][j]$ 表示前 $i$ 个数能否分成 $j$ 个都为因子的子数组 */ func canSplit(nums []int, k int, x int) bool { n := len(nums) dp := make([][]bool, n+1) for i := range dp { dp[i] = make([]bool, k+1) } // 初始化状态 for i := 1; i <= n; i++ { dp[i][1] = dp[i-1][1] || nums[i-1] == x } // 状态转移 for i := 2; i <= n; i++ { for j := 2; j <= k; j++ { for l := i - 1; l >= j-1; l-- { if dp[l][j-1] && sum(nums[l:i]) == x { dp[i][j] = true break } } } } return dp[n][k] } /* 因子分解+动态规划 */ func minSum(nums []int) int { s := sum(nums) m := len(nums) factors := []int{} for i := 1; i*i <= s; i++ { if s%i == 0 { factors = append(factors, i) if i != s/i { factors = append(factors, s/i) } } } sort.Ints(factors) // 对所有因子进行遍历,计算是否能够分成 k 个都为 factor 的子数组 minSum := s for _, factor := range factors { k := s / factor if k < 2 { continue } dp := make([][]bool, m+1) for i := range dp { dp[i] = make([]bool, k+1) } dp[0][0] = true for i := 1; i <= m; i++ { for j := 1; j <= k; j++ { if nums[i-1] > factor { dp[i][j] = false } else if nums[i-1] == factor { dp[i][j] = dp[i-1][j-1] } else { /* 我们可以分为两种情况来考虑: 不选第 $i$ 个物品,此时背包的容量仍然是 $j$,因此选法和 $dp[i-1][j]$ 相同。 选第 $i$ 个物品,此时背包的容量为 $j-1$,因此选法和 $dp[i-1][j-1]$ 相同。 由于我们只需要判断是否存在一种选法,使得背包被恰好填满,因此可以使用逻辑或运算符将这两种情况合并起来,即: dp[i][j]=dp[i−1][j−1] ∣∣ dp[i−1][j] */ dp[i][j] = dp[i-1][j-1] || dp[i-1][j] } } } if dp[m][k] { minSum = factor break } } return minSum }
时间复杂度分析
因子个数是O(sqrt(sum(nums))), 总时间复杂度是 O(sqrt(sum(nums))* m^2)
#机试#