本篇文章根据labuladong的算法小抄介绍有关数学运算技巧的常见算法,采用python3实现
文章目录
常用的位操作
&:与,两个值相应位都为1则为1
|:或,两个值相应为有一个为1则为1
^:异或,两个值对应位相异则为1
~:非,对数据的每个二进位取反
<<:左移,对数据的每个二进位全部左移若干位,高位丢弃,低位补0
>>:右移,对数据的每个二进位全部右移若干位。
-
判断两个数是否异号
x = -1 y = 2 f = ((x^y) < 0) #True
-
消除数字n的二进制表示中的最后一个1
n & (n - 1)
计算汉明权重,T191
输入一个无符号整数,返回其二进制表达式中数字为1的个数
def hammingWeight(n):
res = 0
while n != 0:
n = n & (n - 1)
res += 1
return res
判断整数是否为2的幂次方,T231
如果一个数是2的指数,那么它的二进制一定只有一个1
def isPowerOfTwo(n):
if n <= 0:
return False
return (n & (n - 1)) == 0
查找只出现一次的数字,T136
a ^ a = 0;a ^ 0 = a
对所有数字进行异或,那么成对数字变为0,落单数字还是它本身
def singleNumber(nums):
res = 0
for n in nums:
res = res ^ n
return res
阶乘
阶乘后的0
题目:输入一个非负整数n,请计算n!的结果末尾有几个0
思考:末尾有0,一定是有因子2和5,那么问题转化为n!最多可以分解出多少个因子2和5?
每个偶数都能分解出因子2,所以主要取决于能分解出几个因子5。
#写法1
def trailingZeroes(n):
res = 0
divisor = 5
while divisor <= n:
res += n // divisor
divisor *= 5
return res
#写法2
def trailingZeroes(n):
res = 0
d = n
while d > 0:
res += d // 5
d = d // 5
return res
对于125,首先125/5=25,那么有25个数可以提供1个5;125/25=5,那么有5个数可以提供2个5;125/125=1,那么有1个数可以提供3个5.
阶乘后的k个0
题目:输入一个非负整数K,请计算有多少个n可以满足n!末尾恰好有K个0
方法:使trailingZeroes(n) == k的n有多少个,n在[0,float(“inf”)]中,可以转换成二分搜索的左右边界。
class Solution:
def preimageSizeFZF(self, k: int) -> int:
def trailingZeroes(n):
res = 0
d = n
while d > 0:
res += d // 5
d = d // 5
return res
def leftBound(k):
left = 0
right = sys.maxsize
while left < right:
mid = left + (right - left) // 2
x = trailingZeroes(mid)
if x >= k:
right = mid
else:
left = mid + 1
return left
def rightBound(k):
left = 0
right = sys.maxsize
while left < right:
mid = left + (right - left) // 2
x = trailingZeroes(mid)
if x <= k:
left = mid + 1
else:
right = mid
return left - 1
return rightBound(k) - leftBound(k) + 1
素数
素数:如果一个数只能被1和它本身整除,那么它就是素数
计数质数,T204
判断质数:
def isPrime(n):
for i in range(2,sqrt(n)+1):
if n % i == 0:
return False
return True
计数质数:
def countPrimes(n):
isPrime = [True for i in range(n)]
for i in range(2,int(sqrt(n))+1):
if isPrime[i]:
j = i * i
while j < n:
isPrime[j] = False
j += i
count = 0
for i in range(2,n):
if isPrime[i]:
count += 1
return count
时间复杂度O(n loglogn)
模幂
超级次方,T372
题目:a^b对1337取模,a为正整数,b为非常大的正整数,以数组形式给出
a
[
1
,
5
,
6
,
4
]
=
a
4
∗
a
[
1
,
5
,
6
,
0
]
=
a
4
∗
(
a
[
1
,
5
,
6
]
)
10
a^{[1,5,6,4]}=a^4*a^{[1,5,6,0]}=a^4*(a^{[1,5,6]})^{10}
a[1,5,6,4]=a4∗a[1,5,6,0]=a4∗(a[1,5,6])10
模运算技巧:
对乘法的结果求模,等价于先对每个因子都求模,然后对因子相乘的结果再取模
(
a
∗
b
)
%
k
=
(
a
%
k
)
(
b
%
k
)
%
k
(a*b)\%k = (a\%k)(b\%k)\%k
(a∗b)%k=(a%k)(b%k)%k
#递归
def superPow(a,b):
#计算(a^k)%1337=((a%1337)(a%1337)...(a%1337))%1337
def mypow(a,k):
res = 1
a = a % 1337
for x in range(k):
res *= a
res %= 1337
return res
if not b:
return 1
last = b.pop()
part1 = mypow(a,last)
part2 = mypow(superPow(a,b),10)
return (part1 * part2) % 1337
如何高效求幂

def mypow(a,k):
if k == 0:
return 1
a %= 1337
if k % 2 == 1:
return (a * mypow(a,k-1)) % 1337
else:
sub = mypow(a,k//2)
return (sub * sub) % 1337
缺失元素
丢失的数字,T268
题目:给定一个包含[0,n]中n个数的数组nums,找出[0,n]中没有出现在数组中的那个数
法1:数组排序,遍历一遍(时间复杂度O(NlogN))
法2:用集合储存数组里出现的数字,再遍历[0,n]去集合中查询(时间复杂度O(N),空间复杂度O(N))
法3:位运算,把所有元素和索引做异或运算,成对的数字会变成0,落单的会剩下(时间复杂度O(N),空间复杂度O(1))
def missingNumber(nums):
res = 0 ^ len(nums)
for i in range(len(nums)):
res ^= i ^ nums[i]
return res
法4:等差数列求和,sum(0,1,…,n) - sum(nums)
def missingNumber(nums):
n = len(nums)
expect = (0 + n) * (n + 1) // 2
return expect - sum(nums)
但有可能整型溢出,优化:
def missingNumber(nums):
n = len(nums)
res = 0
res += n
for i in range(n):
res += i - nums[i]
return res
缺失和重复元素
错误的集合,T645
题目:nums数组本应是1…n的整数,但其丢失一个数字并有一个数字重复,请你找到重复出现的整数,再找到丢失的整数,将它们以数组形式返回。
法1:遍历数组,记录每个数字出现的次数;遍历1…N,看哪个元素重复出现,哪个元素没有出现
法2:将每个索引对应的元素变成负数,以表示这个索引被对应过一次了
def findErrorNums(nums):
n = len(nums)
for num in nums:
index = abs(num) - 1
if nums[index] < 0:
dup = nums[i]
else:
nums[index] *= -1
for i in range(n):
if nums[i] > 0:
missing = i + 1
break
return [dup,missing]
这种数组问题,关键点在于元素和索引是成对出现的,常用方法是排序、异或、映射。
重复元素
对数组来说,尾部插入、删除元素比较高效,O(1);开头或中间插入、删除元素,涉及数据搬移,O(N)。
原地删除有序数组的重复项,T26
要求返回修改后数组的长度
def removeDuplicates(nums):
if len(nums) == 0:
return 0
slow = 0
fast = 0
while fast < len(nums):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
fast += 1
return slow + 1
原地删除有序链表的重复项,T83
要求返回修改后的
def deleteDuplicates(head):
if (not head) or (not head.next):
return head
slow = head
fast = head
while fast:
if fast.val != slow.val:
slow.next = fast
slow = slow.next
fast = fast.next
slow.next = None #注意!
return head
移除元素,T27
原地移除所有值等于val的元素,返回新长度
def removeElement(nums,val):
n = len(nums)
if n == 0:
return 0
slow = 0
fast = 0
while fast < n:
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
移动零,T283
将nums所有0移到末尾
def moveZeroes(nums):
slow = 0
fast = 0
while fast < len(nums):
if nums[fast] != 0:
nums[slow] = nums[fast]
slow += 1
fast += 1
for i in range(slow,len(nums)):
nums[i] = 0
随机抽取元素
链表随机节点,T382
链表随机选取一个节点的值:
def getRandom(head):
p = head
i = 0
res = 0
while p:
i += 1
#生成一个[1,i]的整数,这个整数等于1的概率就是1/i
if random.randint(1,i) == 1:
res = p.val
p = p.next
return res
链表随机选取k个节点的值:(概率为k/i)
def getRandom(head,k):
res = [0 for i in range(k)]
p = head
j = 0
#前k个元素先默认选上,0..k-1
while p and j < k:
res[j] = p.val
p = p.next
j += 1
i = k
while p:
i += 1
#生成一个[0,i-1]之间的整数,小于k的概率为k/i
j = random.randint(0,i-1)
if j < k:
res[j] = p.val
p = p.next
return res
脑筋急转弯
Nim游戏,T292
题目:你和朋友面前有一堆石子,轮流拿,一次至少一颗,最多三颗,谁拿走最后一颗获胜。假设你们都很聪明,你先第一个拿。判断n个石子你是否能赢。
思考:如果我能赢,那么到我的时候必须有1-3颗石子 → 对手必须有4颗石子 → 我必须有5 - 7颗 → 对手必须有8颗
def canWinNim(n):
return n % 4 != 0
石子游戏,T877
题目:有偶数堆石头,piles[i]表示第i堆有多少个。你和朋友轮流拿,一人拿一堆,但只能拿最左边或最右边的。石头拿完后,谁的石头多,谁获胜。假设你们都很聪明,你先开始,返回你是否能赢。
动态规划:
def stoneGame(piles):
n = len(piles)
#dp[i..j]中,先手和后手能获得的最高分数
dp = [[[0 for i in range(n)] for j in range(n)] for k in range(2)]
for i in range(n):
dp[i][i][0] = piles[i]
dp[i][i][1] = 0
for i in range(n-2,-1,-1):
for j in range(i+1,n):
left = piles[i] + dp[i+1][j][1]
right = piles[j] + dp[i][j-1][1]
if left > right:
dp[i][j][0] = left
dp[i][j][1] = dp[i+1][j][0]
else:
dp[i][j][0] = right
dp[i][j][1] = dp[i][j-1][0]
return dp[0][i-1][0] - dp[0][i-1][1]
新思路:
def stoneGame(piles):
return True
假设有四堆石头,用1,2,3,4作为索引,可以把它们按照奇偶分为1、3堆和2、4堆,这两组石头的数量一定不同(石头总数为奇数,%2 != 0)。先拿石头的人可以控制自己拿到所有偶数堆或奇数堆。
电灯开关,T319
题目:n盏电灯,开始都是关着的。现在要进行n轮操作:
(1)把每盏电灯的开关按一下(全部打开)
(2)把每2盏电灯的开关按一下(2,4,6…关闭)
(3)把每3盏电灯的开关按一下(3,6,9,…)
……直到第n轮,只按一下第n盏灯的开关。问还有多少盏灯是亮的?
def bulbSwitch(n):
return int(sqrt(n))
思考:如果一盏灯最后是点亮的,必须按奇数次开关。假设有6盏灯,那么第6盏等第1,2,3,6轮会被按,6=1*6=2*3。一般情况下,因子成对出现,也就是说开关被按的次数一般是偶数次但有特殊情况,比如共有16盏等,那么第16盏灯会被按5次,16=1*16=2*8=4*4。