每日一题——Python代码实现力扣58. 最后一个单词的长度(举一反三+思想解读+逐步优化)五千字好文


一个认为一切根源都是“自己不够强”的INTJ

个人主页:用哲学编程-优快云博客
专栏:每日一题——举一反三
Python编程学习
Python内置函数

Python-3.12.0文档解读

目录

我的写法 

代码逻辑:

时间复杂度:

空间复杂度:

代码优化:

我要更强

代码解释:

时间复杂度分析:

空间复杂度分析:

哲学和编程思想

1. 简洁性和直接性(Simplicity and Directness)

2. 空间利用效率(Space Efficiency)

3. 时间效率(Time Efficiency)

4. 算法的高效性(Algorithmic Efficiency)

5. Robustness

6. 减少外部依赖(Reduced Dependency on External Functions)

具体说明优化方法的哲学和编程思想:

总结

举一反三

1. 简洁性和直接性

2. 空间利用效率

3. 时间效率

4. 算法的高效性

5. 代码的鲁棒性

6. 减少外部依赖


题目链接

我的写法 

class Solution:
    def lengthOfLastWord(self, s: str) -> int:
        s=s.split()
        return len(s[-1])
        

这段代码定义了一个名为 Solution 的类,其中包含一个方法 lengthOfLastWord,该方法接收一个字符串 s 作为输入,并返回该字符串中最后一个单词的长度。下面是对这段代码的专业点评:

  1. 代码逻辑:

    • 首先,使用 split() 方法将输入的字符串 s 分割成单词列表。split() 方法默认以空格作为分隔符,将字符串分割成多个子字符串,并返回这些子字符串组成的列表。
    • 然后,通过索引 [-1] 获取列表中的最后一个元素,即最后一个单词。
    • 最后,使用 len() 函数计算并返回这个单词的长度。
  2. 时间复杂度:

    • split() 方法的时间复杂度为 O(n),其中 n 是字符串 s 的长度。这是因为 split() 需要遍历整个字符串来分割单词。
    • len() 函数的时间复杂度为 O(1),因为它只需要返回列表中元素的数量。
    • 因此,整体的时间复杂度主要由 split() 决定,为 O(n)。
  3. 空间复杂度:

    • split() 方法会创建一个新的列表,其大小取决于字符串中单词的数量。在最坏的情况下,如果字符串中的每个字符都是单词的一部分(即没有空格),则列表的大小为 n。
    • 因此,空间复杂度为 O(n),其中 n 是字符串 s 的长度。
  4. 代码优化:

  • 这段代码已经相当简洁和高效,但如果要进一步优化,可以考虑直接从字符串的末尾开始遍历,找到最后一个单词的起始位置,然后计算其长度,这样可以避免创建额外的列表,从而减少空间复杂度。

总的来说,这段代码是一个简洁有效的解决方案,适用于大多数情况。如果输入的字符串非常大,可能需要考虑空间效率的优化。


我要更强

当然,我们可以通过直接从字符串末尾开始遍历来优化时间和空间复杂度。这种方法避免了使用 split() 方法创建额外的列表,从而减少了空间复杂度。下面是优化后的代码:

class Solution:
    def lengthOfLastWord(self, s: str) -> int:
        # 从字符串末尾开始向前遍历
        index = len(s) - 1
        
        # 跳过末尾的空格
        while index >= 0 and s[index] == ' ':
            index -= 1
        
        # 计算最后一个单词的长度
        length = 0
        while index >= 0 and s[index] != ' ':
            length += 1
            index -= 1
        
        return length

代码解释:

  • 首先,我们初始化一个变量 index 为字符串 s 的最后一个字符的索引。
  • 然后,我们使用一个循环从末尾向前移动 index,直到找到第一个非空格字符。这跳过了字符串末尾可能存在的空格。
  • 接着,我们使用另一个循环继续向前移动 index,同时计算遇到的非空格字符的数量,即最后一个单词的长度。
  • 最后,返回计算得到的长度。

时间复杂度分析:

  • 在最坏的情况下,我们需要遍历整个字符串,因此时间复杂度为 O(n),其中 n 是字符串的长度。

空间复杂度分析:

  • 我们没有使用任何额外的数据结构(如列表),只使用了几个变量来存储索引和长度,因此空间复杂度为 O(1)。

这种优化方法在处理大型字符串时尤其有效,因为它避免了创建可能非常大的中间列表。


哲学和编程思想

这些方法背后的哲学和编程思想主要包括以下几个方面:

1. 简洁性和直接性(Simplicity and Directness)

优化后的代码直接从字符串末尾开始遍历,避免了不必要的中间步骤。这种方法体现了“简洁即美”的编程哲学。过于复杂的解决方案可能会增加出错的概率和维护成本,而简单直接的方法则更易于理解和维护。

2. 空间利用效率(Space Efficiency)

通过避免创建额外的列表,优化后的代码极大地减少了空间占用,体现了“尽量减少资源使用”的编程思想。这种思维方式在处理大数据时尤为重要,因为内存资源是有限的。

3. 时间效率(Time Efficiency)

优化后的代码通过单次遍历字符串来计算最后一个单词的长度,而不是进行多次遍历或创建中间数据结构,从而提高了时间效率。这符合“尽量减少计算复杂度”的编程哲学。

4. 算法的高效性(Algorithmic Efficiency)

直接从字符串末尾开始遍历的算法是一种“贪心算法”(Greedy Algorithm)的应用,它在每一步选择当前最优解。这种方法保证了在最短的时间内找到最后一个单词的长度。

5. Robustness

无论字符串的情况如何(是否包含多个空格,是否为空字符串等),优化后的方法都能正确处理。这体现了“鲁棒性”的编程思想,即代码能够在各种边界情况下正常运行。

6. 减少外部依赖(Reduced Dependency on External Functions)

优化后的方法减少了对内置函数(如 split())的依赖,使得代码更加自包含和独立。这有助于提高代码的可移植性和灵活性,符合“减少依赖,增加自包含性”的编程原则。

具体说明优化方法的哲学和编程思想:

  • 简洁性和直接性:通过直接从字符串末尾开始遍历,避免了 split() 方法的使用,简化了代码逻辑。
  • 空间利用效率:通过在原字符串上进行操作,而不是创建中间列表,减少了不必要的空间占用。
  • 时间效率:通过单次遍历字符串,减少了不必要的重复遍历,优化了时间复杂度。
  • 算法的高效性:遍历字符串并计算最后一个单词的长度,这种贪心算法在步骤选择上每次都选择最优解。
  • 减少外部依赖:减少对内置函数的依赖,使代码更加独立和灵活。

总结

通过直接处理字符串而不是依赖于 split() 等内置函数,优化后的代码不仅简化了逻辑,提高了时间和空间效率,还遵循了许多重要的编程哲学和思想,如简洁性、效率和减少依赖。这些思想在日常编程中都是非常重要的,可以帮助开发者编写出更高效、可维护性更强的代码。


举一反三

理解和应用这些哲学和编程思想,可以在编程过程中提高代码的质量和效率。以下是一些具体的技巧和建议,帮助你在编写代码时能够举一反三:

1. 简洁性和直接性

技巧:

  • 减少中间步骤:在解决问题时,思考是否可以直接从输入到输出,不经过多余的中间步骤。
  • 避免冗余代码:删除不必要的变量和重复的逻辑,保持代码简洁。
  • 使用简洁的语法:利用Python的简洁语法特性,如列表推导式、生成器等,来减少代码的长度和复杂度。

示例:

# 繁琐的写法
def square_numbers(nums):
    result = []
    for num in nums:
        result.append(num * num)
    return result

# 简洁的写法
def square_numbers(nums):
    return [num * num for num in nums]

2. 空间利用效率

技巧:

  • 原地操作:如果可以,尽量在原数据结构上进行操作,避免创建新的数据结构。
  • 使用生成器:对于需要惰性求值的情况,使用生成器表达式代替列表推导式,以减少内存占用。
  • 释放不再使用的资源:及时删除或释放不再需要的对象,尤其是在处理大数据时。

示例:

# 使用生成器表达式而不是列表推导式
def large_range():
    for i in range(10**6):
        yield i * i

# 使用生成器表达式
gen = (i * i for i in range(10**6))

3. 时间效率

技巧:

  • 减少重复计算:避免在循环或递归中进行重复计算,使用缓存或记忆化技术。
  • 优化算法:使用更高效的算法和数据结构,以减少时间复杂度。例如,使用哈希表而不是列表查找。
  • 提前终止条件:在循环或递归中添加提前终止条件,以减少不必要的计算。

示例:

# 使用哈希表进行查找
def find_duplicates(nums):
    seen = set()
    for num in nums:
        if num in seen:
            return True
        seen.add(num)
    return False

4. 算法的高效性

技巧:

  • 选择合适的算法:根据具体问题选择合适的算法,如贪心算法、动态规划、二分查找等。
  • 分治思想:将问题分解成更小的子问题,递归求解,然后合并结果。
  • 尽量减少复杂度:在设计算法时,尽量减少时间和空间复杂度。

示例:

# 使用分治思想的归并排序
def merge_sort(arr):
    if len(arr) <= 1:
        return arr
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    return merge(left, right)

def merge(left, right):
    result = []
    i = j = 0
    while i < len(left) and j < len(right):
        if left[i] < right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result.extend(left[i:])
    result.extend(right[j:])
    return result

5. 代码的鲁棒性

技巧:

  • 边界条件处理:在编写代码时,考虑所有可能的边界情况,如空输入、极大值和极小值等。
  • 异常处理:使用适当的异常处理机制,确保代码在异常情况下能够优雅地失败。
  • 单元测试:编写单元测试来验证代码的正确性,特别是针对边界条件和异常情况。

示例:

def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return float('inf')

# 单元测试
assert safe_divide(10, 2) == 5
assert safe_divide(10, 0) == float('inf')

6. 减少外部依赖

技巧:

  • 减少对库的依赖:在可能的情况下,使用标准库或自己实现简单功能,避免引入不必要的第三方库。
  • 模块化设计:将代码划分为独立的模块,使每个模块尽量少依赖其他模块。
  • 自包含性:在编写模块时,确保它们尽量自包含,减少对外部资源的依赖。
# 自己实现简单的功能而不是依赖第三方库
def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n - 1)

通过这些技巧,可以更好地应用简洁性、空间利用效率、时间效率、算法高效性、代码鲁棒性和减少外部依赖等编程哲学和思想,编写出更优雅、高效和可维护的代码。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

用哲学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值