给定方法 rand7 可生成 [1,7] 范围内的均匀随机整数,试写一个方法 rand10 生成 [1,10] 范围内的均匀随机整数。
你只能调用 rand7() 且不能调用其他方法。请不要使用系统的 Math.random() 方法。
每个测试用例将有一个内部参数 n,即你实现的函数 rand10() 在测试时将被调用的次数。请注意,这不是传递给 rand10() 的参数。
示例 1:
输入: 1
输出: [2]
示例 2:
输入: 2
输出: [2,8]
示例 3:
输入: 3
输出: [3,8,10]
提示:
1 <= n <= 105
我的方法:拒绝采样
使用两次 rand7
API,生成两个坐标,选取十个点即可(其他点可递归调用rand10函数
)
class Solution:
def rand10(self):
x = rand7()
y = rand7()
if (x, y) == (1, 1):
return 1
elif (x, y) == (1, 2):
return 2
elif (x, y) == (1, 3):
return 3
elif (x, y) == (1, 4):
return 4
elif (x, y) == (1, 5):
return 5
elif (x, y) == (1, 6):
return 6
elif (x, y) == (1, 7):
return 7
elif (x, y) == (2, 1):
return 8
elif (x, y) == (2, 2):
return 9
elif (x, y) == (2, 3):
return 10
else:
return self.rand10()
生成 10 次,也可以
class Solution:
def rand10(self) -> int:
lst = []
for i in range(10):
lst.append(rand7())
return sum(lst)%10+1
拒绝采样优化
由于上述方法,生成了49个点,只利用了10个点,其余的点都会不断递归,非常浪费时间空间
改进:
class Solution:
def rand10(self) -> int:
while True:
row = rand7()
col = rand7()
idx = (row - 1) * 7 + col
if idx <= 40:
return 1 + (idx - 1) % 10
拒绝采样——花式玩法
class Solution:
def rand10(self) -> int:
x = rand7()
while x>6: # 1/2 的概率
x = rand7()
y = rand7()
while y>5: # 1/5 的概率
y = rand7()
return y if x<=3 else 5+y
r a n d 7 ( ) rand7() rand7() 构造任意范围的随机数发生器
上述方法理论上可以构造任何范围的随机数发生器,比如 r a n d 11 ( ) rand11() rand11() :
构造 2 2 2 次采样,分别有 2 2 2 和 6 6 6 种结果,组合起来便有 12 12 12 种概率相同的结果。
把这 12 12 12 种结果映射到 [ 1 , 12 ] [1,12] [1,12] ,然后再拒绝 12 12 12 即可。
r a n d 100 ( ) rand100() rand100():
构造 3 3 3 次采样,分别有 4 , 5 , 5 4,5,5 4,5,5 种结果,组合起来便有 100 100 100 种概率相同的结果。
把这 100 100 100 种结果映射到 [ 1 , 100 ] [1,100] [1,100] 即可。
质数定义:x是质数,则它的因数只包含 [1,x]
方法1:暴力
方法二:根号优化
注意,因数一定是成对出现的,当遍历到 x \sqrt x x时还未找到一个因数,那么之后的数也不是 x 的因数了
举例 x = 24:
方法三:继续优化,除2以外的偶数一定不是质数,只判断奇数
def isPrime(n: int):
if n==2:
return True
elif n%2==0:
return False
for i in range(3, int(n**0.5)+1, 2):
if n%i==0:
return False
return True
给定整数 n ,返回 所有小于非负整数 n 的质数的数量 。
示例 1:
输入:n = 10
输出:4
解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
示例 2:
输入:n = 0
输出:0
示例 3:
输入:n = 1
输出:0
提示:
0 <= n <= 5 * 106
方法一:暴力解(每个数都单独判断一次)
超时
方法二:埃氏筛
考虑 每个数之间的关联性,基于以下事实:
因此,构造一个 vector
数组,表示第 i
位 是否为质数
def countPrimes(self, n: int) -> int:
isPrimes = [1]*n
cnt = 0
for i in range(2, n):
if isPrimes[i]:
cnt+=1
for j in range(i*i, n, i): # 从 i*i开始,消除冗余
isPrimes[j] = 0
return cnt
时间复杂度: O ( n log log n ) O(n\log \log n) O(nloglogn)
给定一个正整数,求它的质因数
思路:
从 idx=2 开始,满足条件则不断更新 x=x//idx
否则 ++idx
def primeFators(self, x: int)->List[int]:
idx = 2
ans = []
while x>1:
if x%idx==0:
ans.append(idx)
x //= idx
else:
idx += 1
return ans
优化,只判断前根号个数
定理:
退出 while 循环后,如果 x!=1
,则剩下的x一定是一个质数
def primeFators(self, x: int)->List[int]:
idx = 2
ans = []
end = int(x**0.5)+1
while x>1 and idx<end: # end 用于剪枝,复杂度根号n
if x%idx==0:
ans.append(idx)
x //= idx
else:
idx += 1
if x!=1:
ans.append(x)
return ans
给定两个正整数 x,y,求其 gcd
方法:辗转相除法
gcd ( x , y ) = gcd ( y , x m o d y ) \gcd({\color{red}x},{\color{blue}y})=\gcd({\color{blue}y}, {\color{red}x}\mod {\color{blue}y}) gcd(x,y)=gcd(y,xmody).
def _gcd(x: int, y: int)->int:
if y==0:
return x
return _gcd(y, x%y)
def _lcd(x: int, y: int)->int:
return x*y//_gcd(x, y)
二进制数转字符串。给定一个介于0和1之间的实数(如0.72),类型为double,打印它的二进制表达式。如果该数字无法精确地用32位以内的二进制表示,则打印“ERROR”。
示例1:
输入:0.625
输出:"0.101"
示例2:
输入:0.1
输出:"ERROR"
提示:0.1无法被二进制准确表示
提示:
32位包括输出中的 "0." 这两位。
题目保证输入用例的小数位数最多只有 6 位
思路
介于 0 0 0 和 1 1 1 之间的实数的整数部分是 0 0 0,小数部分大于 0 0 0,因此其二进制表示的整数部分是 0 0 0,需要将小数部分转换成二进制表示。
以示例 1 1 1 为例,十进制数 0.625 0.625 0.625 可以写成 1 2 1 + 1 2 3 \dfrac{1}{2^1} + \dfrac{1}{2^3} 211+231,因此对应的二进制数是 0.10 1 ( 2 ) 0.101_{(2)} 0.101(2) ,二进制数中的左边的 1 1 1 为小数点后第一位,表示 1 2 1 \dfrac{1}{2^1} 211 ,右边的 1 1 1 为小数点后第三位,表示 1 2 3 \dfrac{1}{2^3} 231 。
如果将十进制数 0.625 0.625 0.625 乘以 2 2 2,则得到 1.25 1.25 1.25,可以写成 1 + 1 2 2 1 + \dfrac{1}{2^2} 1+221 ,因此对应的二进制数是 1.0 1 ( 2 ) 1.01_{(2)} 1.01(2) 。二进制数 0.10 1 ( 2 ) 0.101_{(2)} 0.101(2) 的两倍是 1.0 1 ( 2 ) 1.01_{(2)} 1.01(2),因此在二进制表示中,将一个数乘以 2 2 2 的效果是将小数点向右移动一位。
根据上述结论, 将实数的十进制表示转换成二进制表示的方法 \color{red}将实数的十进制表示转换成二进制表示的方法 将实数的十进制表示转换成二进制表示的方法是:
由于这道题要求二进制表示的长度最多为 32 32 32 位,否则返回 “ERROR" \text{``ERROR"} “ERROR",因此不需要判断给定实数的二进制表示的结果是有限小数还是无限循环小数,而是在小数部分变成 0 0 0 或者二进制表示的长度超过 32 32 32 位时结束操作。当操作结束时,如果二进制表示的长度不超过 32 32 32 位则返回二进制表示,否则返回 “ERROR" \text{``ERROR"} “ERROR"。
def printBin(self, num: float) -> str:
ans = "0."
while len(ans) <= 32 and num!=0:
num *= 2
digit = int(num)
ans += str(digit)
num -= digit
return ans if len(ans)<=32 else "ERROR"
给你一个整数数组 nums ,返回其中 按位与三元组 的数目。
按位与三元组 是由下标 (i, j, k) 组成的三元组,并满足下述全部条件:
0 <= i < nums.length
0 <= j < nums.length
0 <= k < nums.length
nums[i] & nums[j] & nums[k] == 0
,其中 & 表示按位与运算符。
示例 1:
输入:nums = [2,1,3]
输出:12
解释:可以选出如下 i, j, k 三元组:
(i=0, j=0, k=1) : 2 & 2 & 1
(i=0, j=1, k=0) : 2 & 1 & 2
(i=0, j=1, k=1) : 2 & 1 & 1
(i=0, j=1, k=2) : 2 & 1 & 3
(i=0, j=2, k=1) : 2 & 3 & 1
(i=1, j=0, k=0) : 1 & 2 & 2
(i=1, j=0, k=1) : 1 & 2 & 1
(i=1, j=0, k=2) : 1 & 2 & 3
(i=1, j=1, k=0) : 1 & 1 & 2
(i=1, j=2, k=0) : 1 & 3 & 2
(i=2, j=0, k=1) : 3 & 2 & 1
(i=2, j=1, k=0) : 3 & 1 & 2
示例 2:
输入:nums = [0,0,0]
输出:27
提示:
1 <= nums.length <= 1000
0 <= nums[i] < 216
思路:哈希表降时间复杂度
最容易想到的做法是使用三重循环枚举三元组 ( i , j , k ) (i,j,k) (i,j,k),再判断 nums [ i ] & nums [ j ] & nums [ k ] \textit{nums}[i] ~\&~ \textit{nums}[j] ~\&~ \textit{nums}[k] nums[i] & nums[j] & nums[k] 的值是否为 0 0 0。但这样做的时间复杂度是 O ( n 3 ) O(n^3) O(n3),其中 n n n 是数组 nums \textit{nums} nums 的长度,会超出时间限制。
注意到题目中给定了一个限制:数组 nums \textit{nums} nums 的元素不会超过 2 16 2^{16} 216。这说明, nums [ i ] & nums [ j ] \textit{nums}[i] ~\&~ \textit{nums}[j] nums[i] & nums[j] 的值也不会超过 2 16 2^{16} 216。因此,我们可以首先使用二重循环枚举 i i i 和 j j j,并使用一个长度为 2 16 2^{16} 216 的数组(或哈希表)存储每一种 nums [ i ] & nums [ j ] \textit{nums}[i] ~\&~ \textit{nums}[j] nums[i] & nums[j] 以及它出现的次数。随后,我们再使用二重循环,其中的一重枚举记录频数的数组,另一重枚举 k k k,这样就可以将时间复杂度从 O ( n 3 ) O(n^3) O(n3) 降低至 O ( n 2 + 2 16 ⋅ n ) O(n^2 + 2^{16} \cdot n) O(n2+216⋅n)。
def countTriplets(self, nums: List[int]) -> int:
from collections import Counter
cnt = Counter((x&y) for x in nums for y in nums)
ans = 0
for x in nums:
for mask, freq in cnt.items():
if (x&mask)==0:
ans += freq
return ans
在一根无限长的数轴上,你站在0的位置。终点在target的位置。
你可以做一些数量的移动 numMoves :
每次你可以选择向左或向右移动。
第 i 次移动(从 i == 1 开始,到 i == numMoves ),在选择的方向上走 i 步。
给定整数 target ,返回 到达目标所需的 最小 移动次数(即最小 numMoves ) 。
示例 1:
输入: target = 2
输出: 3
解释:
第一次移动,从 0 到 1 。
第二次移动,从 1 到 -1 。
第三次移动,从 -1 到 2 。
示例 2:
输入: target = 3
输出: 2
解释:
第一次移动,从 0 到 1 。
第二次移动,从 1 到 3 。
提示:
-109 <= target <= 109
target != 0
方法一:分析 + 数学
假设移动了 k k k 次,每次任意地向左或向右移动,那么最终达到的位置实际上就是将 1 , 2 , 3 , … , k 1,2,3,\ldots,k 1,2,3,…,k 这 k k k 个整数添加正号或负号后求和的值。如果 target < 0 \textit{target} < 0 target<0,可以将这 k k k 个数的符号全部取反,这样求和的值为 − target > 0 -\textit{target} > 0 −target>0。因此我们可以只考虑 target > 0 \textit{target} > 0 target>0 的情况。
设 k k k 为最小的满足 s = ∑ i = 1 k ≥ target s s = \sum_{i=1}^{k} \geq \textit{target}s s=∑i=1k≥targets 的正整数。
如果 delta = s − target \textit{delta} = s - \textit{target} delta=s−target 为偶数,则目标为从 1 1 1 到 k k k 中找出若干个整数使得他们的和为 delta 2 \dfrac{\textit{delta}}{2} 2delta,下面证明一定能到找这样的若干个整数。
如果 delta \textit{delta} delta 为奇数,那么就无法凑出这样的若干个数字。考虑 k + 1 k+1 k+1 和 k + 2 k+2 k+2, ∑ i = 1 k + 1 \sum_{i=1}^{k+1} ∑i=1k+1 和 ∑ i = 1 k + 2 \sum_{i=1}^{k+2} ∑i=1k+2 中必有一个和 s s s 的奇偶性相同,使得此时的 delta \textit{delta} delta 为偶数。此时也满足 ⌊ delta 2 ⌋ < ∑ \Big\lfloor \dfrac{\textit{delta}}{2} \Big\rfloor < \sum ⌊2delta⌋<∑,因此也可以找到若干个数的和为 ⌊ delta 2 ⌋ \Big\lfloor \dfrac{\textit{delta}}{2} \Big\rfloor ⌊2delta⌋。
class Solution:
def reachNumber(self, target: int) -> int:
target = abs(target)
k = 0
while target > 0:
k += 1
target -= k
return k if target % 2 == 0 else k + 1 + k % 2
一个序列的 宽度 定义为该序列中最大元素和最小元素的差值。
给你一个整数数组 nums ,返回 nums 的所有非空 子序列 的 宽度之和 。由于答案可能非常大,请返回对 109 + 7 取余 后的结果。
子序列 定义为从一个数组里删除一些(或者不删除)元素,但不改变剩下元素的顺序得到的数组。例如,[3,6,2,7] 就是数组 [0,3,1,6,2,2,7] 的一个子序列。
示例 1:
输入:nums = [2,1,3]
输出:6
解释:子序列为 [1], [2], [3], [2,1], [2,3], [1,3], [2,1,3] 。
相应的宽度是 0, 0, 0, 1, 1, 2, 2 。
宽度之和是 6 。
示例 2:
输入:nums = [2]
输出:0
提示:
1 <= nums.length <= 105
1 <= nums[i] <= 105
思路:转换一下,将两两之间的计算,转换为每个元素的对最终答案的共享
class Solution:
def sumSubseqWidths(self, nums: List[int]) -> int:
nums.sort()
n = len(nums)
ans, MOD = 0, 10**9+7
ans, pow2 = 0, 1
for x, y in zip(nums, reversed(nums)):
ans += (x - y) * pow2
pow2 = pow2 * 2 % MOD
return ans % MOD
一个正整数如果能被 a 或 b 整除,那么它是神奇的。
给定三个整数 n , a , b ,返回第 n 个神奇的数字。因为答案可能很大,所以返回答案 对 109 + 7 取模 后的值。
示例 1:
输入:n = 1, a = 2, b = 3
输出:2
示例 2:
输入:n = 4, a = 2, b = 3
输出:6
提示:
1 <= n <= 109
2 <= a, b <= 4 * 104
找到增函数关系,二分法
题目给出三个数字 n n n, a a a, b b b,满足 1 ≤ n ≤ 1 0 9 1 \le n \le 10^9 1≤n≤109 , 2 ≤ a , b ≤ 4 × 1 0 4 2 \le a, b \le 4 \times 10^4 2≤a,b≤4×104 ,并给出「神奇数字」的定义:若一个正整数能被 a a a 和 b b b 整除,那么它就是「神奇」的。现在需要求出对于给定 a a a 和 b b b 的第 n n n 个「神奇数字」。设 f ( x ) f(x) f(x) 表示为小于等于 x x x 的「神奇数字」个数,因为小于等于 x x x 中能被 a a a 整除的数的个数为 ⌊ x a ⌋ \lfloor \frac{x}{a} \rfloor ⌊ax⌋,小于等于 x x x 中能被 b b b 整除的个数为 ⌊ x b ⌋ \lfloor \frac{x}{b} \rfloor ⌊bx⌋,小于等于 x x x 中同时能被 a a a 和 b b b 整除的个数为 ⌊ x c ⌋ \lfloor \frac{x}{c} \rfloor ⌊cx⌋,其中 c c c 为 a a a 和 b b b 的最小公倍数,所以 f ( x ) f(x) f(x)的表达式为:
f ( x ) = ⌊ x a ⌋ + ⌊ x b ⌋ − ⌊ x c ⌋ f(x) = \lfloor \frac{x}{a} \rfloor + \lfloor \frac{x}{b} \rfloor - \lfloor \frac{x}{c} \rfloor f(x)=⌊ax⌋+⌊bx⌋−⌊cx⌋
即 f ( x ) f(x) f(x) 是一个随着 x x x 递增单调不减函数。那么我们可以通过「二分查找」来进行查找第 n n n 个「神奇数字」。
def nthMagicalNumber(self, n: int, a: int, b: int) -> int:
from math import lcm
MOD = 10 ** 9 + 7
c = lcm(a, b)
func = lambda x: x // a + x // b - x // c
l = min(a, b)
r = n * min(a, b) + 1
while l < r:
m = l + (r - l) // 2
cnt = func(m)
if cnt == n: # 注意此处 如果 cnt==n 这个数不一定是 结果
r = m # 因为如果 x是答案 x~x+(min(a,b)-1)的数的CC是一样的
elif cnt > n:
r = m
else:
l = m + 1
return r % MOD
给定数组 nums 和一个整数 k 。我们将给定的数组 nums 分成 最多 k 个相邻的非空子数组 。 分数 由每个子数组内的平均值的总和构成。
注意我们必须使用 nums 数组中的每一个数进行分组,并且分数不一定需要是整数。
返回我们所能得到的最大 分数 是多少。答案误差在 10-6 内被视为是正确的。
示例 1:
输入: nums = [9,1,2,3,9], k = 3
输出: 20.00000
解释:
nums 的最优分组是[9], [1, 2, 3], [9]. 得到的分数是 9 + (1 + 2 + 3) / 3 + 9 = 20.
我们也可以把 nums 分成[9, 1], [2], [3, 9].
这样的分组得到的分数为 5 + 2 + 6 = 13, 但不是最大值.
示例 2:
输入: nums = [1,2,3,4,5,6,7], k = 4
输出: 20.50000
提示:
1 <= nums.length <= 100
1 <= nums[i] <= 104
1 <= k <= nums.length
思路:DP
命题:平均值和最大的分组的子数组数目必定是 k k k。
证明略,见:
https://leetcode.cn/problems/largest-sum-of-averages/solutions/1993132/zui-da-ping-jun-zhi-he-de-fen-zu-by-leet-09xt/
因此转移方程为:
dp [ i ] [ j ] = { ∑ r = 0 i − 1 nums [ r ] i , j = 1 max x ≥ j − 1 { d p [ x ] [ j − 1 ] + ∑ r = x i − 1 nums [ r ] i − x } , j > 1 \textit{dp}[i][j] = \begin{cases} \dfrac{\sum_{r = 0}^{i - 1}\textit{nums}[r]}{i}, & j = 1 \\ \max\limits_{x \ge j - 1} \{dp[x][j - 1] + \dfrac{\sum_{r = x}^{i - 1}\textit{nums}[r]}{i - x}\}, & j > 1 \end{cases} dp[i][j]=⎩ ⎨ ⎧i∑r=0i−1nums[r],x≥j−1max{dp[x][j−1]+i−x∑r=xi−1nums[r]},j=1j>1
假设数组 nums \textit{nums} nums 的长度为 n n n,那么 dp [ n ] [ k ] \textit{dp}[n][k] dp[n][k] 表示数组 nums \textit{nums} nums 分成 k k k 个子数组后的最大平均值和,即最大分数。
给你一个正整数数组 nums,请你移除 最短 子数组(可以为 空),使得剩余元素的 和 能被 p 整除。 不允许 将整个数组都移除。
请你返回你需要移除的最短子数组的长度,如果无法满足题目要求,返回 -1 。
子数组 定义为原数组中连续的一组元素。
示例 1:
输入:nums = [3,1,4,2], p = 6
输出:1
解释:nums 中元素和为 10,不能被 p 整除。我们可以移除子数组 [4] ,剩余元素的和为 6 。
示例 2:
输入:nums = [6,3,5,2], p = 9
输出:2
解释:我们无法移除任何一个元素使得和被 9 整除,最优方案是移除子数组 [5,2] ,剩余元素为 [6,3],和为 9 。
示例 3:
输入:nums = [1,2,3], p = 3
输出:0
解释:和恰好为 6 ,已经能被 3 整除了。所以我们不需要移除任何元素。
示例 4:
输入:nums = [1,2,3], p = 7
输出:-1
解释:没有任何方案使得移除子数组后剩余元素的和被 7 整除。
示例 5:
输入:nums = [1000000000,1000000000,1000000000], p = 3
输出:0
提示:
1 <= nums.length <= 105
1 <= nums[i] <= 109
1 <= p <= 109
方法一:前缀和 + 两层循环遍历 (超时)
前缀和 用于记录 连续子数组的和,无脑两层循环遍历找到最终答案
def minSubarray(self, nums: List[int], p: int) -> int:
preSum = [0 for _ in range(len(nums)+1)]
for i in range(len(nums)):
preSum[i+1] = preSum[i] + nums[i]
if preSum[-1]%p==0:
return 0
ans = len(nums)
for i in range(len(preSum)):
for j in range(i+1, len(preSum)):
if preSum[-1]-preSum[j]+preSum[i]%p==0:
ans = min(ans, j-i)
return ans if ans<len(nums) else -1
时间复杂度: O ( n 2 ) O(n^2) O(n2)
方法二:数学化简 + 前缀和 + 哈希表
证明: y m o d p = x y \bmod p = x ymodp=x 等价于 y = k 1 × p + x y = k_1 \times p + x y=k1×p+x, ( y − z ) m o d p = 0 (y-z) \bmod p = 0 (y−z)modp=0 等价于 y − z = k 2 × p y - z = k_2 \times p y−z=k2×p, z m o d p = x z \bmod p = x zmodp=x 等价于 z = k 3 × p + x z = k_3 \times p + x z=k3×p+x 都是整数,那么给定 y = k 1 × p + x y = k_1 \times p + x y=k1×p+x,有 y − z = k 2 × p ↔ z = ( k 1 − k 2 ) × p + x ↔ z = k 3 × p + x y - z = k_2 \times p \leftrightarrow z = (k_1 - k_2) \times p + x \leftrightarrow z = k_3 \times p + x y−z=k2×p↔z=(k1−k2)×p+x↔z=k3×p+x。
证明: ( y − z ) m o d p = x (y - z) \bmod p = x (y−z)modp=x 等价于 y − z = k 1 × p + x y - z = k_1 \times p + x y−z=k1×p+x,其中 k 1 k_1 k1是整数,经过变换有 z = y − k 1 × p − x = k 2 × p + ( y − x ) m o d p − k 1 × p = ( k 2 − k 1 ) × p + ( y − x ) m o d p z = y - k_1 \times p - x = k_2 \times p + (y - x) \bmod p - k_1 \times p = (k_2 - k_1) \times p + (y - x) \bmod p z=y−k1×p−x=k2×p+(y−x)modp−k1×p=(k2−k1)×p+(y−x)modp,等价于 z m o d p = ( y − x ) m o d p z \bmod p = (y - x) \bmod p zmodp=(y−x)modp。
因此,这里我们利用定理1,改进方法1中的语句
preSum[-1]-preSum[j]+preSum[i]%p == 0
= > => =>
preSum[-1]+preSum[i]%p == preSum[j]%p
此时,等式两边只存在一个变量,因此我们可以用哈希表对其进行优化。
def minSubarray(self, nums: List[int], p: int) -> int:
preSum = [0 for _ in range(len(nums)+1)]
for i in range(len(nums)):
preSum[i+1] = preSum[i] + nums[i]
if preSum[-1]%p==0:
return 0
ans = len(nums)
dic = {}
for i in range(len(preSum)):
cur = preSum[i]%p
if cur in dic:
ans = min(ans, i-dic[cur])
dic[(preSum[-1] + preSum[i])%p] = i
return ans if ans<len(nums) else -1
时间复杂度: O ( n ) O(n) O(n)