数组
LeetCode中的数组题目是非常常见的题型之一,考察的是对数组的基本操作和一些高级算法的运用。数组是一种线性数据结构,它可以存储同一类型的元素,这些元素可以通过索引来访问。数组的操作包括:插入、删除、查找、遍历等。以下是一些常见的LeetCode数组题目类型:
-
数组排序
这类题目主要考察排序算法的实现和优化。常见的排序算法有冒泡排序、插入排序、选择排序、归并排序、快速排序等。LeetCode中的数组排序题目包括:
- 88.合并两个有序数组
- 215.数组中的第K个最大元素
- 287.寻找重复数
- 75.颜色分类
-
数组查找
这类题目主要考察查找算法的实现和优化。常见的查找算法有线性查找、二分查找、哈希查找等。LeetCode中的数组查找题目包括:
- 33.搜索旋转排序数组
- 153.寻找旋转排序数组中的最小值
- 240.搜索二维矩阵 II
- 4.寻找两个正序数组的中位数
-
数组操作
这类题目主要考察对数组的一些基本操作的实现和优化。包括去重、最大子序列和、最长上升子序列、数组旋转等。LeetCode中的数组操作题目包括:
- 26.删除有序数组中的重复项
- 53.最大子序和
- 300.最长上升子序列
- 189.旋转数组
-
数组变换
这类题目主要考察对数组变换的实现和优化。包括对称变换、交换变换、数值变换等。LeetCode中的数组变换题目包括:
- 31.下一个排列
- 48.旋转图像
- 56.合并区间
- 289.生命游戏
总的来说,LeetCode中的数组题目难度从简单到困难不一,需要具备扎实的数组操作和算法基础知识,同时也需要具备灵活的思维和创新能力,能够根据问题特点灵活选择算法和数据结构。
169. 多数元素 - 力扣(Leetcode)
给定一个大小为 n **的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: nums = [3,2,3]
输出: 3
示例 2:
输入: nums = [2,2,1,1,1,2,2]
输出: 2
提示:
n == nums.length1 <= n <= 5 * 104-109 <= nums[i] <= 109
进阶: 尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
class Solution:
def majorityElement(self, nums: List[int]) -> int:
count = Counter(nums)
for k,v in count.items():
if v > len(nums) // 2:
return k
该算法的思路是先使用 collections.Counter 函数统计每个元素在数组中出现的次数,然后遍历 Counter 的字典对象,找到出现次数超过数组长度一半的元素并返回。
算法的时间复杂度为 O(n),因为需要对整个数组进行遍历统计。空间复杂度为 O(n),因为需要使用 Counter 对象来存储每个元素出现的次数。
上边是一个人人都能想到的思路,所以我们看一下进阶解法,如何只使用O(n)的额外空间。
摩尔投票法
摩尔投票法是一种用来寻找数组中出现次数超过一半的元素的算法,其基本思想是不断消除不同元素之间的差异,最终剩余的元素就是出现次数超过一半的元素。
算法的具体过程如下:
-
假设数组中的第一个元素为候选元素,计数器初始化为1。
-
从第二个元素开始遍历数组,如果当前元素与候选元素相同,则计数器加1,否则计数器减1。
-
如果计数器变为了0,则将当前元素作为候选元素,并将计数器重新初始化为1。
-
遍历完数组后,候选元素就是出现次数超过一半的元素。
该算法的时间复杂度为 O(n),空间复杂度为 O(1)。
摩尔投票法的正确性可以通过反证法证明。
假设有一个数 x 出现的次数超过了数组长度的一半,我们用 count 表示目前 x 出现的次数,遍历整个数组,当遇到 x 时,count 加 1,否则减 1。如果 count 变成了 0,则说明目前并没有超过一半的数,此时从下一个数开始重新计数。最后如果 x 真的出现的次数超过了数组长度的一半,那么最终遍历完成后 x 的计数一定大于 0。
假设有两个数 x 和 y 都出现的次数超过了数组长度的一半,用 count_x 和 count_y 分别表示两个数目前出现的次数。同样进行遍历,当遇到 x 或 y 时,对应的计数加 1,否则减 1。当 count_x 和 count_y 都变成了 0 时,说明目前并没有超过一半的数,此时从下一个数开始重新计数。最终,如果 x 和 y 都真的出现的次数超过了数组长度的一半,那么最终遍历完成后 count_x 和 count_y 一定都大于 0。此时,如果 x 和 y 不相等,那么我们可以认为数组中存在一些数先后出现,抵消掉了 x 和 y 的计数,导致它们的计数都变成了 0,这是矛盾的。因此,x 和 y 必须相等。
综上所述,摩尔投票法可以正确地找出出现次数超过数组长度一半的数。
所以使用摩尔投票法的代码为:
class Solution:
def majorityElement(self, nums: List[int]) -> int:
res,count = 0,0
for n in nums:
if count == 0:
res = n
count += 1
else:
if n == res:
count += 1
else:
count -= 1
return res
这个实现是摩尔投票法的具体实现,跟之前的实现原理是一样的。代码中 res 存储的是当前的候选众数,count 记录了当前候选众数的出现次数。遍历整个数组,如果 count 为0,则当前数字成为候选众数;否则如果当前数字与候选众数相同,将 count 加1;否则将 count 减1。最后剩下的候选众数就是众数。
189. 轮转数组 - 力扣(Leetcode)
给定一个整数数组 nums,将数组中的元素向右轮转 k **个位置,其中 k **是非负数。
示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
示例 2:
输入: nums = [-1,-100,3,99], k = 2
输出: [3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]
提示:
1 <= nums.length <= 105-231 <= nums[i] <= 231 - 10 <= k <= 105
进阶:
- 尽可能想出更多的解决方案,至少有 三种 不同的方法可以解决这个问题。
- 你可以使用空间复杂度为
O(1)的 原地 算法解决这个问题吗?
class Solution:
def rotate(self, nums, k):
k %= len(nums)
reverse(nums, 0, len(nums) - 1)
reverse(nums, 0, k - 1)
reverse(nums, k, len(nums) - 1)
def reverse(nums,l,r):
while l < r:
nums[l], nums[r] = nums[r], nums[l]
r -= 1
l += 1
这段代码实现了将列表 nums 向右旋转 k 步的功能,其中 k 是非负整数。该算法采用了三次翻转数组的方法来实现旋转:
-
将整个列表翻转。
-
将前
k个元素翻转。 -
将剩下的元素翻转。
最终得到的列表就是向右旋转 k 步后的结果。
该算法的时间复杂度为 ,其中 是列表的长度,空间复杂度为 ,因为只用了常数级别的额外空间来保存中间结果。算法的正确性也可以通过手动模拟旋转过程来验证。
238. 除自身以外数组的乘积 - 力扣(Leetcode)
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请不要使用除法, 且在 O(n) 时间复杂度内完成此题。
示例 1:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
提示:
2 <= nums.length <= 105-30 <= nums[i] <= 30- 保证 数组
nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内
进阶: 你可以在 O(1) 的额外空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)
当我是个笨比的时候,我还在考虑math.prod() + 数组切片。但是动脑想想都会超时。
所以我决定看看别人咋做的。直到我发现了前后缀结果乘法。
class Solution:
def productExceptSelf(self, nums):
ans = []
preMul = [1]
tailMul = [1]
for i in range(len(nums)-1):
preMul.append(nums[i]*preMul[-1])
tailMul.append(nums[len(nums)-1-i]*tailMul[-1])
tailMul.reverse()
for i in range(len(nums)):
ans.append(preMul[i]*tailMul[i])
return ans
该代码实现了一个函数 productExceptSelf,接受一个数组 nums 作为输入,返回一个新数组 ans,其中 ans[i] 等于原数组 nums 中除了 nums[i] 以外所有元素的乘积。具体实现如下:
- 首先定义一个空数组
ans。 - 然后定义两个数组
preMul和tailMul,preMul用来记录nums[i]左边所有元素的乘积,tailMul用来记录nums[i]右边所有元素的乘积。preMul[0]和tailMul[-1]都初始化为 1。 - 通过循环遍历数组
nums,分别计算出preMul和tailMul的所有值。 - 由于
tailMul的顺序是从右往左的,所以需要将其反转过来。 - 再次遍历数组
nums,计算ans中每个元素的值,并将其加入到ans数组中。 - 最后返回
ans数组。
这种方法的时间复杂度是 ,空间复杂度也是 。
我们看一下如何实现进阶目标:
class Solution:
def productExceptSelf(self, nums: [int]) -> [int]:
res = [1]
pre = 1
tail = 1
for i in range(len(nums) - 1): # bottom triangle
pre *= nums[i]
res.append(pre)
for i in range(len(nums) - 1, 0, -1): # top triangle
tail *= nums[i]
res[i - 1] *= tail
return res
这个题解的思路和前一个类似,但是代码更简洁。其主要思路是先遍历一遍数组,计算每个元素左边所有元素的乘积。然后再遍历一遍数组,计算每个元素右边所有元素的乘积,并将左边的乘积乘上右边的乘积作为最终结果。代码中,res用于记录每个元素左边所有元素的乘积,pre用于计算左边乘积,tail用于计算右边乘积。遍历时,先计算左边乘积,然后在计算右边乘积,最后将左边乘积和右边乘积相乘即可。