1.只出现一次的数字(136,简单)
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例1:
输入:[2,2,1]
输出:1
示例2:
输入:[4,1,2,1,2]
输出:4
解法一:
我首先想到的比较蠢的方法,直接存到字典里,把数组里的数当作key,如果字典里没有这个键值就设为1,有的话就加一,最后取出值为1的key值。
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
dict = {}
for item in nums:
if item in dict:
dict[item] += 1
else:
dict[item] = 1
for key in dict:
if dict[key] == 1:
return key
解法二:
首先要了解一个事情,异或操作,两个相同的数异或结果为0,一个数跟0异或是这个数本身,异或是对于二进制来说的,一个数异或自己本身结果为零(全部二进制位相同) 一个数异或零结果为这个数本身(0和0异或为0,1和0异或为1,所以结果还是原来的数) 而一个数异或另一个数两次就是相当于一个数异或零 所以结果还是这个数本身。所以当数组中只有一个数出现了一次,其他数都出现两次时,初始化一个0对数组里每一个数进行异或,结果就是这个数本身。
python的异或符号为^
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
num = 0
for i in nums:
num ^= i
return num
2.求众数(169,简单)
给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在众数。
示例1:
输入: [3,2,3]
输出: 3
示例2:
输入: [2,2,1,1,1,2,2]
输出: 2
解法一:
和上一题思路一样,定义一个字典,key值为数组的数字,value为它出现的次数,最后从字典里找出value值大于n/2的值。
class Solution(object):
def majorityElement(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
dict = {}
for i in nums:
if i in dict:
dict[i] += 1
else:
dict[i] = 1
num = len(nums)/2
for i in dict:
if dict[i]>num:
return i
改进:
可以用max函数,关于max函数可以参考这篇文章
dict = {}
for i in nums:
if i in dict:
dict[i] += 1
else:
dict[i] = 1
return max(dict.items(), key=lambda x: x[1])[0]
dict.items()返回的是可以遍历的(键,值)元组数组
print(dict.items())
# dict_items([(1, 3), (2, 4)])
print(max(dict.items(), key=lambda x: x[1]))
# (2,6)
print(max(dict.items(), key=lambda x: x[1])[0])
# 2
解法二:
比较鸡贼的方法,由于众数的数量是大于n/2的,所以我们把nums用sorted函数排序,得到的list的第n/2个就是所求答案。
class Solution(object):
def majorityElement(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
return sorted(nums)[len(nums)/2]
3.搜索二维矩阵Ⅱ(240,中等)
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:
- 每行的元素从左到右升序排列。
- 每列的元素从上到下升序排列。
示例:
现有矩阵matrix如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定target = 5,返回true。
给定target = 20, 返回false。
解法一:
暴力解法,遍历数组
class Solution(object):
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[int]]
:type target: int
:rtype: bool
"""
if len(matrix) == 0 or len(matrix[0]) == 0:
return False
for i in range(len(matrix)):
for j in range(len(matrix[i])):
if matrix[i][j] == target:
return True
return False
写的更简洁的方法:
class Solution:
def searchMatrix(self, matrix, target):
if not matrix:
return False
for x in matrix:
for c in x:
if target== c:
return True
return False
解法二:
对于每一行用二分法:
class Solution(object):
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[int]]
:type target: int
:rtype: bool
"""
for i in range(len(matrix)):
first = 0
last = len(matrix[i]) - 1
while first <= last:
mid = (last + first) // 2
if target < matrix[i][mid]:
last = mid - 1
elif target > matrix[i][mid]:
first = mid +1
else:
return True
return False
这种方法没有利用到每一列也是排序好的信息,不是最佳。
解法三:
我们可以观察到,对于矩阵的右上角,所有在它下面的数都比他大,所有在他左边的数都比他小,所以我们可以从右上角的数开始与target对比,target比它大行数就加一,比它小列数就减一。
class Solution(object):
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[int]]
:type target: int
:rtype: bool
"""
if not matrix:
return False
row = 0
col = len(matrix[0]) - 1
rows = len(matrix)-1
while row <= rows and col >= 0:
if target == matrix[row][col]:
return True
elif target > matrix[row][col]:
row += 1
else:
col -= 1
return False
4.合并两个有序数组(88,简单)
给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明:
- 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
- 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解法一:
class Solution(object):
def merge(self, nums1, m, nums2, n):
"""
:type nums1: List[int]
:type m: int
:type nums2: List[int]
:type n: int
:rtype: None Do not return anything, modify nums1 in-place instead.
"""
while m > 0 and n > 0:
if nums1[m-1] > nums2[n-1]:
nums1[m+n-1] = nums1[m-1]
m -= 1
else:
nums1[m+n-1] = nums2[n-1]
n -= 1
if n > 0:
nums1[:n] = nums2[:n]
return None
5.验证回文串(125,简单)
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
回文串就是正读反读相同。
说明: 本题中,我们将空字符串定义为有效的回文串。
示例1:
输入: "A man, a plan, a canal: Panama"
输出: true
示例2:
输入: "race a car"
输出: false
这道题主要是对字符串的操作不太熟悉,思路很清晰,把字符串转换成只包含字母和数字的列表,之后比较原列表和倒序后的列表是否相同就可以了。
解法一:
class Solution(object):
def isPalindrome(self, s):
"""
:type s: str
:rtype: bool
"""
list = []
for i in s:
if i.isalpha() or i.isdigit():
list.append(i.lower())
return True if list == list[::-1] else False
后来发现,其实没必要转成列表,字符串也可以实现同样的操作,原理一样。
解法二:
class Solution(object):
def isPalindrome(self, s):
"""
:type s: str
:rtype: bool
"""
s = s.lower()
newStr = ""
for i in s:
if i.isalnum():
newStr += i
return newStr==newStr[::-1]
最后实测,用第一种列表的方法速度要快了十倍左右。
解法三:
最简洁的解法,用到了一个高级的函数filter。
filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。
该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。
filter(function, iterable)
# funcion --判断函数
# iterable --可迭代对象
# 注意: Pyhton2.7 返回列表,Python3.x 返回迭代器对象
python2解法:
class Solution(object):
def isPalindrome(self, s):
"""
:type s: str
:rtype: boolp
"""
s = filter(str.isalnum, str(s.lower()))
return s == s[::-1]
python3解法:
class Solution:
def isPalindrome(self, s: str) -> bool:
s = list(filter(str.isalnum, s.lower()))
return s == s[::-1]
可以看到python3的filter返回对象必须list之后才能继续操作,否则会报错:
Line 4: TypeError: 'filter' object is not subscriptable
6.两数之和Ⅱ-输入有序数组(167,简单)
给定一个已按照升序排列的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1必须小于index2。
说明:
- 返回的下标值(index1 和 index2)不是从零开始的。
- 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
解法:
这题主要还是抓住题目中数组是升序排列的条件,可以设置两个指针,分别从数组的最左和最右开始判断,假如比目标值小,左边的指针就加一,比目标值大右边的指针就减一。
class Solution(object):
def twoSum(self, numbers, target):
"""
:type numbers: List[int]
:type target: int
:rtype: List[int]
"""
left = 0
right = len(numbers)-1
while left<right:
if numbers[left]+numbers[right] == target:
return [left+1, right+1]
elif numbers[left]+numbers[right] < target:
left+=1
else:
right-=1
7.数组中的第K个最大元素(215,中等)
在未排序的数组中找到第k个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
解法一:
第k大的元素,只要把数组排序取第K个或第-k个元素就可以了。
list.sort(cmp=None, key=None, reverse=False)
cmp -- 可选参数, 如果指定了该参数会使用该参数的方法进行排序。
key -- 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
reverse -- 排序规则,reverse = True 降序, reverse = False 升序(默认)。
class Solution(object):
def findKthLargest(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
nums.sort()
return nums[-k]
解法二:
用了快排的思想,快排的思想就是找一个基准点,把比基准点大的放到左边,比基准点小的放到右边,这样基准点的位置就是正确的,即左边都比他大,右边都比他小,在这道题中,不需要把所有的数组全部排好,假如排好基准点恰好在第K个位置上,那么他就是第k大的元素。
快排的实现思想值得思考。首先设置一个index,然后循环list,比较list当前值与基准值(这里取得最后一个元素)的大小,当比基准值大时,就把list[index]与list[i]互换,之后index加一,最后再把list[index]与list[high]互换,就完成了一次操作,之后再进行判断,来更改high或low的位置继续进行这个操作。
class Solution(object):
def findKthLargest(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
low, high = 0, len(nums)-1
while low <= high:
pivot = self.findpivot(nums, high, low)
if pivot == k-1:
return nums[pivot]
elif pivot > k-1:
high = pivot-1
else:
low = pivot+1
def findpivot(self, nums, high, low):
index = low
pivot = nums[high]
for i in range(low, high):
if nums[i] >= pivot:
nums[index], nums[i] = nums[i], nums[index]
index += 1
nums[index], nums[high] = nums[high], nums[index]
return index
解法三:
维护一个最小堆,堆可以看作一个完全二叉树,最小堆的堆顶一定是最小的那个数,最大堆堆顶一定是最大的。
python中内置的堆模块为heapq
heaqp模块提供了堆队列算法的实现,也称为优先级队列算法。
要创建堆,请使用初始化为[]的列表,或者可以通过函数heapify()将填充列表转换为堆。
提供以下功能:
Usage:
heap = [] # creates an empty heap
heappush(heap, item) # pushes a new item on the heap
item = heappop(heap) # pops the smallest item from the heap
item = heap[0] # smallest item on the heap without popping it
heapify(x) # transforms list into a heap, in-place, in linear time
item = heapreplace(heap, item) # pops and returns smallest item, and adds
# new item; the heap size is unchanged
用堆来实现就比较简单了,循环列表加入堆,当堆的size大于k了就弹出堆顶,最后的堆顶就是第k大的值。
class Solution(object):
def findKthLargest(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
import heapq
heap = []
heapq.heapify(heap)
for num in nums:
heapq.heappush(heap, num)
if len(heap)>k:
heapq.heappop(heap)
return heap[0]
8.前K个高频元素(347,中等)
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例2:
输入: nums = [1], k = 1
输出: [1]
说明:
- 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
- 你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
解法一:
用一个字典来统计频次,最后根据字典的value值进行排序,然后循环k次放到列表里返回。
根据字典的value或者key排序要可以用lamada表达式,关于lamada表达式:
Python提供了很多函数式编程的特性,如:map、reduce、filter、sorted等这些函数都支持函数作为参数,lambda函数就可以应用在函数式编程中。如下:
# 需求:将列表中的元素按照绝对值大小进行升序排列
list1 = [3,5,-4,-1,0,-2,-6]
sorted(list1, key=lambda x: abs(x))
当然,也可以如下:
list1 = [3,5,-4,-1,0,-2,-6]
def get_abs(x):
return abs(x)
sorted(list1,key=get_abs)
只不过这种方式的代码看起来不够Pythonic
class Solution(object):
def topKFrequent(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
dict = {}
re = []
for num in nums:
if num in dict:
dict[num] += 1
else:
dict[num] = 1
s = sorted(dict.items(), key=lambda x:x[1], reverse=True)
print(s)
for i in range(k):
re.append(s[i][0])
return re
这种解法的时间复杂度是:
O(nlogn)
不符合题目要求(还是不太懂怎么计算复杂度)
解法二:
求出出现频率最高的k个数,当我们通过dict统计出频率之后,其实和上一题一样都是一个排序之后输出前k大个数,所以依然可以使用堆来实现。
class Solution(object):
def topKFrequent(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
dict = {}
list = []
re = []
for num in nums:
if num in dict:
dict[num] += 1
else:
dict[num] = 1
heapq.heapify(list)
for key, value in dict.items():
if len(list) < k:
heapq.heappush(list, (value, key))
elif value > list[0][0]:
heapq.heapreplace(list, (value, key))
while list:
re.append(heapq.heappop(list)[1])
return re
复杂度分析:
- 时间复杂度:O(nlogk),其中 n 表示数组的长度。首先,遍历一遍数组统计元素的频率,这一系列操作的时间复杂度是 O(n) 的;接着,遍历用于存储元素频率的 map,如果元素的频率大于最小堆中顶部的元素,则将顶部的元素删除并将该元素加入堆中,这一系列操作的时间复杂度是 O(nlogk) 的;最后,弹出堆中的元素所需的时间复杂度是 O(klogk) 的。因此,总的时间复杂度是 O(nlogk) 的。
- 空间复杂度:O(n),最坏情况下(每个元素都不同),map 需要存储 n 个键值对,优先队列需要存储 k 个元素,因此,空间复杂度是 O(n) 的。
解法三:
最后,为了进一步优化时间复杂度,可以采用桶排序(bucket sort),即用空间复杂度换取时间复杂度。
第一步和解法二相同,也是统计出数组中元素的频次。接着,将数组中的元素按照出现频次进行分组,即出现频次为 i 的元素存放在第 i 个桶。最后,从桶中逆序取出前 k 个元素。
桶排序详见这里
class Solution(object):
def topKFrequent(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
dict = {}
re = []
for num in nums:
if num in dict:
dict[num] += 1
else:
dict[num] = 1
bucket = [[] for _ in range(len(nums) + 1)]
# 这里的输出是 [[], [], [], [], [], [], [], [], [], []]
for key, value in dict.items():
bucket[value].append(key)
for i in range(len(nums), -1, -1): # range(start,stop,step) 不包含stop,所以到-1,就是到0
if bucket[i]:
re.extend(bucket[i])
if len(re) >= k:
break
return re[:k]
extend和append的区别就是,append会把整体当作一个元素加到列表里,extend会把整体拆成元素加进去,如:
music_media = ['compact disc', '8-track tape', 'long playing record']
new_media = ['DVD Audio disc', 'Super Audio CD']
music_media.append(new_media)
print music_media
>>>['compact disc', '8-track tape', 'long playing record', ['DVD Audio disc', 'Super Audio CD']]
复杂度分析
- 时间复杂度:O(n),其中n表示数组的长度。
- 空间复杂度:O(n)