4. 寻找两个正序数组的中位数
数组长度分别为m,n,当m+n为奇数时,中位数是第k=(m+n)//2个元素,当m+n为偶数时,中位数是第k和k+1个元素平均值
法一:归并法合并,维护两个指针,初始时分别指向两个数组的下标 00 的位置,每次将指向较小值的指针后移一位(如果一个指针已经到达数组末尾,则只需要移动另一个数组的指针),直到到达中位数的位置。
时间复杂度O(m+n),空间复杂度O(1)
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
m, n = len(nums1), len(nums2)
i, j, k = 0, 0, 0
#用两个数记录总数组中相邻位置的值,用于总数是偶数的情况;
r1, r2 = 0, 0
while k <= (m+n)//2:
if j>=n or (i<m and nums1[i] < nums2[j]):
r1, r2 = r2, nums1[i]
i += 1
k += 1
else:
r1, r2 = r2, nums2[j]
j += 1
k += 1
return float(r2) if (m+n)%2 != 0 else (r1 + r2)/2
法二:二分查找
相当于找两个有序数组的第k小元素,通过比较A[k/2-1]和B[k/2-1],
idx1,idx2=0,0
1):如果A[k/2-1]<B[k/2-1],说明A的前k/2个元素都小于B[k/2-1],中位数不可能出自A的前k/2个元素,所以k=k-k/2,idx1=idx1+k/2
2):如果A[k/2-1]>B[k/2-1],说明B的前k/2个元素都小于A[k/2-1],中位数不可能出自B的前k/2个元素,所以k=k-k/2,idx2=idx2+k/2
3):A[k/2-1]=B[k/2-1],归到情况1里
特殊情况:
A[k/2-1]或者B[k/2-1]越界,那么我们可以选取对应数组中的最后一个元素。在这种情况下,我们必须根据排除数的个数减少 k 的值,而不能直接将 k减去 k/2。
k==1:则返回两个数组的首元素最小值
如果一个数组为空,则返回另一个数组的第K小元素
为了编码方便,分割线左右元素要么 相等,要么左边元素比右边元素多1,k=(m+n+1
/2
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
def getmedian(k):
idx1,idx2=0,0
while True:
# 如果一个数组元素为空,则返回另一个数组第K个元素
if idx1==m:
return nums2[idx2+k-1]
if idx2==n:
return nums1[idx1+k-1]
if k==1:
return min(nums1[idx1],nums2[idx2])
#正常情况
#判断是否越界,如果越界则选择该数组最后一个元素进行大小比较
newidx1=min(idx1+k//2-1,m-1)
newidx2=min(idx2+k//2-1,n-1)
if nums1[newidx1]<nums2[newidx2]:
#根据排除数的个数减少k
k-=newidx1-idx1+1
idx1=newidx1+1
else:
k-=newidx2-idx2+1
idx2=newidx2+1
m,n=len(nums1),len(nums2)
if (m+n)%2:
return getmedian((m+n+1)//2)
else:
return (getmedian((m+n)//2)+getmedian((m+n)//2+1))/2
位运算:
https://muyids.github.io/simple-algorithm/chapter/%E4%BD%8D%E8%BF%90%E7%AE%97.html
将第k位置为1(k从0开始):x |= (1 << k)
将第k位置为0:x &= ~(1 << k)
判断第k位是不是1:(x >> i) & 1 或者 x & (1 << i)
删除最后一位的1:x&(x-1)
x&-x:得到x的二进制表示中最右边的1 负数 = 正数的补码 + 1
负数是正数所有位取反在加1,x&-x可以得到最右边的1,且其余位置0
260 只出现一次的数字 三:
用mask保存两个出现一次的数字的异或值==保存两个数字之间的差别,因为相同数字异或值为0
mask&=-mask:保留mask中最右边的1,这个1要么来自x,要么来自y,当我们找到x,那么x^mask=y
class Solution:
def singleNumber(self, nums: List[int]) -> List[int]:
mask=0
for num in nums:
mask^=num
diff=mask&-mask
ans=[0,0]
for num in nums:
if diff&num:
ans[0]^=num
else:
ans[1]^=num
return ans
201. 数字范围按位与
x << k 相当于 x * 2的k次幂
x >> k 相当于 x / 2的k次幂,x>>1:x的二进制向右移一位
将每个数字表示成二进制位,在对所有数字执行完与操作以后,剩余的部分是所有这些位字符串的公共前缀,这个问题的最终结果由位字符串的公共前缀为左半部分,其余的位为零组成。更具体的说,所有这些位字符串的公共前缀也是指定范围的起始和结束编号的公共前缀(即在上面的示例中分别为 9 和 12)。
问题转换为:给定两个整数,找到他们二进制字符串的公共前缀
法一:m,n右移直到将两个数字压缩为公共前缀,然后再将公共前缀左移相同操作数得到结果
class Solution:
def rangeBitwiseAnd(self, m: int, n: int) -> int:
shift=0
while m<n:
#右移得到公共前缀
m=m>>1
n=n>>1
shift+=1
#左移填0得到结果
return m<<shift
法二:
Brian Kernighan 算法,它用于清除二进制串中最右边的 1。
当我们在 number 和 number-1 之间进行位运算时,原始 number 中最右边的 1 将变为 0。
对于给定范围的【m,n】,我们先清除n最右边的1,直到n<=m,让n与m与操作,就可以得到结果
class Solution:
def rangeBitwiseAnd(self, m: int, n: int) -> int:
while m<n:
#删除最右边的1,得到公共前缀
n=n&(n-1)
return n&m
231. 2的幂
如果x是2的幂,那么x的二进制表示只有1位是1,x&-x=x,x&-x:找到最右边的1,其余位置0
class Solution:
def isPowerOfTwo(self, n: int) -> bool:
if n==0:
return False
return n&-n==n
371 两整数之和
a ^ b相当于 a 与 b 的二进制的不进位相加,配合移位操作可以实现加法操作
a&b获得进位,但是a&b不是我们想要的进位,应该把其中的1放到更高位上(左侧位),进位通过&和左移实现,循环此过程,直到进位为0
class Solution:
def getSum(self, a: int, b: int) -> int:
#转换为无符号整数
a&=0xFFFFFFFF
b&=0xFFFFFFFF
while b:
#进位
carry=a&b
#无进位加法
a^=b
#进位转换成无符号
b=(carry<<1)&0xFFFFFFFF
#根据结果映射为有符号整数,结果小于0x80000000说明是正数,大于则符号位为1,要转换成负数
return a if a<0x80000000 else ~(a^0xFFFFFFFF)
405. 数字转换为十六进制数
二进制-16进制,每四位合成一位,所以可以从低位存储到高位,每次向右移四位,
使用 & 操作符,把负数前置无限个 1 和 正数 0xFFFFFFFF 前置无限个 0 与运算,那么负数前置的 1 全部被干掉成为 0,即成为正数,是 python 中获取 【x 位整型】补码形式的一种操作。
class Solution:
def toHex(self, num: int) -> str:
if num==0:
return '0'
#将负数转换成正数
num &= 0xFFFFFFFF
s=''
z='0123456789abcdef'
while num!=0 and len(s)<8:
#从低位合成高位,每四位合成一位
#每次与15,即&0000 1111,只得到后四位合成一位整数值
# s+=z[num&15]
s+=z[num%16]
#右移四位
num=num>>4
return s[::-1]
476 数字的补数
class Solution:
def findComplement(self, num: int) -> int:
b=bin(num).replace('0b','')
# num长度相等的11111异或
a =bin(num).replace('0b','').replace('0','1')
return int(a,2)^int(b,2)
762. 二进制表示中质数个计算置位
class Solution:
def countPrimeSetBits(self, L: int, R: int) -> int:
def zhishu(x):
if x==1:
return False
for i in range(2,x-1):
if x%i==0:
return False
return True
ans=0
for x in range(L,R+1):
co=bin(x)[2:].count('1')
if zhishu(co):
ans+=1
return ans
421. 数组中两个数的最大异或值 (按位前缀)
如果 a ^ b = c 成立,那么a ^ c = b 与 b ^ c = a 均成立。即 如果有三个数,满足其中两个数的异或值等于另一个值,那么这三个数的顺序可以任意调换。
二进制中,希望一个数越大,即越高位上存在1,于是,我们可以从最高位开始,到最低位,首先假设高位是 “1”,把这 n 个数全部遍历一遍,看看这一位是不是真的可以是“1”,否则这一位就得是“0”
如果 a ^ b = max 成立 ,max 表示当前得到的“最大值”,那么一定有 max ^ b = a 成立,
按哈希集合存储按位前缀/按字典树存储按位前缀
为了简化按位前缀的计算,将所有数转化成二进制形式之后,需要在左边补 0 使得所有数对齐。最终所有数的长度都为 L,其中 L为最大数的二进制长度。
假设当前位为1,把当前得到的最大异或值的前缀(tmp)与这个 n 个数的 前缀(因为是从高位到低位看,所以称为“前缀”)进行异或运算(max^b=a),放在一个哈希表中,目的是得到另一个数的前缀值a,如果查得到a,就说明这个数位上可以是“1”,否则就只能是 0
将x的第k位置为1(k从0开始):x |= (1 << k)
将mask与原数依次进行 “与” 运算,就能得到前缀。
因为只需要得到最大的异或结果,而不需要知道是哪两个数异或得来
class Solution:
def findMaximumXOR(self, nums: List[int]) -> int:
res=0
mask=0
for i in range(31,-1,-1):
mask|=1<<i
#记录前缀
mark=set()
for num in nums:
mark.add(num&mask)
#假设最大值
tmp=res|1<<i
for t in mark:
if t^tmp in mark:
res=tmp
break
return res
477. 汉明距离总和
我们考虑数组中每个数二进制的第 i 位,假设一共有 t 个 0 和 n - t 个 1,那么显然在第 i 位的汉明距离的总和为 t * (n - t)。
class Solution:
def totalHammingDistance(self, nums: List[int]) -> int:
N=len(nums)
res=0
#最多32位
for i in range(32):
n=0
for num in nums:
#计算二进制的每一位上的1的个数
if (num>>i)&1:
n+=1
#0的个数乘以1的个数
res+=(N-n)*n
return res
862. 和至少为 K 的最短子数组 (前缀和+单调栈)
如果x1<x2,且p[x2]<=p[x1],则如果p[y]-p[x1]满足要求,那么p[y]-p[x2]也满足要求,且最短子数组是x2-y而不是x2-y,,所以要维护一个单调递增栈,存入元素下标
class Solution:
def shortestSubarray(self, A: List[int], K: int) -> int:
p=[0]
#求子数组的和就要考虑前缀和
for i in range(1,len(A)+1):
p.append(p[i-1]+A[i-1])
stack=deque()
minl=float('inf')
for i in range(len(p)):
#维护单调递增栈,保证p[j]-p[i]>=k 且j-i最小
while stack and p[stack[-1]]>=p[i]:
stack.pop()
#当存在满足要求的i,j时,从左弹出栈内元素,找到最短子数组
while stack and p[i]-p[stack[0]]>=K:
minl=min(minl,i-stack[0])
stack.popleft()
stack.append(i)
return minl if minl!=float('inf') else -1