Python世界:力扣题解1712:将数组分成三个子数组的方案数,中等
- 任务背景
- 思路分析
- 代码实现
- 测试套件
- 本文小结
任务背景
问题来自力扣题目1712. Ways to Split Array Into Three Subarrays,大意如下:
A split of an integer array is good if:
- The array is split into three non-empty contiguous subarrays - named
left
,mid
,right
respectively from left to right.- The sum of the elements in
left
is less than or equal to the sum of the elements inmid
, and the sum of the elements inmid
is less than or equal to the sum of the elements inright
.Given
nums
, an array of non-negative integers, return the number of good ways to splitnums
. As the number may be too large, return it modulo10^9 + 7
.
翻译下,需求是:对给定无序数组划分成三组子数组,划分后要求左、中、右数组元素和递增,返回可划分的方法总数,若不可划分,则返回-1.
思路分析
第一个坑,读题失误,不是元素个数和递增,而是元素之和要递增。下面为错误做法,埋坑警戒。
import mathclass Solution(object):def waysToSplit(self, nums):""":type nums: List[int]:rtype: int"""res = 0nums_len = len(nums)ways_total = 0left_max = nums_len // 3 + 1for l in range(1, left_max):mid_max = (nums_len - l) // 2 + 1for m in range(l, mid_max):r = nums_len - l - mif (l <= m) and (m <= r) and (l + m + r == nums_len):ways_total += 1res = ways_totalreturn res
搞清楚真正意图后,思路如下:
- 先获取一个同样大小sum_arr数组,累加nums的元素和,构造有序数组,创造二分条件
- 对sum_arr数组做两个划分,即插两个板子,先定左板依次向右遍历
- 再浮动右板,找到第一个大于左板划分子数组mid,即找数组左上界low
- 再移动右板,找到第一个超过右板划分子数组right,即找数组右上界high
- 区间长度len=high-low,即为该定左板的可划分总数
- 依次移动左板,重复此步骤,累加len得到总的split_ways
代码实现
# nums,升序;找到第一个大于target值的索引
def get_array_left_bound(nums, l, h, t):# range: [low, high)target = tlow = lhigh = hfound_flag = Falsewhile (low < high):mid = low + (high - low) // 2if (nums[mid] >= 2*target):high = midfound_flag = Trueelse:low = mid + 1if found_flag:return lowreturn -1# nums,升序;找到最后一个大于target值的索引
def get_array_right_bound(nums, l, h, t):# range: [low, high)tar = tlow = lhigh = h# range: [low, high)found_flag = Falsewhile (low < high):mid = low + (high - low) // 2acc = nums[mid] - nums[l]left = acc + tarright = nums[h - 1] - nums[mid]if (left > right): # 不满足预期,缩减右界high = midelse: # 若 left <= rightlow = mid + 1found_flag = Trueif low > h - 1:low = h - 1if found_flag:return low # 返回low-1则表示right右界,能取到;返回low,则表示右界临界值,无法取。return -1def get_array_sum(nums):nums_len = len(nums)nums_sum = []val_sum = 0for i in range(nums_len):val_sum += nums[i]nums_sum.append(val_sum)# print(nums_sum)return nums_sumdef get_ways(nums, l):h = len(nums)t = nums[l] # left res, 0-lstart = get_array_left_bound(nums, l + 1, h, t)# start最大只能为倒数第二个索引,要确保最后至少有一个子数组if (start < 1 or start + 1 >= h):return 0t = nums[start] - nums[l] # mid res, [l+1, start]end = get_array_right_bound(nums, start, h, t)if (end <= start): # start最远能偏移的边界return 0ways = end - startreturn ways# sum + binary
class Solution(object):def waysToSplit(self, nums):""":type nums: List[int]:rtype: int"""res = 0nums_len = len(nums)nums_sum = get_array_sum(nums)# 左右边界挡板ways_total = 0left_max = nums_len - 1for l in range(0, left_max):if (2*nums_sum[l] > nums_sum[nums_len - 1]):breakways_total += get_ways(nums_sum, l)res = ways_total % 1_000_000_007return res
这里也埋了个坑,最开始94行没有注意处理大数取余10^9+7
,导致[0, 0, 0, 0, 0, …]大数用例一直无法通过。
测试套件
单例测试demo:
# one case test
nums = [1,1,1]
nums = [1,2,2,2,5,0]
nums = [3,2,1]
nums = [0,0,0,0]
nums = [0, 0, 0, 0] # 3
nums = [0, 0, 0, 0, 0] # 6sol = Solution()
res = sol.waysToSplit(nums)
print(res)
套件测试demo:
# 导入单元测试
import unittestdef test_base(self, nums, ret):sol = Solution()res = sol.waysToSplit(nums)self.assertEqual(res, ret)# 编写测试套
class TestSol(unittest.TestCase):def test_special1(self):nums = [1,1,1]ret = 1test_base(self, nums, ret)def test_special2(self):nums = [0,0,0]ret = 1test_base(self, nums, ret)def test_special3(self):nums = [0,0,0,0]ret = 3test_base(self, nums, ret)def test_special4(self):nums = [0,0,0,0,0]ret = 6test_base(self, nums, ret)def test_common1(self):nums = [1,2,2,2,5,0]ret = 3test_base(self, nums, ret)def test_common2(self):nums = [3,2,1]ret = 0test_base(self, nums, ret)def test_common3(self):nums = [1,2,2,2,5,0]ret = 3test_base(self, nums, ret)# 含测试套版本主调
if __name__ == '__main__':print('start!')unittest.main() # 启动单元测试print('done!')
本文小结
此题关键点在于二分法左右边界的获取,需要对边界条件有很熟练的处理,同时注意题意处理和特殊用例,如全零大数组处理,可用排列组合Cm2公式来处理,获取理论值。
- 参考实现1:https://leetcode.com/problems/ways-to-split-array-into-three-subarrays/solutions/999157/python3-binary-search-2-pointer/
- 参考实现2:https://blog.csdn.net/weixin_59916649/article/details/125187567