目录
一. 利用交换,值交换到对应的index上
剑指03:数组中重复的数字(找到任意一个重复数即可)
Leetcode41:缺失的第一个正数
Leetcode442:数组中重复的数据
Leetcode448:找到所有数组中消失的数字
剑指04:二维数组中的查找
剑指29:顺时针打印矩阵
剑指53-I:在排序数组中查找数字
剑指53-II:0到n-1中缺失的数字
Leetcode88:合并两个有序数组
Leetcode215:数组中的第K个最大元素(TopK问题;至少三种解法)!重点!
Leetcode349:两个数组的交集||Hashmap
Leetcode350:两个数组的交集II||双指针
Leetcode560:和为K的子数组(参考题解)
-->进阶版:Leetcode442:数组中重复的数据(找到所有的重复数)
-->微调版:Leetcode448:找到所有数组中消失的数字(重复数占领的index就是消失的数)
题目:找出数组中重复的数字。在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例:Input:[2, 3, 1, 0, 2, 5, 3];Output:2 or 3
基本思路:1. 暴力 2.hashmap 3.交换
class Solution03(object):
"""
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
"""
"""
方法1:暴力解法
最差时间复杂度为O(n*n); 最好时间复杂度O(1)
空间复杂度为O(1);
"""
def findRepeatNums(self, nums):
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] == nums[j]:
return nums[i]
"""
方法2.1:利用hashmap思想,构造字典(!巨坑!不必先构造一遍字典再来查找, 因为字典中的key是不会重复的, 构造字典的时候遇到重复的element时, 执行dict[value] = key, 会将原来相同value所对应的key给替换掉; )
所以可以边构造字典边判断;
最好时间复杂度为O(1), 最差时间复杂度为O(n), 平均复杂度也为O(n); search时间复杂度为O(1)
最好空间复杂度为O(1), 最差空间复杂度为O(n), 平均空间复杂度为O(n)
"""
def findRepeatNums_02(self, nums):
dict = {}
for key, value in enumerate(nums):
if dict.get(value) is not None:
return nums[dict.get(value)]
else:
dict[value] = key
"""
方法2.2:依然利用hashmap但是更加简洁的版本
不用care列表下标; 只需要care它的值;
"""
def findRepeatNums_03(self, nums):
dict = {}
for i in nums:
if i in dict:
return i
else:
dict[i] = 0 #这里可以赋任意值, 目的是存入i即可;
return -1
"""
方法3:排序再遍历
时间复杂度为O(nlogn): 排序阶段复杂度为O(nlogn), 遍历阶段时间复杂度为O(n)
空间复杂度为O(1)
"""
def findRepeatNums_04(self, nums):
nums.sort()
pre = nums[0]
for i in range(1, len(nums)):
if pre == nums[i]:
return pre
else: # pre != nums[i]
pre = nums[i]
return -1
"""
方法4.1:交换--时间和空间复杂度最优
注意题目中关键信息:nums中的数字都在 0~n-1 的范围内,也就是说可以将nums中的element一一放入跟它的值相等的下标位置;
归位过程中,发现待归位elements与被归位上的element相等则返回, otherwise交换;
时间复杂度为:O(n)
空间复杂度为:O(1)
"""
def findRepeateNums_05(self, nums):
for i in range(len(nums)):
element = nums[i]
if i != nums[i] and nums[element] == nums[i]:
return nums[i]
else:
nums[i], nums[element] = nums[element], nums[i]
return -1
"""
方法4.2:交换
找到第一个i != nums[i]即为重复元素;
"""
def findRepeatNumber(self, nums):
if not nums: return None
for i in range(len(nums)):
while nums[i] != nums[nums[i]]:
self.swap(nums, i, nums[i])
# nums[i], nums[nums[i]] = nums[nums[i]], nums[i] # i固定不变,将i位置上的元素不断送到对应的位置上去。一直换到i位置满足i=nums[i]为止;
for i in range(len(nums)):
if i != nums[i]:
return nums[i]
def swap(self, nums, i, j):
tmp = nums[i]
nums[i] = nums[j]
nums[j] = tmp
题目:给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。
示例:Input:[3, 4, -1, 1];Output:2
基本思路:index1, 2, 3, 4...对应着放数字1, 2, 3, 4...。再遍历一遍,不相等则返回该数字;
2个注意点:
1. 交换的时候用while,因为交换一次的话,只能保证换过去的element是去到正确的位置,换过来的又是不对的element,所以持续交换,直到换到正确的对应的element或者是out of size的element为止<这里指的是负数>;
2. 元素交换这里要用swap,原来的直接交换行不通
class Solution(object):
def firstMissingPositive(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if not nums: return 1
size = len(nums)
for i in range(size):
while nums[i] > 0 and nums[i] <= size and nums[i] != nums[nums[i]-1]: # 用while而不用if,因为持续需要换,换回来的可能也不满足条件
# nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1] # 只能用swap,而不能直接交换是因为赋值先后问题;
self.swap(nums, i, nums[i]-1)
for i in range(size):
if i + 1 != nums[i]:
return i + 1
return size + 1 # 如果正好一一对应,则返回size + 1 (eg. [1, 2, 3])
def swap(self, nums, i, j):
tmp = nums[i]
nums[i] = nums[j]
nums[j] = tmp
题目:给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。找到所有出现两次的元素。你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?
示例:[4,3,2,7,8,2,3,1] --> [2, 3]
基本思路:第一次遍历,对于每一个i,连续交换至无法交换为止;第二次遍历,不满足i+1 == num[i]即是重复元素
class Solution(object):
def findDuplicates(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
if not nums: return None
size = len(nums)
ans = []
for i in range(size):
while nums[i] != nums[nums[i] - 1]:
self.swap(nums, i, nums[i] - 1)
for i in range(size):
if i + 1 != nums[i]:
ans.append(nums[i])
return ans
def swap(self, nums, i, j):
tmp = nums[i]
nums[i] = nums[j]
nums[j] = tmp
题目:给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。找到所有在 [1, n] 范围之间没有出现在数组中的数字。您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。
基本思路:跟上一题完全一样,上一题输出重复元素,这一题输出被重复元素占掉位置的消失元素;
class Solution(object):
def findDisappearedNumbers(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
size = len(nums)
ans = []
for i in range(size):
while nums[i] != nums[nums[i] - 1]:
self.swap(nums, i, nums[i] - 1)
for i in range(size):
if i + 1 != nums[i]:
ans.append(i + 1)
return ans
def swap(self, nums, i, j):
tmp = nums[i]
nums[i] = nums[j]
nums[j] = tmp
题目:在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
基本思路:因为是有序的,所以可以从右上角开始,复杂度降为O(m+n);
class Solution04(object):
"""
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
"""
"""
方法1:暴力解法(遍历一遍)
时间复杂度O(n*m)--最好O(1),最差O(n*m)
空间复杂度O(1)
"""
def findNumberIn2DArray(self, matrix, target):
if len(matrix) == 0: return False
n, m = len(matrix), len(matrix[0])
for i in range(n):
for j in range(m):
if matrix[i][j] == target:
return True
return False
"""
方法2:引入标志数(题目的特殊性,由左到右,从上到下都是递增的,所有右上角位置以及左下角位置为特殊位置)
时间复杂度O(m+n)
空间复杂度O(1)
"""
def findNumberIn2DArray_02(self, matrix, target):
if len(matrix) == 0: return False
n, m = len(matrix), len(matrix[0])
for i in range(n):
if m - 1 >= 0 and matrix[i][m-1] == target: # 添加m - 1 >= 0是为了解决test [[]] 这种matrix时,index报错的情况;改进:用while loop
return True
if m - 1 >= 0 and matrix[i][-1] > target:
for j in range(m-1):
if matrix[i][j] == target:
return True
return False
"""
方法2:while loop -- 方法2中添加 m - 1 >= 0是为了解决test case中 [[]] 这种matrix时,index报错的情况;改进:用while loop
while loop一定会先设置好循环算子
"""
def findNumberIn2DArray_03(self, matrix, target):
i, j = 0, len(matrix[0]) - 1
while(i <= len(matrix) - 1 and j >= 0):
if matrix[i][j] > target:
j -= 1
elif matrix[i][j] < target:
i += 1
else:
return True
return False
class Solution29(object):
"""
玩技巧题,知道有这一种技巧就可以了!
"""
def spiralOrder(self, matrix):
if not matrix: return []
l, r, t, b = 0, len(matrix[0]) - 1, 0, len(matrix) - 1
ans = []
while True:
for i in range(l, r + 1): # from left to right
ans.append(matrix[t][i])
t += 1
if t > b: break
for i in range(t, b + 1): # from top to bottom
ans.append(matrix[i][r])
r -= 1
if r < l: break
for i in range(r, l - 1, -1 ): # from right to left
ans.append(matrix[b][i])
b -= 1
if b < t: break
for i in range(b, t - 1, -1): # from bottom to top
ans.append(matrix[i][l])
l += 1
if l > r: break
return ans
题目:统计一个数字在排序数组中出现的次数。
示例:nums[5, 7, 7, 8, 8, 10] target =8 --> output: 2
基本思路:1. 遍历即可;复杂度O(n);2. 二分法;复杂度O(logn) -- 先找到第一个target的位置,再往后遍历有几个target;
class Solution53(object):
"""
方法1:暴力统计
时间复杂度:O(n)
空间复杂度:O(1)
"""
def search(self, nums, target):
ans = 0
for i in range(len(nums)):
if nums[i] == target:
ans += 1
return ans
"""
方法2暴力改进版:因为是sorted list,搜索到该target之后, 就停止往后遍历了
时间复杂度:最好O(1), 最坏O(n), 平均O(n)
空间复杂度: O(1)
"""
def search_02(self, nums, target):
ans = 0
for i in range(len(nums)):
if nums[i] == target:
j = i # 因为是排序数组,找到跟target相等element之后就不用再往后遍历了
while j < len(nums) and nums[j] == target: # 用j遍历之后几个==target的elements;
j += 1
ans += 1
return ans
return ans
"""
方法3:先找到target值的位置,再继续往后遍历
"""
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
# 2020/05/28
if not nums: return 0
size = len(nums)
lo, hi = 0, size - 1
while lo < hi:
mid = (lo + hi) >> 1
if nums[mid] < target:
lo = mid + 1
else:
hi = mid
ans = 0
if nums[lo] == target:
ans = 1
while lo + 1 < size and nums[lo + 1] == nums[lo]:
ans += 1
lo += 1
return ans
题目:一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例:[0, 1, 2, 3, 4, 5, 6, 7, 9] --> 8
基本思路:标准的二分法;需要考虑到[0, 1, 2]这种case,循环会在lo=size-1处跳出
class Solution53(object):
"""
方法1:暴力法逐一比较
时间复杂度:最好是O(1),最坏是O(n),平均是O(n)
空间复杂度:O(1)
"""
def missingNumber(self, nums):
if nums == [0]: return 1
i = 0
while i <= len(nums) - 1:
if nums[i] != i:
return i
else:
i += 1
return i
"""
方法2:二分法查找
时间复杂度为O(logn)
空间复杂度:O(1)
"""
def missingNumber(self, nums):
if not nums: return 0
size = len(nums)
lo, hi = 0, size - 1
while lo < hi:
mid = (lo + hi) >> 1
if nums[mid] == mid:
lo = mid + 1
else:
hi = mid
if nums[lo] == lo: return lo + 1 # 考虑到[0, 1, 2]这种case,循环会在lo=size-1处跳出
return lo
题目:给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。说明:初始化 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]
基本思路:3个指针;从后往前遍历,p1是m尾部,p2是n尾部,p是nums1最尾部;完成遍历之后,如果p2中依然有值,全部插到nums1的头部;
时间复杂度:O(m+n)
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.
"""
p1, p2 = m - 1, n - 1
p = m + n - 1
while p1 >= 0 and p2 >= 0: # 从后往前遍历,p1是m尾部,p2是n尾部,p是nums1最尾部
if nums1[p1] <= nums2[p2]:
nums1[p] = nums2[p2]
p -= 1
p2 -= 1
else:
nums1[p1], nums1[p] = nums1[p], nums1[p1]
p -= 1
p1 -= 1
if p2 >= 0: # 完成遍历之后,如果p2中依然有值,全部插到nums1的头部
for i in range(p2+1):
nums1[i] = nums2[i]
基本思路:
1. 排序:时间复杂度O(nlogn) + 空间复杂度O(1)
2. 堆排序:时间复杂度O(n*logk) + 空间复杂度O(k)
3. partition思想:注意要随机选择pivot--利用randint(lo, hi),再交换lo and randint(lo, hi)的元素
时间复杂度O(n): 假设每次都可以均分 n + n/2 + n/4 + n/8 + ... + 1,最后结果为n*(1 + 1/2 + 1/4 + 1/8 +...),后者不会超过2;
最差的情况O(n*n),每次都选到最小或者最大元素,n + n-1 + ....1
空间复杂度O(1)
堆排序参考:
堆排序Python实现 AND 【Kick Algorithm】十大排序算法及其Python实现 AND 白话经典算法系列之七 堆与堆排序(清楚明白)
class Solution(object):
"""
方法1:排序
时间复杂度为O(nlogn))
空间复杂度为O(1)
"""
def findKthLargest(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
nums.sort()
return nums[-k]
"""
方法2.1:堆排序--小顶堆(自己实现版本)
时间复杂度:O(nlogk):n个
空间复杂度:O(k) -- k为题中的k,需要维护的堆的nodes数量
"""
def findKthLargest(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
def buildheap(nums):
size = len(nums)
for i in range(size//2 - 1, -1, -1):
heapfy(nums, size, i)
def heapfy(nums, size, i):
left = 2 * i + 1 # left node
right = 2 * i + 2 # right node
tmp = i
if left < size and nums[tmp] > nums[left]:
tmp = left
if right < size and nums[tmp] > nums[right]:
tmp = right
if i != tmp:
nums[i], nums[tmp] = nums[tmp], nums[i]
heapfy(nums, size, tmp)
"""
引申:堆排序 -- 对数组中所有元素进行排序
"""
def heapsort(nums):
buildheap(nums) # 小顶堆建堆完成
size = len(nums)
for i in range(size - 1, -1, -1):
nums[i], nums[0] = nums[0], nums[i]
heapfy(nums, i, 0)
return nums
kheap = nums[0:k] # 取前k个元素
size = len(nums)
buildheap(kheap) # 维护一个小顶堆
for i in range(k, size): # 之后的元素逐个比较
if nums[i] > kheap[0]: # 如果大于堆顶元素,则进入堆,然后进行堆化操作
kheap[0] = nums[i]
heapfy(kheap, k, 0)
else:
continue
return kheap[0] # 最终堆顶元素即为所求的值
"""
方法2.2:堆排序--内置函数,一步到位
内置函数
"""
def findKthLargest(self, nums, k):
from heapq import heappush,heapreplace
# 使用堆的nlargest(n,iter)返回前n个最大的数,倒序排练
return nlargest(k,nums)[-1]3
"""
方法2.3:堆排序--内置函数,借助heappu and heapreplace
时间复杂度:O(nlogk) 遍历所有元素O(n),heapfy过程O(logk)
空间复杂度:O(k) -- k为题中的k,需要维护的堆的nodes数量
"""
def findKthLargest(self, nums, k):
from heapq import heappush,heapreplace
# 使用小顶堆
heap = []
for i in range(len(nums)):
if i < k:
heappush(heap,nums[i]) # 元素为满时,建堆过程
else: # 元素已满时,heapfy操作
if nums[i] > heap[0]:
m = heapreplace(heap,nums[i])
return heap[0]
"""
方法3:partition方法
时间复杂度O(n): 假设每次都可以均分 n + n/2 + n/4 + n/8 + ... + 1,最后结果为n*(1 + 1/2 + 1/4 + 1/8 +...),后者不会超过2;
最差的情况O(n*n),每次都选到最小或者最大元素,n + n-1 + ....1
空间复杂度O(1)
"""
def findKthLargest(self, nums, k):
from random import randint
def partition(nums, lo, hi):
i, j = lo, hi + 1 # 这里要注意,i, j表示首尾index,基于lo,hi来设置,而不能用0, len(nums),后续这都是要变的
ind = randint(lo, hi) # !重点!为了防止复杂度退化到O(n*n),随机选一个pivot,再跟lo位置处的元素置换即可
p = nums[ind]
nums[ind], nums[lo] = nums[lo], nums[ind]
while i + 1 < j:
if nums[i+1] <= p:
nums[i+1], nums[i] = nums[i], nums[i+1]
i += 1
else:
nums[i+1], nums[j-1] = nums[j-1], nums[i+1]
j -= 1
return i
k = len(nums) - k # 需要找到的index(eg.find the largest, then len(nums) - 1)
lo = 0
hi = len(nums) - 1
while lo < hi:
pivot = partition(nums, lo, hi)
if pivot < k:
lo = pivot + 1
elif pivot > k:
hi = pivot - 1
else:
break
return nums[k]
题目:给定两个数组,编写一个函数来计算它们的交集。说明:输出结果中的每个元素一定是唯一的;不考虑输出结果顺序;
示例:nums1 = [1, 2, 2, 1], nums2 = [2, 2] --> output: [2]
基本思路:两次遍历即可:第一次建dict,第二次查找(结果集不重复)
class Solution(object):
"""
方法1:hashmap,对第一个数组建hashmap,O(m),遍历第二个数组查找O(n)
时间复杂度O(m+n)
"""
def intersection(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: List[int]
"""
dict = {}
ans = []
for i in nums1:
dict[i] = ''
for j in nums2:
if dict.get(j) != None:
ans.append(j)
return list(set(ans))
题目:给定两个数组,编写一个函数来计算它们的交集。说明:输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。不考虑输出结果的顺序。
示例:nums1 = [4, 9, 5];nums2 = [9, 4, 9, 8, 4]
基本思路:先对两个数组排序,再对两个数组分别维护一个指针,遍历即可(可重复)
class Solution(object):
def intersect(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: List[int]
"""
nums1.sort()
nums2.sort()
size1, size2 = len(nums1), len(nums2)
i, j = 0, 0
ans = []
while i < size1 and j < size2:
if nums1[i] == nums2[j]:
ans.append(nums1[i])
i += 1
j += 1
elif nums1[i] > nums2[j]:
j += 1
else:
i += 1
return ans
题目:给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
示例:输入:nums = [1,1,1], k = 2 输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
基本思路:切忌用双指针;这里可能会有负数,且无序;
时间复杂度:O(n);空间复杂度:O(n);
class Solution(object):
"""
方法:前缀和
"""
def subarraySum(self, nums, k):
hash = {0:1}
sum = 0
count = 0
for i in range(len(nums)):
sum += nums[i]
if (sum -k ) in hash:
count += hash[sum - k]
if sum in hash:
hash[sum] += 1
else:
hash[sum] = 1
return count
"""
错误方法(没考虑到有负数):双指针 -- 如果有负数,用双指针就是错的解法
"""
def subarraySum(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
if not nums: return 0
i, j = 0, 0
ans = [] # 如果需要输出具体有哪些可以用这个
cnt = 0
sum = nums[0]
size = len(nums)
while i < size and j < size:
if sum == k:
cnt += 1
ans.append(nums[i:j+1])
i += 1
j += 1
if j < size:
sum = sum + nums[j] - nums[i-1]
elif sum < k:
j += 1
if j < size:
sum += nums[j]
else:
i += 1
sum -= nums[i-1]
return cnt