1.脑筋急转弯问题
LeetCode292题 Nim游戏
如果一个人拿的时候只剩4颗石子,那么无论他怎么拿,总会剩下1~3颗石子,对手都能赢
class Solution:
def canWinNim(self, n: int) -> bool:
if n % 4 == 0:
return False
else:
return True
LeetCode319题 灯泡开关
只有平方数对应的灯泡,乘法因子个数是奇数,最后是亮的
class Solution:
def bulbSwitch(self, n: int) -> int:
return int(pow(n, 0.5))
LeetCode877题 石子游戏
注意到石头堆数量为偶数,石头的总数为奇数。
如果我们把石头按索引的奇偶分为两组,那么这两组石头的数量一定不同,也就是说一堆多一堆少。因为石头的总数是奇数,不能被平分。而作为第一个拿石头的人,你可以控制自己拿到所有偶数堆/奇数堆。
比如一共有四堆石头,你最开始可以选择第 1 堆或第 4 堆。如果你想要偶数堆,你就拿第 4 堆,这样留给对手的选择只有第 1、3 堆,他不管怎么拿,第 2 堆又会暴露出来,你就可以拿。如果你想拿奇数堆也是同理。
class Solution:
def stoneGame(self, piles: List[int]) -> bool:
return True
2.位运算
原码、反码、补码都是计算机存储具体数字的编码方式,以二进制进行编码。原码的最左侧位是符号位,0代表正数,1代表负数。
反码:主要针对负数的处理,非负数的反码与原码相同;负数的反码在原码基础上,符号位不变,其他位取反
补码:非负数的补码与原码相同;负数的补码在反码基础上+1
7 | -7 | |
原码 | 0000 0111 | 1000 0111 |
反码 | 0000 0111 | 1111 1000 |
补码 | 0000 0111 | 1111 1001 |
补码的提出是为了方便计算,计算机底层以补码形式保存和处理所有整数,所有位运算符都是以补码形式操作的
例如我们用补码做减法,结果就是对的:7-7=7+(-7)=0000 0111 + 1111 1001=0000 0000=0
位运算符:
与 | & | 判断奇偶:1 & x 等于1,x为奇数;等于0,x为偶数 |
或 | | | |
异或 | ^ | 相同为0,不同为1 |
取反 | ~ | 7二进制为 0000 0111, ~7=1111 1000(是补码形式,需要再取补码得原码) ->1000 0111(反码) ->1000 1000(补码) ->-8(转换为十进制) |
左移 | << | a<<x,运算数a所有二进制位向左移x位,高位丢弃,低位补零, 7二进制为 0000 0111,左移1位后变成 0000 1110,对应十进制数为14,即7<<1=14,左移1位相当于乘2 |
右移 | >> | a>>x,运算数a所有二进制位向右移x位,低位丢弃,高位按符号位补全, -7>>1,-7补码为1111 1001,右移1位后为1111 1100, 再取补码为原码1111 1100->1000 0011->1000 0100,对应十进制为-4,即-7>>1=-4 |
n&(n-1)运用
n&(n-1)
这个操作在算法中比较常见,作用是消除数字n
的二进制表示中的最后一个 1
LeetCode191题 位1的个数
class Solution:
def hammingWeight(self, n: int) -> int:
res = 0
while n != 0:
res += 1
n = n & n - 1
return res
异或运算应用
异或运算的性质:一个数和它本身做异或运算结果为0,即a^a=0;
一个数和0做异或运算的结果为它本身,即a^0=a
LeetCode136题 只出现一次的数字
成对的数字异或就会变成0,落单的数字和0做异或还是它本身,所以最后异或的结果就是只出现一次的元素
class Solution:
def singleNumber(self, nums: List[int]) -> int:
res = 0
for i in nums:
res ^= i
return res
3.随机算法
LeetCode382题 链表的随机节点
如何从包含未知大小的数据流中随机选取k个数据,并且要保证每个数据被抽取到的概率相等。当k=1时,即此题的情况
蓄水池抽样算法:每次只保留一个数,当遇到第 i 个数时,以 1/i的概率保留它,(i-1)/i的概率保留原来的数。举例说明:
- 遇到1,概率为1,保留第一个数
- 遇到2,概率为1/2,这个时候,1和2各1/2的概率被保留
- 遇到3,3被保留的概率为1/3,(之前剩下的数假设1被保留),2/3的概率 1 被保留,(此时1被保留的总概率为 2/3 * 1/2 = 1/3)
- 遇到4,4被保留的概率为1/4,(之前剩下的数假设1被保留),3/4的概率 1 被保留,(此时1被保留的总概率为 3/4 * 2/3 * 1/2 = 1/4)
- 以此类推,每个数被保留的概率都是1/N
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def __init__(self, head: Optional[ListNode]):
self.head = head
def getRandom(self) -> int:
count = 0
reserve = 0
cur = self.head
while cur:
count += 1
rand = random.randint(1, count)
if rand == count:
reserve = cur.val
cur = cur.next
return reserve
# Your Solution object will be instantiated and called as such:
# obj = Solution(head)
# param_1 = obj.getRandom()
LeetCode384题 打乱数组
洗牌算法,对于下标x而言,我们从[x,n−1]中随机出一个位置与x进行值交换,当所有位置都进行这样的处理后,我们便得到了一个公平的洗牌方案
lass Solution:
def __init__(self, nums: List[int]):
self.nums = nums
def reset(self) -> List[int]:
return self.nums
def shuffle(self) -> List[int]:
nums = self.nums[:]
for i in range(len(nums)):
import random
rand_num = random.randint(i, len(nums) - 1)
nums[i], nums[rand_num] = nums[rand_num], nums[i]
return nums
# Your Solution object will be instantiated and called as such:
# obj = Solution(nums)
# param_1 = obj.reset()
# param_2 = obj.shuffle()
4.阶乘相关
LeetCode172题 阶乘后的零
阶乘后的零的个数=乘法因子中5的个数,时间复杂度为log n
class Solution:
def trailingZeroes(self, n: int) -> int:
p = 1
res = 0
while p * 5 <= n:
p *= 5
res += n // p
return res
LeetCode793题 阶乘函数后K个零
假定n是阶乘数,k是阶乘后尾部零个数
上一题是给定n求k,这题是给定k,问k的合法性,如果k合法,那么符合k的n有5种;如果k不合法,那么符合k的n有0种
那么可以结合上题的代码,可以用二分法搜索k是否合法,搜索的n范围是[0, 5 * k],合法返回5,不合法返回0
class Solution:
def preimageSizeFZF(self, k: int) -> int:
left = 0
right = 5 * k
while left <= right:
mid = (left + right) // 2
mid_res = self.trailingZeroes(mid)
if mid_res == k:
return 5
elif mid_res < k:
left = mid + 1
else:
right = mid - 1
return 0
def trailingZeroes(self, n: int) -> int:
p = 1
res = 0
while p * 5 <= n:
p *= 5
res += n // p
return res
5.高效寻找素数
LeetCode204题 计数质数
用最小的素数,去进行扩散,排除掉非素数
class Solution:
def countPrimes(self, n: int) -> int:
if n <= 2:
return 0
is_primes = [False if i % 2 == 0 and i > 2 else True for i in range(n)]
for i in range(2, int(n**0.5) + 1):
if is_primes[i]:
for j in range(i * i, n, i):
is_primes[j] = False
res = 1
for i in range(3, n, 2):
if is_primes[i]:
res += 1
return res
6.高效进行模幂运算
LeetCode372题 超级次方
乘法在取模的意义下满足分配律:
可以推导出:
然后pow(x,n)的运算可以退化为pow(x,n/2) * pow(x,n/2)...,时间复杂度为logn
class Solution:
def superPow(self, a: int, b: List[int]) -> int:
res = 1
for i in range(len(b) - 1, -1, -1):
res *= self.pos_pow_mod(a, b[i])
a = self.pos_pow_mod(a, 10)
return res % 1337
def pos_pow_mod(self, x, n):
if n == 1:
return x % 1337
if n == 0:
return 1
if n % 2 == 0:
tmp = self.pos_pow_mod(x, n / 2)
return (tmp * tmp) % 1337
else:
tmp = self.pos_pow_mod(x, (n - 1) / 2)
return ((x % 1337) * tmp * tmp) % 1337