题目:
前三题都可以用集合和来求。【sum(set(nums))*N-sum(nums)】
4、数组中别的数都出现2次,只有两个数只出现1次,找出这两个数。
5.1、数组中1到1000遍历(顺序打乱),有两个数缺失,求出这两个数。
【n个正整数,每个数值不超过n-1,有一个重复的数,找出那个重复的数】
7、n个[0,n)的数,求每个数出现次数(不能开辟额外空间)【排序】
8、数组去重
10、在一个有序数组中查找一个数,怎么最快【二分查找】
12、奇数下标都是奇数或偶数下标都是偶数【even、odd变量】
13、在数组中找到一个局部最小的位置
16、求最短通路值【宽度优先】
解题:
1、(3)第1题相当于第3题N为偶数。解法思路:数组为nums,则数组中所有数异或,最后的值为所求。
def findNum(nums):
if not nums:
return -1
i=1 res=nums[0]
while i<len(nums): res^=nums[i]
i += 1 return res
2、(3)第2题相当于第3题N为奇数。解法思路:所有数的二进制位相加 mod 3 ,所有模3值不为0的位汇总的数即为所求。
def findNum(nums): if not nums: return -1 i=0 res=0 bitSum=[0]*4 #这里是4是因为若设32位,但无论怎么算都只需4位 while i<4: j=0 while j<len(nums): bitSum[i]+=(nums[j]>>i)&1
j+=1 res|=(bitSum[i]%3)<<i i+=1 return res
更简单的解法:一个数用来表示出现一次的位,也就是说这个数的为1的位就表示这个位上面的数出现过了一次,比如0x10001,就表示bit[0]和bit[4]就是出现过了一次的位。然后再用一个数表示出现过了两次的位,再用一个数表示出现过了3次的位。只要这个位出现过了3次,我就把这个位拿清除掉,这样剩余的最后出现过一次的位的这个数就是我们要找的数了。
def findNum(nums): ones=twos=threes=0 for i in range(len(nums)): twos |= ones & A[i] ones ^= A[i] threes = ones & twos ones &= ~threes twos &= ~threes return ones
第3题:所有数的二进制位相加 mod n ,所有模n值不为0的位汇总的数即为所求。
def findNum(nums,n): if not nums: return -1 arr = [0] * 32 for i in range(len(nums)): binnum = nums[i] j = 0 #将十进制转成二进制 while binnum: binnum = binnum >> j arr[j] += binnum & 1 j += 1 res = 0 #记录%n有余数的数 for i in range(len(arr)): tmp = (arr[i] % n)&1 res += tmp << i return int(res) nums = [1000,1000,1000,2,3,4,4,4,3,3] n = 3 print(findNum(nums,n))
|
import math def findNum(nums,n): if not nums: return -1 arr = [0] * 32 for i in range(len(nums)): binnum = nums[i] j = 0 while binnum: binnum , remainder = divmod(binnum,2) #divmod将商和余数结合起来 if remainder == 1: arr[j] += 1 j += 1 res = 0 for i in range(len(arr)): if arr[i] % n == 1: res += math.pow(2,i) return int(res) nums = [5,5,5,2,3,4,4,4,3,3] n = 3 print(findNum(nums,n))
|
第4题:先将数组所有数字异或,最后结果就是两个出现一次的数字相互异或的结果,再将这两个数分别分在两个数组中进行异或。如链接:
https://blog.csdn.net/morewindows/article/details/8214003
设题目中这两个只出现1次的数字分别为A和B,如果能将A,B分开到二个数组中,那显然符合“异或”解法的关键点了。因此这个题目的关键点就是将A,B分开到二个数组中。由于A,B肯定是不相等的,因此在二进制上必定有一位是不同的。根据这一位是0还是1可以将A,B分开到A组和B组。而这个数组中其它数字要么就属于A组,要么就属于B组。再对A组和B组分别执行“异或”解法就可以得到A,B了。而要判断A,B在哪一位上不相同,只要根据A异或B的结果就可以知道了,这个结果在二进制上为1的位都说明A,B在这一位上是不相同的。
比如int a[] = {1, 1, 3, 5, 2, 2}
整个数组异或的结果为3^5即 0x0011 ^ 0x0101 = 0x0110
对0x0110,第1位(由低向高,从0开始)就是1。因此整个数组根据第1位是0还是1分成两组。
a[0] =1 0x0001 第一组
a[1] =1 0x0001 第一组
a[2] =3 0x0011 第二组
a[3] =5 0x0101 第一组
a[4] =2 0x0010 第二组
a[5] =2 0x0010 第二组
第一组有{1, 1, 5},第二组有{3, 2, 3},明显对这二组分别执行“异或”解法就可以得到5和3了。
def findNum(nums): if not nums: return -1 temp=0 ##先所有数异或得到A^B的值,A和B为只出现一次的数 for item in nums: temp^=item #找到A和B不同的第一位,A^B的第一个1位。该位设为j位 j=0 while j<32: if (temp>>j)&1==1: break j+=1 A=[] B=[] #将A、B分到按第j位不同分到不同组A和B for num in nums: if (num>>j)&1==1: A.append(num) else: B.append(num) ##分到的两个组全部都异或 resA=0 resB=0 for itemA in A: resA^=itemA for itemB in B: resB^=itemB return resA,resB nums=[1,1,3,5,2,2] print(findNum(nums))
第5.1题:数组中1到1000遍历(顺序打乱),有两个数缺失,求出这两个数。
- 思路1:list(set(range(1,1001))-set(nums)),即两个集合相减
- 思路2:排序,然后比较【时间O(nlogn),空间O(1)】
- 思路3:采用字典,将数组的元素存到key中,从1-n查询时若有哪两个数缺失则返回。【空间换时间】
第5.2题:数组中未出现的最小正整数【左右变量遍历】
给定一个无序整型数组arr,找到数组中未出现的最小正整数。
举例:
arr = 【-1,2,3,4】。返回1
arr = 【1,2,3,4】。返回5.
思路:时间O(N),空间O(1)
分析:
假如arr有序,则:
(1) arr为整数1,2,3…N,N+2,N+3……的一个随机排列,那个未出现的最小正整数就是N+1。
【设置left变量表示已经有序的N个数 arr [0:N-1] 】
(2) arr中有小于1或者大于N或者重复的数出现(我们称之为“不合法”的数),则未出现的最小正整数一定在1到N中间(因为数组一共只有N个数,如果出现不合法的数,则出现的1到N之间的数的个数一定小于N,故一定有没有出现的数)。
【设置right变量,初始N+2, 表示还可以有序的arr [ N:N+2 ]】
【比如:发现 arr[N](即N+2)合法但位置不理想,将N+2换到它理想的位置,然后将N+3换到该位置 arr[ N ],继续比较N+3是不是合法且理想,发现并不合法,合法区间right-1变成 arr[ N:N+1 ] 】
做法:
(1) 先设置两个变量L,R。初始值:L=0,R=len(arr)
-
- L表示已经从1到L已经出现(左边界),L的初值为0。
- 如果一个数字过大(不合法),就会被扔掉,用R表示这个右边界,即大于R的数会被扔掉。R的初值为N,表示从1到R的元素都不会被扔掉,大于R的就会被扔掉。但是这个R的值是变化的,如果L+1到R中有一个元素不合法,那么R--,因为最多只能放下R-1个合法数。
也就是说,1到L上的数已经出现,[L+1,R]区间上的数未出现但可能会出现。
(2)遍历数组:
1》当 L 的位置值等于L+1,表示得到想要的,故L ++
2》三种不合法情况:都需R--
-
- 小于左边的数:当 L 的位置值< L 时,表示 L 位置的值已经存在,所以这个数组不会存在到 R 的值了 so R--
- 大于右边的数:当 L 的位置值> R 时,表示 L 的位置已经超过 R ,所以数组不会存在到 R 的值了, so R--
- 左边区域重复的数:当 L 的位置值和 (L 值的位置-1)的的位置值相等时,所有数组不会存在到 R 的值了,so R-- 【即L位置的值已经存在左区域已经出现的数中了,两个一样的值,比如 [1,2,3,2,4] ,第二个2已经重复了[1,2,3]中的2了 】
3》合法但位置不理想:else,将 arr【L】 交换到理想位置,理想位置的值换到 L 位置来,继续判断 L 位置的值。
代码:
def missNum(arr): if not arr: return 1 left , right = 0 , len(arr) while left < right: if arr[left] == left + 1: left += 1 elif arr[left] <= left or arr[left] > right or arr[left] == arr[arr[left]-1]: right -= 1 arr[left] = arr[right] #right-=1后合法区域不包括arr【right】了,将arr[right]换到left位置继续判断 else: #arr[left]理想位置为arr[left]-1,将arr[arr[left]-1]换到left位置继续判断 arr[left] , arr[arr[left] - 1] = arr[arr[left]-1] , arr[left] return left + 1 arr = [1,3,-2,4,5] missNum(arr)
第6题:数组中别的数都出现1次,只有一个数出现2次,找出该数。
- 思路1:【空间换时间】,采用字典,将数组的元素存到key中,value对应数量,若value=2则返回该key值。
- 思路2:【累加求和法,可能溢出】,sum(nums) - sum([1……n])即所求
- 思路3:【异或法】nums数与 [1……n] n个数异或,异或结果为所求
- 思路4:【数据映射法】全部变为负数,最后一个为正数的即所求。【如arr = [2,3,4,3,1],令arr[0] = -2,指向下标为2,即arr[2] = -4, arr[4] = -1, arr[1] = -3,arr[3] = -3,arr[3] = 3变为正数,即为所求。】如果元素不合理时,存在数组越界问题
- 思路5:【环形相遇法】将数组连成链表,如果有重复值则会形成环形链表,只需要求入环点即可。
- 思路6:排序,找相邻的数。
9、在数组中找到出现次数大于N/K的数
思路:找到出现最多的那个数且次数超过一半。方法是遍历过程中成对删除不同的数,一个数如果出现次数超过一半,剩下的数必是该数,否则可能是也可能不是。如:
【4,3,7,4,4,4,5】,设置一个cand和time变量。开始,
开始,cand = 4,time= 1,
遇到下一个数3,两者不同,time - 1 = 0
遇到下一个数7,因为time=0,所以cand重置为=7,time=1
遇到下一个数4,cand和4不同,所以time - 1 =0
遇到下一个数4,time = 0,cand重置为4,time= 1
遇到下一个数4,cand和4相同,time + 1 = 2
遇到下一个数5,cand和5不同,time - 1 = 1
故:剩下的数cand为4。
然后从头开始遍历,判断4是否出现超过一半【因为如果没超过一半也可能cand为4,如 [ 4,3,4,5]】
代码:
def printHalfMajor(arr): if arr == None or len(arr) == 0: print("No such number!") return False cand = 0 time = 0 for i in range(len(arr)): if time == 0: cand = arr[i] time += 1 elif cand == arr[i]: time += 1 else: time -= 1 time = 0 for i in range(len(arr)): if arr[i] == cand: time += 1 if time > len(arr)//2: return True else: return False arr=[4,3,4,3,4,6,5,4] printHalfMajor(arr)
进阶问题思路:一次删掉K个不同的数,多次删除,直到剩下的数种类不足K。若一个数出现次数超过N/K,该数最后一定会剩下。
一个map记录K个不同的数,一旦map大小==k-1,且下一个arr[i]不在map中,就可以开始删除map中K个不同值。
代码:
def printHalfMajor(arr,k): if arr == None or len(arr) <= k: print("No such number!") return False #重点:每次删除K种不同的值【采用字典存储K种不同的值】 map = {} for i in range(len(arr)): if arr[i] in map: map[arr[i]] += 1 else: if len(map) == k-1: for key in list(map): map[key] -= 1 if map[key] == 0: del map[key] else: map[arr[i]] = 1 #查询map剩余的数在原数组中的数量,【如[1,1,3,3,4,4,4,4,2,2]中最后map会剩下{4:2,2:2}】 time = 0 flag = False for key in map: for i in range(len(arr)): if arr[i] == key: time += 1 if time > len(arr)//k: flag = True print(key) time = 0 return flag arr=[4,3,4,3,4,6,5,4] K = 3 printHalfMajor(arr,K)
10、在行列都排好序的矩阵中找数
思路:
11、奇数下标都是奇数或偶数下标都是偶数
给定一个长度不小于2的数组arr,实现一个函数调整arr,要么让所有的偶数下标都是偶数,要么让所有的奇数下标都是奇数。
思路:时间O(N),空间O(1)
设置三个变量,even下标、odd下标、end下标
只要arr【end】 是偶数,就将arr【end】和arr【even】交换,even+=2.
同样,arr【end】 是奇数,就将arr【end】和arr【odd】交换,odd+=2.
代码:
def test(arr): if not arr or len(arr) <= 1: return arr even = 0 odd = 1 end = len(arr) - 1 while even < len(arr) and odd < len(arr): if arr[end] % 2 == 0: arr[end] , arr[even] = arr[even] , arr[end] even += 2 elif arr[end] % 2 == 1: arr[end] , arr[odd] = arr[odd] , arr[end] odd += 2 return arr arr = [1,8,3,2,4,6] test(arr)
12、边界都是1的最大正方形大小
给定一个N*M的矩阵matrix, 在这个矩阵中, 只有0和1两种值, 返回边框全是1的最大正方
形的边长长度。
例如:
0 1 1 1 1
0 1 0 0 1
0 1 0 0 1
0 1 1 1 1
0 1 0 1 1
其中, 边框全是1的最大正方形的大小为4*4, 所以返回4
思路1:时间O(N4),枚举所有正方形,判断边框是否都为1
1.矩阵中一共有N*N个位置。O(N2)
2.对每一个位置都可以成为边长为N~1的正方形左上角。比如,对于(0,0)位置,依次检查是否是边长为5的正方形的左上角,然后检查边长为4、3等。O(N)
3.如何检查一个位置是否可以成为边长为N的正方形的左上角?遍历这个边长为N的正方形边界看是否只由1组成,也就是走过四个边的长度(4N)。O(N)
总的时间复杂度:O(N2)*O(N)*O(N)=O(N4)
思路2:时间复杂度为O(N3),以空间换时间的做法。
采用预处理矩阵的方法,同样也是枚举所有的正方形,但是判断该正方形是否符合规则是,是O(1)的时间复杂度,所以当M=N时,这是O(N^3)时间复杂度。
用与原矩阵同样大小的两个矩阵,一个为right,一个为down,
right[i][j]的值表示从位置(i,j)向右出发有多少个连续的1。
down[i][j]的值表示从位置(i,j)向下出发有多少个连续的1。
right和down的计算过程:从右到左,从下到上依次填好两个矩阵。
-
- 从矩阵的右下角(n-1,n-1)位置开始计算,如果matrix[n-1][n-1]=1,那么,right[n-1][n-1]=1,down[n-1][n-1]=1,否则都等于0。
- 从右下角向上计算,即在matrix最后一列上计算,位置就表示为(i,n-1)。对right来说,最后一列的右边没有内容,所以,如果matrix[i][n-1]=1, right[i][n-1]=1并且down[i][n-1]=down[i+1][n-1]+1,否则right[i][n-1]=0并且down[i][n-1]=0。
- 从右下角向左计算,即在matrix最后一行上计算,位置就表示为(n-1,j)。对down来说,最后一行的下边没有内容,所以,如果matrix[n-1][j]=1, down[n-1][j]=1并且right[n-1][j]=down[n-1][j+1]+1,否则right[n-1][j]=0并且down[n-1][j]=0。
- 剩下的位置都是既有右,又有下,假设位置(i,j):
- if matrix[i][j]=1, then right[i][j+1]=right[i][j]+1,down[i][j]=down[i+1][j]+1.
- if matrix[i][j]=0,then right[i][j]=0,down[i][j]=0.
13、不包含本位置值的累乘数组
给定一个整型数组arr,返回不包含本位置值的累乘数组。
例如,arr = [2, 3, 4, 1],返回[12, 8, 24, 6],即除自己以外,其他位置上的累乘。
【要求】
-
- 时间复杂度O(N)
- 除需要返回的结果数组外,额外空间复杂度O(1)。
不用除法思路:
分别使用辅助两个数组left和right,其中left表示数组从左到右的累乘结果(即left[i] = arr[0…i]的累乘);相反,right表示数组从右到左的累乘结果。那么对于结果数组res,res[i] = left[i-1] * right[i+1]。
实际上,并不需要额外声明两个辅助数组。可以复用结果数组res,即先将res当辅助数组用,再把res调整为结果数组即可。具体实现见如下代码:
def product2(arr): if arr == None or len(arr) < 2: return res = [0 for i in range(len(arr))] res[0] = arr[0] for i in range(1, len(res)): res[i] = res[i-1] * arr[i] tmp = 1 for i in range(len(arr)-1, 0, -1): res[i] = res[i-1] * tmp tmp *= arr[i] res[0] = tmp return res arr = [2,3,1,4] product2(arr)
17、二维数组的转置
题目:
代码:
def merge(arr,n): if not arr: return arr res = [] num = [0] * len(arr) count = len(arr) while count: for i in range(len(arr)): m = len(arr[i]) if num[i] + n <= m: res.extend(arr[i][num[i]:num[i]+n]) num[i] += n elif num[i]+n > m and arr[i][num[i]:]: res.extend(arr[i][num[i]:]) num[i] += n count -= 1 return res arr = [[2,5,6,7,9,5,7],[1,7,4,3,4]] n = 3 merge(arr,n)
长度一样的转置:【采用解压*】list(zip(*matrix))
18. 股票最大利润
思路1:用后一天减去前天得到隔天的利润,然后将该题目转化为求最大子序列和的问题。
思路2:当天的价格减去今天以前的股票最小值
代码:
arr = [7,1,4,3,1] min_P = 2**32 max_P = -min_P if not arr: print('0') else: for i in range(len(arr)): min_P = min(min_P,arr[i]) max_P = max(max_P,arr[i]-min_P) print(max_P)