初学python的我开始Leetcode题-17
提示:100道LeetCode热题-17主要是一些技巧,包括五题:只出现一次的数字、多数元素、颜色分类、下一个排列、寻找重复数。由于初学,所以我的代码部分仅供参考。
前言
这是Leetcode100题的最后一点啦,记得时时复习哇!加油~
提示:以下是本篇文章正文内容,下面结果代码仅供参考
题目1:只出现一次的数字
1.题目要求:
题目如下:
给你一个 非空 整数数组
nums
,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
示例 1 :
输入:nums = [2,2,1]
输出:1
示例 2 :
输入:nums = [4,1,2,1,2]
输出:4
示例 3 :
输入:nums = [1]
输出:1
提示:
1 <= nums.length <= 3 *
-3 *
<= nums[i] <= 3 *
- 除了某个元素只出现一次以外,其余每个元素均出现两次。
代码框架已经提供如下:
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
2.结果代码:
class Solution(object):def singleNumber(self, nums):""":type nums: List[int]:rtype: int"""result = 0for num in nums:result ^= numreturn result
说明:
可能的解决方案:
方法一:哈希表
使用哈希表(如 Python 的 dict
或 collections.Counter
)来统计每个数字出现的次数。然后遍历哈希表,找出出现次数为1的数字。
-
时间复杂度:O(n),因为需要遍历数组统计次数,再遍历哈希表找出单元素。
-
空间复杂度:O(n),因为哈希表需要存储最多 n/2 + 1 个键值对。
问题:虽然时间复杂度满足 O(n),但空间复杂度是 O(n),不满足 O(1) 的要求。因此,这种方法不符合题目要求。
方法二:排序后查找
先对数组排序,然后遍历数组,检查相邻元素是否相同。因为相同的元素会相邻出现,所以可以找到单独的元素。
-
时间复杂度:排序需要 O(n log n),遍历需要 O(n),总体是 O(n log n)。
-
空间复杂度:如果使用原地排序(如 Timsort),空间复杂度可能是 O(1) 或 O(log n)。
问题:时间复杂度不满足 O(n) 的要求。
方法三:数学方法
利用数学性质,所有数字的和可以表示为:
sum(nums) = 2 * sum(set(nums)) - single_number
因此:
single_number = 2 * sum(set(nums)) - sum(nums)
-
时间复杂度:O(n),因为
sum
和set
都是 O(n)。 -
空间复杂度:
set(nums)
需要 O(n) 空间。
问题:空间复杂度不满足 O(1)。
方法四:位运算(异或)
异或(XOR)运算有以下性质:
-
a ^ a = 0
(任何数异或自己等于 0)。 -
a ^ 0 = a
(任何数异或 0 等于自己)。 -
异或运算满足交换律和结合律:
a ^ b ^ a = (a ^ a) ^ b = 0 ^ b = b
。
因此,可以将所有数字进行异或运算,出现两次的数字会互相抵消为 0,最终剩下的就是只出现一次的数字。
-
时间复杂度:O(n),只需遍历数组一次。
-
空间复杂度:O(1),只需要一个变量存储结果。
综上所述,这里使用异或(XOR)。
代码解释:
-
初始化
result
为 0:因为0 ^ x = x
,0 是异或运算的单位元。 -
遍历数组
nums
:对每个元素num
,执行result ^= num
。 -
返回
result
:遍历结束后,result
就是只出现一次的数字。
题目2:多数元素
1.题目要求:
题目如下:
给定一个大小为
n
的数组nums
,返回其中的多数元素。多数元素是指在数组中出现次数 大于⌊ n/2 ⌋
的元素。你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入:nums = [3,2,3] 输出:3示例 2:
输入:nums = [2,2,1,1,1,2,2] 输出:2提示:
n == nums.length
1 <= n <= 5 *
-
<= nums[i] <=
进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
代码框架已经提供如下:
class Solution(object):
def majorityElement(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
2.结果代码:
class Solution(object):def majorityElement(self, nums):""":type nums: List[int]:rtype: int"""candidate = Nonecount = 0for num in nums:if count == 0:candidate = numcount += (1 if num == candidate else -1)return candidate
说明:
可能的解决方案
方法一:哈希表计数
使用哈希表(如 Python 的 dict
或 collections.Counter
)统计每个数字的出现次数,然后遍历哈希表找到出现次数大于 n/2
的数字。
-
时间复杂度:O(n),遍历数组统计次数需要 O(n),遍历哈希表需要 O(k)(k 是不同元素的数量,最坏为 n)。
-
空间复杂度:O(n),哈希表最多存储 n 个键值对。
问题:虽然时间复杂度满足 O(n),但空间复杂度是 O(n)。可以接受,但不是最优解。
方法二:排序后取中位数
多数元素的出现次数大于 n/2
,因此排序后,中间位置的元素一定是多数元素。
-
时间复杂度:O(n log n),排序需要时间。
-
空间复杂度:O(1) 或 O(log n)(取决于排序实现)。
问题:时间复杂度不满足 O(n)。
方法三:Boyer-Moore 投票算法
这是一种专门用于寻找多数元素的高效算法,满足 O(n) 时间和 O(1) 空间。
算法思路:
-
初始化候选元素
candidate
和计数器count
为 0。 -
遍历数组:
-
如果
count == 0
,将当前元素设为candidate
。 -
如果当前元素等于
candidate
,count
加 1;否则减 1。
-
-
遍历结束后,
candidate
就是多数元素。
综上所述,选择Boyer-Moore 投票算法。
代码解释:
-
初始化:
-
candidate
:当前候选的多数元素,初始为None
。 -
count
:当前候选元素的“净票数”,初始为 0。
-
-
遍历数组:
-
如果
count == 0
,说明之前的候选元素已被抵消完,重新选择当前元素作为候选。 -
如果当前元素等于
candidate
,count
加 1(支持票);否则减 1(反对票)。
-
-
返回结果:遍历结束后,
candidate
一定是多数元素(题目保证多数元素存在)。
题目3:颜色分类
1.题目要求:
题目如下:
给定一个包含红色、白色和蓝色、共
n
个元素的数组nums
,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。我们使用整数
0
、1
和2
分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0] 输出:[0,0,1,1,2,2]示例 2:
输入:nums = [2,0,1] 输出:[0,1,2]提示:
n == nums.length
1 <= n <= 300
nums[i]
为0
、1
或2
进阶:
- 你能想出一个仅使用常数空间的一趟扫描算法吗?
代码框架已经提供如下:
class Solution(object):
def sortColors(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
2.结果代码:
class Solution(object):def sortColors(self, nums):""":type nums: List[int]:rtype: None Do not return anything, modify nums in-place instead."""low, mid, high = 0, 0, len(nums) - 1while mid <= high:if nums[mid] == 0:nums[low], nums[mid] = nums[mid], nums[low]low += 1mid += 1elif nums[mid] == 1:mid += 1else: # nums[mid] == 2nums[high], nums[mid] = nums[mid], nums[high]high -= 1
说明:
这是经典的“荷兰国旗问题”,可以用三指针(或双指针)在一趟扫描内完成。
思路:
-
维护三个指针:
-
low
:0 的右边界(nums[0..low-1]
都是 0)。 -
mid
:当前考察的元素。 -
high
:2 的左边界(nums[high+1..n-1]
都是 2)。
-
-
初始:
low = 0
,mid = 0
,high = n - 1
。 -
遍历条件:
mid <= high
:-
nums[mid] == 0
:交换nums[low]
和nums[mid]
,然后low += 1
,mid += 1
。 -
nums[mid] == 1
:mid += 1
(直接跳过)。 -
nums[mid] == 2
:交换nums[mid]
和nums[high]
,然后high -= 1
(不移动mid
,因为交换后nums[mid]
可能是 0 或 1)。
-
题目4:下一个排列
1.题目要求:
题目如下:
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
- 例如,
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
的下一个排列。必须 原地 修改,只允许使用额外常数空间。
示例 1:
输入:nums = [1,2,3] 输出:[1,3,2]示例 2:
输入:nums = [3,2,1] 输出:[1,2,3]示例 3:
输入:nums = [1,1,5] 输出:[1,5,1]提示:
1 <= nums.length <= 100
0 <= nums[i] <= 100
代码框架已经提供如下:
class Solution(object):
def nextPermutation(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
2.结果代码:
class Solution(object):def nextPermutation(self, nums):""":type nums: List[int]:rtype: None Do not return anything, modify nums in-place instead."""n = len(nums)if n <= 1:return# Step 1: Find the first decreasing element from the endi = n - 2while i >= 0 and nums[i] >= nums[i + 1]:i -= 1if i >= 0:# Step 2: Find the first element larger than nums[i] from the endj = n - 1while j >= 0 and nums[j] <= nums[i]:j -= 1# Step 3: Swap nums[i] and nums[j]nums[i], nums[j] = nums[j], nums[i]# Step 4: Reverse the subarray from i+1 to endleft, right = i + 1, n - 1while left < right:nums[left], nums[right] = nums[right], nums[left]left += 1right -= 1
说明:
我们需要找到给定整数数组 nums
的下一个排列。下一个排列是指字典序比当前排列大的最小排列。如果当前排列已经是最大的字典序,则返回最小的字典序(即升序排列)。
关键概念:字典序:
字典序是比较两个序列的顺序,类似于字典中单词的顺序。例如:
-
[1,2,3] < [1,3,2]
(因为第二个元素 2 < 3)。 -
[3,2,1]
是最大的字典序,因此下一个排列是[1,2,3]
。
寻找下一个排列的步骤:
-
从后向前查找第一个下降的位置
i
:-
即找到最大的
i
使得nums[i] < nums[i+1]
。 -
如果找不到这样的
i
,说明整个数组是降序排列,已经是最大字典序,直接反转整个数组即可。
-
-
从后向前查找第一个大于
nums[i]
的位置j
:-
即找到最大的
j
使得nums[j] > nums[i]
。
-
-
交换
nums[i]
和nums[j]
。 -
反转
i+1
到末尾的子数组:-
因为
i+1
到末尾的子数组是降序排列,反转后变为升序,这是比原排列大的最小排列。
-
题目5:寻找重复数
1.题目要求:
题目如下:
给定一个包含
n + 1
个整数的数组nums
,其数字都在[1, n]
范围内(包括1
和n
),可知至少存在一个重复的整数。假设
nums
只有 一个重复的整数 ,返回 这个重复的数 。你设计的解决方案必须 不修改 数组
nums
且只用常量级O(1)
的额外空间。示例 1:
输入:nums = [1,3,4,2,2] 输出:2示例 2:
输入:nums = [3,1,3,4,2] 输出:3示例 3 :
输入:nums = [3,3,3,3,3] 输出:3提示:
1 <= n <=
nums.length == n + 1
1 <= nums[i] <= n
nums
中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次进阶:
- 如何证明
nums
中至少存在一个重复的数字?- 你可以设计一个线性级时间复杂度
O(n)
的解决方案吗?
代码框架已经提供如下:
class Solution(object):
def findDuplicate(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
2.结果代码:
class Solution(object):def findDuplicate(self, nums):""":type nums: List[int]:rtype: int"""# 快慢指针初始化slow = nums[0]fast = nums[nums[0]]# 第一阶段:找到相遇点while slow != fast:slow = nums[slow]fast = nums[nums[fast]]# 第二阶段:找到环入口slow = 0while slow != fast:slow = nums[slow]fast = nums[fast]return slow
说明:
可能的解决方案:
方法一:暴力法(哈希表)
使用哈希表记录出现过的数字,遇到重复的直接返回。
-
时间复杂度:O(n)。
-
空间复杂度:O(n)(哈希表需要额外空间)。
-
问题:不满足 O(1) 空间要求。
方法二:排序后查找
排序后,重复的数字会相邻出现。
-
时间复杂度:O(n log n)。
-
空间复杂度:O(1)(原地排序)。
-
问题:修改了原数组,不满足“不修改数组”的要求。
方法三:二分查找(基于值域)
利用 [1, n]
的数字范围,统计数字 <= mid
的个数:
-
初始化
left = 1
,right = n
。 -
计算
mid = (left + right) // 2
。 -
统计
nums
中<= mid
的数字个数count
。-
如果
count > mid
,说明重复数字在[left, mid]
。 -
否则,重复数字在
[mid + 1, right]
。
-
-
缩小范围,直到
left == right
。
-
时间复杂度:O(n log n)(二分需要 log n 次,每次统计需要 O(n))。
-
空间复杂度:O(1)。
-
问题:时间复杂度不满足 O(n)。
方法四:快慢指针(Floyd 判环法)(选择这个)
将 nums
视为链表,其中 nums[i]
表示 i -> nums[i]
的边。由于数字在 [1, n]
且长度为 n + 1
,必然存在环,重复的数字就是环的入口。
步骤:
-
初始化:
slow = nums[0]
,fast = nums[nums[0]]
。 -
相遇阶段:
-
slow = nums[slow]
。 -
fast = nums[nums[fast]]
。 -
直到
slow == fast
(相遇)。
-
-
找入口阶段:
-
slow = 0
。 -
slow
和fast
每次移动一步,直到slow == fast
(入口即重复数字)。
-
原理:
-
类似链表环检测,重复数字会导致多个指针指向同一位置。
-
数学证明:相遇点到环入口的距离等于起点到环入口的距离。
总结
对五种题型的技巧进行了学习,了解了部分有关python的相关知识,大家加油!