格式:
题号+题名+简单思路+code
T204: 计数质数
- 埃拉托色尼筛选法
- 若一个数是质数,则其从2开始的倍数应该是合数
- 范围可以缩小到 i 2 i^2 i2
- O(nloglogn)时间复杂度
- ( n ∗ ∑ P r i m e N u m b e r 1 2 + 1 3 + 1 5 + 1 7 + . . . + 1 m a x P r i m e n*\sum_{PrimeNumber}{\frac{1}{2}+\frac{1}{3}+\frac{1}{5}+\frac{1}{7}+...+\frac{1}{maxPrime}} n∗∑PrimeNumber21+31+51+71+...+maxPrime1)
func countPrimes(n int) int {
isPrime:=make([]bool,n)
for i:=0;i<len(isPrime);i++ {
isPrime[i]=true
}
for i:=2;i*i<n;i++ {
if isPrime[i] {
for j:=i*i;j<n;j+=i {
isPrime[j]=false
}
}
}
count:=0
for i:=2;i<n;i++ {
if isPrime[i] {
count++
}
}
return count
}
T535: TinyURL的加密
- 随机数
- 哈希表
- 不受int范围影响,如用数字加字母来作为加密,有 ( 10 + 26 ∗ 2 ) m (10+26*2)^m (10+26∗2)m种
public class Codec {
String alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
HashMap<String, String> map = new HashMap<>();
Random rand = new Random();
String key = getRand();
public String getRand() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 6; i++) {
sb.append(alphabet.charAt(rand.nextInt(62)));
}
return sb.toString();
}
public String encode(String longUrl) {
while (map.containsKey(key)) {
key = getRand();
}
map.put(key, longUrl);
return "http://tinyurl.com/" + key;
}
public String decode(String shortUrl) {
return map.get(shortUrl.replace("http://tinyurl.com/", ""));
}
}
T771: 宝石与石头
- 考点主要在由于只使用了字母,所以可以用数组构造哈希表
- 字母在ASCII中的跨度为 65 ∼ 122 65 \sim 122 65∼122
func numJewelsInStones(J string, S string) int {
jewel:=make([]bool, 58)
for i := range J {
jewel[J[i]-'A']=true
}
count:=0
for i := range S {
if jewel[S[i]-'A'] {
count++
}
}
return count
}
T136: 只出现一次的数字
- 要求O(N)时间复杂度;O(1)空间复杂度
- 位运算
- 异或运算的特点:① a ⊗ a = 0 a \otimes a=0 a⊗a=0;② a ⊗ 0 = a a \otimes 0=a a⊗0=a;③ ⊗ 运 算 满 足 交 换 律 和 结 合 律 \otimes 运算满足交换律和结合律 ⊗运算满足交换律和结合律
func singleNumber(nums []int) int {
ans:=0
for i:=0;i<len(nums);i++ {
ans^=nums[i]
}
return ans
}
T137: 只出现一次的数字Ⅱ
- 设置三个变量,分别表示二进制位上出现的次数;最后返回出现一次的变量
- T136相当于有两个变量
func singleNumber(nums []int) int {
var one, two, three int
for i := range nums {
two|=one & nums[i]
one^=nums[i]
three=two&one
one&=^three
two&=^three
}
return one
}
T260: 只出现一次的数字Ⅲ
- 设这俩只出现一次的数为m, n;先通过一次遍历找出m^n
- 此时m^n必然有一个位置,一个数在该位置为1,另一个数为0;设最后为1的位为二级制的第i位
- 那么我们根据第i位为0/1将数组分成两组,这时相同的数也会分到同一组,此时变成T136
func singleNumber(nums []int) []int {
tmp:=0
for i:=range nums {
tmp^=nums[i]
}
m,n:=0,0
bit:=1
for tmp&bit==0 {
bit<<=1
}
for i:=range nums {
if nums[i]&bit==0 {
m^=nums[i]
} else {
n^=nums[i]
}
}
return []int{m, n}
}
T7: 整数反转
- 正向取余→反向乘十
class Solution:
MAXINT=(1<<31)-1
MININT_=1<<31
def reverse(self, x: int) -> int:
t=abs(x)
ans=0
while t!=0:
i=t%10
ans=ans*10+i
t=t//10
if ans>self.MAXINT:
if x>0:
return 0
if x<0 and ans>self.MININT_:
return 0
return ans if x>0 else -ans
T371: 两整数之和
- ①a^b求出所有不需要进位的1
- ②a&b求出所有需要进位的1
- 只要有需要进位的1,就再次进行循环①②
func getSum(a int, b int) int {
x:=a^b
y:=a&b
for y!=0 {
y<<=1
tmp:=x
x=tmp^y
y=tmp&y
}
return x
}
T13: 罗马数字转整数
class Solution:
dic={"I":1, "V":5, "X":10, "L":50, "C":100, "D":500, "M":1000, "IV":4, "IX":9, "XL":40, "XC":90, "CD":400, "CM":900}
def romanToInt(self, s: str) -> int:
ans=0
i=0
while i < len(s):
if i+1 < len(s) and s[i:i+2] in self.dic:
ans+=self.dic[s[i:i+2]]
i+=1
else:
ans+=self.dic[s[i]]
i+=1
return ans
T12: 整数转罗马数字
- 记忆化递归
dic={"I":1, "V":5, "X":10, "L":50, "C":100, "D":500, "M":1000, "IV":4, "IX":9, "XL":40, "XC":90, "CD":400, "CM":900}
r_dic={dic[key]:key for key in dic}
searchList=sorted(list(r_dic.keys()))
class Solution:
memo={}
def intToRoman(self, num: int) -> str:
if num==0:
return ""
if num in self.memo:
return self.memo[num]
tmp=self.binarySearch(num)
ans=r_dic[tmp]+self.intToRoman(num-tmp)
self.memo[num]=ans
return ans
def binarySearch(self, num: int) -> int: # 二分搜索找到不大于当前值的最大值
lo=0
hi=len(searchList)-1
while lo<=hi:
mid=(hi-lo)//2+lo
if searchList[mid]>num:
hi=mid-1
elif searchList[mid]<num:
lo=mid+1
else:
return searchList[mid]
return searchList[lo-1]
T338: 比特位计数
- 类似T12,都使用了DP的思想;O(Nlog2M)时间复杂度
- f ( n ) = f ( n − 2 [ l o g 2 ( n ) ] ) + 1 f(n)=f(n-2^{[log_2(n)]})+1 f(n)=f(n−2[log2(n)])+1
func countBits(num int) []int {
memo:=make([]int, num+1)
for i:=1;i<len(memo);i++ {
tmp:=i
margin:=1
for tmp!=1 {
tmp>>=1
margin<<=1
}
memo[i]=1+memo[i-margin]
}
return memo
}
T9: 回文数
class Solution:
def isPalindrome(self, x: int) -> bool:
if x<0:
return False
tmp=x
ans=0
while tmp!=0:
ans=ans*10+tmp%10
tmp=tmp//10
return ans==x
T476: 数字的补数
观察规律可得补数与取反的差别
func findComplement(num int) int {
tmp:=num
c:=0
for tmp!=0 {
tmp>>=1
c++
}
return 1<<c+(^num)
}
T78: 子集
- 位运算,通过1的位置来表示是否选该元素
func subsets(nums []int) [][]int {
ans:=[][]int{}
i:=0
for i < 1<<len(nums) {
tmp:=i
index:=0
res:=[]int{}
for tmp!=0 {
if tmp%2==1 {
res=append(res, nums[index])
}
index++
tmp=tmp>>1
}
i++
ans=append(ans, res)
}
return ans
}
T401: 二进制手表
- 回溯法剪枝得到小时或者分钟亮n个时的所有不重复值;再组合起来
var hour []int=[]int{1, 2, 4, 8}
var minute []int=[]int{1, 2, 4, 8, 16, 32}
func readBinaryWatch(num int) []string {
ans:=[]string{}
for i:=0;i<=num;i++ {
j:=num-i
ans_hour:=[]int{}
readBinaryWatchAssist(i, 0, 11, hour, 0, &ans_hour)
ans_minute:=[]int{}
readBinaryWatchAssist(j, 0, 59, minute, 0, &ans_minute)
for idx_h:=0;idx_h<len(ans_hour);idx_h++ {
for idx_m:=0;idx_m<len(ans_minute);idx_m++ {
ans=append(ans, strconv.Itoa(ans_hour[idx_h])+":"+fmt.Sprintf("%02s", strconv.Itoa(ans_minute[idx_m])))
}
}
}
return ans
}
func readBinaryWatchAssist(num int, sum int, limit int, list []int, idx int, ans_hour *[]int) {
if sum>limit {
return
}
if num==0 {
*ans_hour=append(*ans_hour, sum)
return
}
if idx==len(list) {
return
}
for i:=idx;i<len(list);i++ {
readBinaryWatchAssist(num-1, sum+list[i], limit, list, i+1, ans_hour)
}
}
T886: 可能的二分法
- 二分图问题
func possibleBipartition(N int, dislikes [][]int) bool {
graph:=make(map[int][]int,N)
for i:=1;i<=N;i++ {
graph[i]=[]int{}
}
for i:=0;i<len(dislikes);i++ {
a:=dislikes[i][0]
b:=dislikes[i][1]
graph[a]=append(graph[a],b)
graph[b]=append(graph[b],a)
}
marked:=make([]bool,N+1)
color:=make([]bool,N+1)
for i:=1;i<=N;i++ {
if !marked[i] {
if !dfs(graph,color,marked,i) {
return false
}
}
}
return true
}
func dfs(graph map[int][]int, color []bool, marked []bool, root int) bool {
marked[root]=true
for _,w := range graph[root] {
if !marked[w] {
color[w]=!color[root]
if !dfs(graph,color,marked,w) {
return false
}
} else if color[w]==color[root] {
return false
}
}
return true
}
T421: 数组中两个数的最大异或值
- O(N)时间复杂度
- 首先我们将数组中每个数按照最大长度为 L = [ log 2 ( max { n u m s } ) ] + 1 L=[\log_2(\max \{nums\})]+1 L=[log2(max{nums})]+1左补零,从而得到所有数的相同长度的二进制表示
- 然后存入字典树Trie中,每个存入操作的时间复杂度为 O ( L ) ≈ O ( 1 ) O(L)\approx O(1) O(L)≈O(1)
- 针对每一个存入操作,同时计算其可以构成的最大异或值,根据DP的思想,最终得到全局最大值
- 具体计算时,从左到右每一位都检查Trie相应节点有无异或值(0/1),若有则相应增加complement并跳到其异或节点,无则使用原节点,不进行其他操作
import "math"
type Node struct {
next [2]*Node
}
type Trie struct {
root *Node
}
func (t *Trie) Insert(zero int, x []int) {
node:=t.root
for i:=0;i<zero+len(x);i++ {
if i<zero {
if node.next[0]==nil {
node.next[0]=&Node{}
}
node=node.next[0]
} else {
tmp:=x[len(x)-1-i+zero]
if node.next[tmp]==nil {
node.next[tmp]=&Node{}
}
node=node.next[tmp]
}
}
}
func findMaximumXOR(nums []int) int {
maxNum:=0
for i:=0;i<len(nums);i++ {
if maxNum<nums[i] {
maxNum=nums[i]
}
}
trie:=Trie{&Node{}}
L:=int(math.Trunc(math.Log2(float64(maxNum)))+1)
ans:=0
for i:=0;i<len(nums);i++ {
x:=nums[i]
tmp:=[]int{}
for x!=0 {
tmp=append(tmp, x%2)
x/=2
}
trie.Insert(L-len(tmp), tmp)
complement:=0
node:=trie.root
pow:=L-1
for j:=0;j<L-len(tmp);j++ {
if node.next[1]!=nil {
complement+=1<<pow
node=node.next[1]
} else {
node=node.next[0]
}
pow--
}
for j:=len(tmp)-1;j>=0;j-- {
c:=tmp[j]^1
if node.next[c]!=nil {
complement+=1<<pow
node=node.next[c]
} else {
node=node.next[tmp[j]]
}
pow--
}
if complement>ans {
ans=complement
}
}
return ans
}
剑指62: 循环删除剩下的最后一个数字
- 约瑟夫环
- 可以推广到对数组循环删除第m个(0,1,2,…,n-1理解成索引)
class Solution:
def lastRemaining(self, n: int, m: int) -> int:
k=1
index=0
while k<n:
index=(index+m)%(k+1)
k+=1
return index
T172: 阶乘后的零
- 首先一个数阶乘后面的零的个数只与该数阶乘后因数分解中2,5对的个数有关
- 而因为一个数的阶乘在一定范围后2因子的个数超过5因子的个数,所以一个数的阶乘后面零的个数与该数阶乘5因子的个数相等
- 一个数的阶乘可以表示为 5 × ( n / 5 ) ∗ 5 × ( n / 5 − 1 ) ∗ . . . ∗ 5 ∗ a 5 \times (n/5)*5\times(n/5-1)*...*5*a 5×(n/5)∗5×(n/5−1)∗...∗5∗a,其中a中无法分解出5因子
- 因此一个树阶乘的5因子个数为 n / 5 + ( n / 5 ) ! 中 包 含 的 5 因 子 个 数 n/5+(n/5)!中包含的5因子个数 n/5+(n/5)!中包含的5因子个数
- 递归解法
func trailingZeroes(n int) int {
if n<5 {
return 0
}
n/=5
return n+trailingZeroes(n)
}