【数组和字符串】(五) 小结

目录

五、数组相关的技术

5.1 杨辉三角

5.2 杨辉三角 II

5.3 反转字符串中的单词 III

5.4 寻找旋转排序数组中的最小值

5.5 删除排序数组中的重复项

5.6 移动零


五、数组相关的技术

参考文献:https://leetcode-cn.com/explore/learn/card/array-and-string/202/conclusion/790/


5.1 杨辉三角

5.1.1 问题描述

5.1.2 求解过程

个人实现

法一:暴力迭代 + 动态规划。正常地生成各行以及行中各元素。复杂度 O(n^2)。

2020/06/18 - 9.06%

class Solution:
    def generate(self, numRows: int) -> List[List[int]]:
        if not numRows:
            return []
        if numRows == 1:
            return [[1]]
        if numRows == 2:
            return [[1],[1,1]]
        
        res = [[1],[1,1]]               # 初始化结果列表
        for line in range(2, numRows):  # 当前行行号
            temp = [1]                  # 初始化当前行头
            for j in range(1, line):    # 当前行需计算的中间元素数
                num = res[line-1][j-1] + res[line-1][j]  # 求和生成中间元素
                temp.append(num)        # 中间元素加入临时列表
            temp.append(1)              # 补充当前行尾
            res.append(temp)            # 当前行加入结果列表
        
        return res

法一改:暴力迭代 + 动态规划 改。稍作简化,思想不变。复杂度 O(n^2)。

2020/06/18 - 65.52%

class Solution:
    def generate(self, numRows: int) -> List[List[int]]:
        if not numRows:
            return []
        if numRows == 1:
            return [[1]]

        res = [[1]]                    # 初始化结果列表 res 第0行
        for line in range(numRows-1):  # 上一行 line 行号
            cur = [1]                  # 当前行 cur 行头取1
            for index in range(line):  # 当前行 cur 需计算的中间元素数
                cur.append(res[line][index] + res[line][index+1])  # 求和生成中间元素加入当前行 cur
            cur.append(1)              # 当前行 cur 行尾取1
            res.append(cur)            # 当前行 cur 加入结果列表 res
        return res

法二:迭代 + 动态规划 再改。由于每行都是回文串,所以可以 通过对称性使运算量减半。复杂度 O(n^2)。

2020/06/18 - 65.52%

class Solution:
    def generate(self, numRows: int) -> List[List[int]]:
        if not numRows:
            return []
        if numRows == 1:
            return [[1]]
        
        res = [[1]]
        for line in range(1, numRows):         # 当前行 temp 的 index
            temp = [0 for _ in range(line+1)]  # 当前行 temp 0值初始化
            temp[0] = temp[-1] = 1             # 当前行 temp 首尾均为1
            for j in range(line // 2):         # 对称运算与赋值
                temp[j+1] = temp[-j-2] = res[line-1][j] + res[line-1][j+1]
            res.append(temp)  # 结果列表 res 加入当前行 temp
        return res

官方实现与说明

## 比个人实现-法一改还要简洁泛化
class Solution:
    def generate(self, num_rows):
        triangle = []
        for row_num in range(num_rows):
            # The first and last row elements are always 1.
            row = [None for _ in range(row_num+1)]
            row[0], row[-1] = 1, 1
            # Each triangle element is equal to the sum of the elements
            # above-and-to-the-left and above-and-to-the-right.
            for j in range(1, len(row)-1):
                row[j] = triangle[row_num-1][j-1] + triangle[row_num-1][j]
            triangle.append(row)

        return triangle

 2020/06/18 - 65.52%

其他实现

法一递归法,不论是效率还是简洁度都显著提升,值得认真学习。

2020/06/18 - 99.50% - 最佳

class Solution:
    def generate(self, numRows: int) -> List[List[int]]:
        if numRows == 1:
            return [[1]]
        if not numRows:
            return []
        ## 递归
        pre = self.generate(numRows-1)    # 取得上一行, 从首行[[1]]开始
        pre.append([None for _ in range(numRows)])  # 初始化新行 pre[numRows-1]
        pre[numRows-1][0], pre[numRows-1][numRows-1] = 1, 1  # 当前行首、尾元素总为1
        ## 若直接用 pre.append([1 for _ in range(numRows)]) 可能更简洁些
        for index in range(1, numRows-1): # 当前行中间元素求和与加入
            pre[numRows-1][index] = pre[numRows-2][index-1] + pre[numRows-2][index]
        return pre

参考文献

https://leetcode-cn.com/explore/learn/card/array-and-string/202/conclusion/1421/

https://leetcode-cn.com/problems/pascals-triangle/solution/yang-hui-san-jiao-by-leetcode/ 


5.2 杨辉三角 II

5.2.1 问题描述

5.2.2 求解过程

法一:对《杨辉三角 I》的解答稍作修改,朴素地计算每一行并记录在 res 中,然后返回最后一行 res[-1]。

2020/06/18 - 98.50% - 还有优化空间

class Solution:
    def getRow(self, rowIndex: int) -> List[int]:
        if rowIndex == 0:
            return [1]

        res = [[1]]
        for line in range(1, rowIndex+1):      # 当前行 temp 的 index
            temp = [1 for _ in range(line+1)]  # 当前行 temp 1 值初始化
            for j in range(line // 2):         # 对称运算与赋值
                temp[j+1] = temp[-j-2] = res[line-1][j] + res[line-1][j+1]
            res.append(temp)  # 结果列表 res 加入当前行 temp
        return res[-1]

法二递归法,每次递归维护和计算两个列表,即前一行 pre 和当前行 cur。

2020/06/18 - 99.75% - 几乎效率最佳写法最简洁

class Solution:
    def getRow(self, rowIndex: int) -> List[int]:
        if rowIndex == 0:
            return [1]
        ## 递归
        pre = self.getRow(rowIndex-1)  # 前一行
        cur = [1 for _ in range(rowIndex+1)]  # 本行, 1 代替 0 值初始化
        for i in range(rowIndex//2):
            # 左右相同位置元素对称
            cur[i+1] = cur[-i-2] = pre[i] + pre[i+1]  
        return cur

参考文献

https://leetcode-cn.com/explore/learn/card/array-and-string/202/conclusion/1422/ 


5.3 反转字符串中的单词 III

5.3.1 问题描述

5.3.2 求解过程

法一:使用 str.split() 以 " " 为间隔分割字符串并存入列表 lst 中,然后用列表推导式生成每个元素的反转并存入列表 temp 中,最后用 " ".join() 以 " " 为间隔将列表 temp 分割并返回新的字符串。有多种等价表示。复杂度 O(n)。

2020/06/18 - 99.50% - 大量使用 Python 内置函数而且没有复杂的要求当然快...

class Solution:
    def reverseWords(self, s: str) -> str:
        # s = "Let's take LeetCode contest"
        lst = s.split()
        # lst = ["Let's", 'take', 'LeetCode', 'contest']  
        temp = [lst[i][::-1] for i in range(len(lst))]  
        # temp = ["s'teL", 'ekat', 'edoCteeL', 'tsetnoc']
        return " ".join(temp)
        # "s'teL ekat edoCteeL tsetnoc" 
        # ----------------------------------------------------------------------
        '''
        lst = s.split()
        res = []
        for i in range(len(lst)):
            res.append(lst[i][::-1])
        return ' '.join(res)
        '''
        # ----------------------------------------------------------------------
        '''
        return ' '.join(s.split(' ')[::-1])[::-1]
        '''

如果不允许使用内置函数或根本没有相关内置函数,则手动遍历和处理也可以。


5.4 寻找旋转排序数组中的最小值

5.4.1 问题描述

5.4.2 求解过程

事实上,遍历整个数组依次比较和查找,或直接使用内置函数 min() 就能解答,但其复杂度为 O(n),属于次优解法,此处不再赘述。出题者可能希望进一步优化时间复杂度,故最先想到的就是二分法。

法一:二分法查找,复杂度 O(logn)。

2020/06/19 - 44.75% - 说明存在特殊的规律及其优化方法

class Solution:
    def findMin(self, nums: List[int]) -> int:
        ## 二分法
        start = 0
        end = len(nums)-1
        while start < end:
            mid = (start+end) // 2
            if nums[mid] > nums[end]:
                start = mid + 1  # 搜索区间右移
            if nums[mid] < nums[end]:
                end = mid        # 搜索区间左移 
        return nums[start]

法二:根据规律,对二分法在开头特殊条件部分稍作优化,复杂度 O(logn)。

2020/06/19 - 69.49% - 这其实基本是最高效的方法之一了。

class Solution:
    def findMin(self, nums: List[int]) -> int:
        ## 二分法-特定优化
        if (len(nums) == 1) or (nums[0] < nums[-1]):
            return nums[0]  # 单元素 或 全顺序

        start = 0
        end = len(nums)-1
        while start < end:
            mid = (start+end) // 2
            if nums[mid] > nums[end]:
                start = mid + 1  # 搜索区间右移
            if nums[mid] < nums[end]:
                end = mid        # 搜索区间左移 
        return nums[start]

5.5 删除排序数组中的重复项

5.5.1 问题描述

5.5.2 求解过程

法一:双指针,遍历所有元素,遇见相同元素直接 del 删除,最后只保留一个长度为 k 的、元素各不重复的列表。复杂度 O(n^2)。

2020/06/19 - 31.72% - del 操作不断调整列表 nums 的长度及整体元素的位移,因此效率极低!

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        if len(nums) <= 1:
            return len(nums)

        p = 1  # 令指针初始 index = 1
        while p < len(nums):
            if nums[p-1] == nums[p]:  # 相当于双指针/滑动窗口
                del nums[p-1]  # del 是 O(j-k+1) 复杂度的操作, 并不高效
            else:
                p += 1
        return len(nums)
        

法二快慢指针,充分利用题中条件,只在列表 nums 的前 k 个位置保留不重复元素,而无需关心后 n - k 个元素。复杂度 O(n)。

2020/06/19 - 99.95% - 最简洁和高效的方式

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        # 充分利用题给条件 + 只有O(1)的 index 操作
        # 不需要考虑数组中超出新长度后面的元素!
	    slow = 0                               # slow 是不重复元素慢指针, 
	    for fast in range(slow+1, len(nums)):  # fast 是顺序搜索快指针
		    if nums[fast] != nums[slow]:       # 一旦找到不同元素, 直接替换到下一位
			    slow += 1                      # 移到待修改位置的 index
			    nums[slow] = nums[fast]        # 替换
	    return slow+1

        # 此外, 以下方法不可行,因为使用了额外空间!导致结果异常
        #nums = list(set(nums))
        #return len(nums)

5.6 移动零

5.6.1 问题描述

5.6.2 求解过程

法一快慢指针,慢指针 slow 指向 0 元素,快指针 fast 指向非 0 元素,满足此条件则交换元素,否则分别后移搜素,直至满足条件或超出数组长度。复杂度 O(n)。

2020/06/19 - 85.30% - 朴素的方式

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        if len(nums) <= 1:
            return nums
        
        slow = 0  # 0元素慢指针
        while nums[slow] != 0:  
            slow += 1  # 找到第一个0元素索引
            if slow >= len(nums):
                return nums  # 没有0元素则无需后续操作了
            
        fast = slow + 1  # 非0元素快指针
        while slow < fast < len(nums):
            if nums[slow] == 0 and nums[fast] != 0:
                nums[slow], nums[fast] = nums[fast], nums[slow]  # 交换
            if nums[slow]:  # 查找0元素
                slow += 1
            if not nums[fast]:  # 查找非0元素
                fast += 1
        return nums
    
    ''' 测试用例
    [0,1,0,3,12]
    [0]
    [0,0]
    [1,0]
    [1,0,1]
    [4,2,4,0,0,3,0,5,1,0]
    '''

法二:对法一的简化与优化,避免不够抽象导致的过于冗长,使纯属简单问题复杂化。复杂度 O(n)。

2020/06/19 - 99.95% - 多维护一个作用域更大的变量开销还是较大的。

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        slow = 0
        # 既然 fast 总要遍历整个数组, 那么直接用 for loop 内的局部变量迭代即可
        for fast in range(len(nums)):  
            if nums[fast] != 0:
                nums[slow], nums[fast] = nums[fast], nums[slow]
                slow += 1
        return nums

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值