[Leetcode] [Tutorial] 数组


53. 最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

示例:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6

Solution

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        left, right = 0, 0
        max_sum = float('-inf')
        current_sum = 0
        
        while right < len(nums):
            current_sum += nums[right]
            max_sum = max(max_sum, current_sum)
            
            if current_sum < 0:
                left = right + 1
                right = left
                current_sum = 0
            else:
                right += 1
                
        return max_sum

动态规划是解决这个问题的一种常见方法,而且不需要显式地编写逻辑来决定何时丢弃前面的数。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 0: 
            return 0
            
        dp = [0] * n
        dp[0] = nums[0]
        for i in range(1, n):
            dp[i] = max(nums[i], dp[i-1] + nums[i])
        return max(dp)

Kadane’s Algorithm 是一个求解“最大子数组和”问题的经典算法,核心思想实际上是一种简化版本的动态规划。

对于最大子数组和问题,滑动窗口的直观想法是:当当前和为正数时,继续扩展窗口(增加元素),因为这会增加总和;但当当前和为负数时,应该移动窗口的左边界到下一个元素,从而开始一个新的子数组,因为一个负的子数组和不会增加后续元素的和。

Kadane’s Algorithm 也遵循了这个逻辑,但它不显式地维护窗口的左边界和右边界。它简单地遍历数组,通过 current_max 来隐式地跟踪潜在的滑动窗口。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        current_sum = 0
        max_sum = nums[0]
        
        for num in nums:
            current_sum = max(num, current_sum + num)
            max_sum = max(max_sum, current_sum)
        
        return max_sum

这个问题还可以用分治法解决,分治法的基本思想是将大问题分解成小问题,然后合并小问题的解来得到大问题的解。

如果只有一个元素,那么最大子数组和就是这个元素本身。

如果有多个元素,我们将数组从中间划分为两个部分,那么最大子数组和要么在左半部分,要么在右半部分,要么跨越了中点。对于前两种情况,我们可以用递归的方式解决。对于第三种情况,我们可以分别从中点向两边扩展,找到最大的和。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        # 定义一个辅助函数,用于找到跨越中点的最大子数组和
        def findMaxCrossingSubarray(nums, low, mid, high):
            left_sum = float('-inf')
            total = 0
            for i in range(mid, low - 1, -1):
                total += nums[i]
                if total > left_sum:
                    left_sum = total

            right_sum = float('-inf')
            total = 0
            for i in range(mid + 1, high + 1):
                total += nums[i]
                if total > right_sum:
                    right_sum = total

            return left_sum + right_sum

        def findMaxSubarray(nums, low, high):
            if low == high:
                return nums[low]

            mid = (low + high) // 2

            return max(
                findMaxSubarray(nums, low, mid),
                findMaxSubarray(nums, mid + 1, high),
                findMaxCrossingSubarray(nums, low, mid, high)
            )

        return findMaxSubarray(nums, 0, len(nums) - 1)

分治法不仅可以解决区间 [0,n−1],还可以用于解决任意的子区间 [l,r] 的问题。如果我们把 [0,n−1] 分治下去出现的所有子区间的信息都用堆式存储的方式记忆化下来,即建成一棵真正的树之后,我们就可以在 O(logn) 的时间内求到任意区间内的答案,我们甚至可以修改序列中的值,做一些简单的维护,之后仍然可以在 O(logn) 的时间内求到任意区间内的答案,对于大规模查询的情况下,这种方法的优势便体现了出来。

56. 合并闭区间

以数组 intervals 表示若干个闭区间的集合,其中单个闭区间为 intervals[i] = [start_i, end_i] 。请你合并所有重叠的闭区间,并返回 一个不重叠的闭区间数组,该数组需恰好覆盖输入中的所有闭区间 。

示例:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]

Solution

class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        intervals.sort(key=lambda x: x[0])

        merged = []
        for interval in intervals:
            # 如果列表为空,或者当前区间与上一区间不重合,直接添加
            if not merged or merged[-1][1] < interval[0]:
                merged.append(interval)
            else:
                # 否则的话,我们就可以与上一区间进行合并
                merged[-1][1] = max(merged[-1][1], interval[1])

        return merged

189. 轮转数组

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]

Solution

我们可以使用额外的数组来将每个元素放到正确的位置。新的位置可以通过(i + k) % n来计算,其中n是数组的长度。然后我们再把额外的数组复制回原数组。

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        n = len(nums)
        rotated_nums = [0] * n
        for i in range(n):
            rotated_nums[(i + k) % n] = nums[i]
        nums[:] = rotated_nums

从另一个角度,我们可以将被替换的元素保存在变量 temp 中,从而避免了额外数组的开销。

我们可以把每个元素都移到正确的位置,然后再把新位置的元素移到它的正确位置,这样形成一个替换的环。为了确保每个元素都移动到正确的位置,我们需要从每个被替换的元素开始一个新的环,这样可以确保我们移动了所有元素。

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        n = len(nums)
        k %= n
        
        start = count = 0
        while count < n:
            current, temp = start, nums[start]
            while True:
                next_idx = (current + k) % n
                nums[next_idx], temp = temp, nums[next_idx]
                current = next_idx
                count += 1
                
                if start == current:
                    break
            start += 1

我们还可以用数组翻转的方法来解决这个问题。

这个方法基于这样一个事实:当我们把k个尾部元素旋转到头部后,原先的第一个元素就被放到第k + 1个位置。因此,我们可以首先把所有元素翻转,这样尾部的k个元素就被放到头部。然后我们把前k个元素翻转,再把剩下的元素翻转,就能得到我们需要的结果。

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        n = len(nums)
        k %= n

        def reverse(i, j):
            while i < j:
                nums[i], nums[j] = nums[j], nums[i]
                i += 1
                j -= 1

        reverse(0, n - 1)
        reverse(0, k - 1)
        reverse(k, n - 1)

238. 除自身以外数组的乘积

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。

示例:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]

Solution

这个问题的关键在于找到一种不直接计算除nums[i]之外的所有元素乘积的方法。一个直观的想法是先计算整个数组的乘积,然后对每个元素,用总乘积除以它。但这种方法在元素为0时会遇到问题,所以需要另一种方法。

为了找到除nums[i]之外的所有元素的乘积,我们可以先计算出数组的每个元素的前缀乘积和后缀乘积。前缀乘积指的是当前元素之前所有元素的乘积,后缀乘积则是当前元素之后的所有元素的乘积。

class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]: 
        n = len(nums)
        
        prefix = [1] * n
        suffix = [1] * n
        result = [1] * n
        
        for i in range(1, n):
            prefix[i] = prefix[i - 1] * nums[i - 1]
        
        for i in range(n - 2, -1, -1):
            suffix[i] = suffix[i + 1] * nums[i + 1]
            result[i] = prefix[i] * suffix[i]
        result[-1] = prefix[-1]  # 更新最后一个元素
        
        return result

136. 只出现一次的数字

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

示例:
输入:nums = [2,2,1]
输出:1

Solution

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        res = 0
        for num in nums:
            res ^= num
        return res

169. 多数元素

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

Solution

75. 颜色分类

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

必须在不使用库内置的 sort 函数的情况下解决这个问题。

示例:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

Solution

31. 下一个排列

整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。

例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。

必须 原地 修改,只允许使用额外常数空间。

示例:
输入:nums = [1,2,3]
输出:[1,3,2]

Solution

287. 寻找重复数

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。

你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

示例:
输入:nums = [1,3,4,2,2]
输出:2

Solution

把数组看作是一个链表,其中数组的索引是节点,数组的值是下一个节点的指针。由于存在重复的数字,链表中一定存在环。

class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        slow, fast = nums[0], nums[nums[0]]
        while slow != fast:
            slow = nums[slow]
            fast = nums[nums[fast]]
        
        slow = 0
        while slow != fast:
            slow = nums[slow]
            fast = nums[fast]
        return slow

41. 缺失的第一个正数

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。

请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。

Solution

猜你喜欢

转载自blog.csdn.net/weixin_45427144/article/details/131395132