割绳子

Submission

运行时间: 248 ms

内存: 28.1 MB

class Solution:
    def maxLength(self, ribbons: List[int], k: int) -> int:
        # corner case
        if not ribbons:
            return 0 
        if len(ribbons) == 1:
            return (ribbons[0] // k) if (ribbons[0] // k) > 0 else 0 
        
        n = len(ribbons)
        # targeted wood len 
        start, end = 1, min(max(ribbons), sum(ribbons) // k)
        # can not obtain k ribbons
        if end < 1:
            return 0
        # we exclude no answer situation, hence there must have an answer 
        while start + 1 < end:
            mid = (start + end) // 2
            cur_cnt = self.get_wood_count(mid, ribbons) 
            if cur_cnt < k:
                # cnt is smaller than target
                # we need to make cnt bigger, making wood_len smaller 
                end = mid
            elif cur_cnt > k:
        # cnt is larger than target
        # we need to make cnt smaller, making wood_len bigger                 
                start = mid 
            elif cur_cnt == k:
        # we need to find max wood_len, which is the last element == target 
                start = mid 
        # goal is to find max wood_len 
        return end if self.get_wood_count(end, ribbons) >= k else start 

    
    def get_wood_count(self, targeted_wood_len, ribbons):
        return sum(i // targeted_wood_len for i in ribbons)

Explain

此题解采用的是二分查找的方法来解决割绳子问题。题目要求将一组绳子割成若干段,每段长度相等,且段数至少为k。这里的关键是确定每段绳子的最大可能长度。使用二分查找在可能的长度范围内寻找最大的满足条件的长度。起始长度为1,最大长度设为绳子中的最大值和所有绳子总长除以k的最小值之间的较小者。通过二分查找,每次计算中间值mid,然后根据mid来计算可以割出的绳子段数。如果段数少于k,减小长度上限;如果段数多于或等于k,增大长度下限并继续搜索,以找出最大可能的长度。

时间复杂度: O(n log(maxLength))

空间复杂度: O(1)

class Solution:
    def maxLength(self, ribbons: List[int], k: int) -> int:
        if not ribbons:
            return 0  # 没有绳子直接返回0
        if len(ribbons) == 1:
            return (ribbons[0] // k) if (ribbons[0] // k) > 0 else 0  # 只有一根绳子的情况
        n = len(ribbons)
        start, end = 1, min(max(ribbons), sum(ribbons) // k)
        if end < 1:
            return 0  # 如果最大长度小于1,无法割出任何段
        while start + 1 < end:
            mid = (start + end) // 2
            cur_cnt = self.get_wood_count(mid, ribbons)
            if cur_cnt < k:
                end = mid
            elif cur_cnt >= k:
                start = mid
        return end if self.get_wood_count(end, ribbons) >= k else start
    def get_wood_count(self, targeted_wood_len, ribbons):
        return sum(i // targeted_wood_len for i in ribbons)  # 计算当前长度下可割得的总段数

Explore

这个选择基于两个考虑:首先,割出的每段绳子长度不可能超过绳子中的最大值,因为你不能从一根比这根短的绳子中割出更长的段。其次,所有绳子的总长度除以k给出了理论上每段绳子最大可能的平均长度。如果这个计算值小于绳子中的最大值,说明即使所有绳子均匀分割,每段的最大长度也不会超过这个平均值。因此,取这两者之间的较小者作为搜索的上界,可以有效缩小搜索范围,并保证找到的最大长度是合理的。

在这种情况下,算法先通过条件`end = min(max(ribbons), sum(ribbons) // k)`设定最大长度`end`。如果所有绳子的总长除以k小于1(即`sum(ribbons) < k`),则`end`会被设定为0。随后在二分查找过程中,如果`end`小于1,则算法会直接返回0,表示无法割出满足条件的绳子段。这是一种边界情况处理,确保算法在逻辑上严谨并能正确处理所有输入情况。

整除操作确实会忽略小数部分,但这对于割绳子问题是适当的。因为我们不能考虑不完整的绳子段,整段的长度必须是整数。使用整除可以确保只计算完整段,即使这意味着忽略了一部分绳子。这种方法是为了确保每段绳子的实际可用性,而不是仅仅从理论上分割绳子。因此,这不会影响结果的准确性,而是符合问题的实际需求。

在二分查找中使用`start + 1 < end`作为终止条件是为了防止进入无限循环,并确保可以探讨所有可能的长度。当`start`和`end`相邻时,中点`mid`将会等于`start`,此时如果继续使用`start < end`或`start <= end`作为条件,可能导致重复计算同一个中值,从而陷入循环。而当使用`start + 1 < end`时,一旦`start`和`end`相邻,循环就会停止,此时可以安全地检查`end`处的值是否满足条件,然后再决定是否使用`start`或`end`作为最终结果,这样可以更精确地找到最大可能长度。