目录
- 1. 题记
- 2. 题型分类
- 3. 题目列表
- 剑指offer:
- Offer 06.从头到尾打印链表
- Offer 08.双栈实现队列
- Offer 10-1斐波那契数列
- Offer10-2.青蛙跳台阶问题
- Offer 11.旋转数组的最小数字
- Offer 15.二进制中1的个数
- Offer 18.删除链表节点
- Offer 21. 调整数组顺序使奇数位于偶数前面
- Offer 22.链表中倒数第k个节点
- Offer 24.反转链表
- Offer 29. 顺时针打印矩阵
- Offer30.包含min函数的栈
- Offer32-II.从上到下打印二叉树II
- Offer39.数组中出现次数超过一半的数字
- Offer42.连续子数组的最大和
- Offer50.第一个只出现一次的字符
- Offer52.两个链表的第一个公共节点
- Offer53-Ⅰ
- Offer54.二叉搜索树的第k大节点
- Offer55-I.二叉树的深度
- Offer55-Ⅱ.平衡二叉树
- Offer57.和为s的两个数字
- Offer58-I.翻转单词顺序
- Offer58-Ⅱ.左旋转字符串
- Offer61.扑克牌中的顺子
- Offer62.圆圈中最后剩下的数字
- Offer65.不用加减乘除做加法
- 14. 最长公共前缀
- 220.存在重复元素III❓
- 1143.最长公共子序列
- 3.无重复字符的最长子串 ⭐⭐⭐
- 28.实现strStr()
- 面试题56-I.数组中数字出现的次数
- 137.只出现一次的数字Ⅱ
- 554.砖墙
- 7.整数反转
- 1473.粉刷房子Ⅲ
- 198.打家劫舍
- 740.删除并获得点数
- 1720.解码异或后的数组
- 1486.数组异或操作
- 1723.完成所有工作的最短时间❓
- 1482.制作m束花所需的最少天数
- 872.叶子相似的树
- 1734.解码异或后的排列
- 1310.子数组异或查询
- 1269.停在原地的方案数
- 12.整数转罗马数字
- 13.罗马数字转整数
- 208.实现Trie(前缀树)
- 421.数组中两个数的最大异或值
- 421.数组中两个数的最大异或值
- 993.二叉树的堂兄弟节点
- 1442.形成两个异或相等数组的三元组数目
- 692.前k个高频单词
- 1035.不相交的线
- 1707.与数组中元素的最大异或值
- 131.分割回文串
- 664.奇怪的打印机
- 1190.反转每队括号间的子串
- 477.汉明距离总和
- 234.2的幂
- 342.4的幂
- 523.连续的子数组和
- 525.连续数组
- 160.相交链表
- 474.一和零
- 494.目标和
- 879.盈利计划
- 518.零钱兑换
- 279.完全平方数
- 877.石子游戏
- 401.二进制手表
- 38.字符串的排序
- 149.直线上最多的点数
- 168.Excel表列名称
- LCP07.传递信息
- 1833.雪糕的最大数量
- 726.原子的数量
- 1711.大餐计数
- 930.和相同的二元子数组
- 981.基于时间的键值存储
- 274.H的指数
- 275.H指数Ⅱ
- 218.天际线问题⭐⭐
- 1818.绝对差值和
- 1931.用三种不同颜色为网格涂色⭐❓
- 面试题10.02.变位词组
- 1838.最高频元素的频数
- 138.复制带随机指针的链表
- 1738.找出第K大的异或坐标值
- 671.二叉树中第二小的节点
- 987.二叉树的垂序遍历
- 611.有效三角形个数
- 847.访问所有节点的最短路径
- 516.最长回归子序列
- 470.用Rand7()实现Rand10()
- 318.最大单词长度乘积
- python
1. 题记
本篇博客旨在便于作者本人学习记录,方便翻阅。故所引用之处皆已附上链接。能力有限,也望能给诸位带来些许方便。语言:python
。
2. 题型分类
滑动窗
递归分治
程序调用自身
回溯:
用递归实现的算法。递归调用之前「做选择」,在递归调用之后「撤销选择」。
剪枝:
搜索算法:暴力搜索 ⟹ \Longrightarrow ⟹ 剪枝+优化。参考在(☞゚ヮ゚)☞这里
动态规划DP⭐ (dynamic planning)
最常见
重叠子问题、最优子结构、状态转移方程:动态规划三要素
- 找状态转移方程:eg. f ( n ) = f ( n − 2 ) + a f(n) = f(n-2)+a f(n)=f(n−2)+a
- 自底向上计算,重用子问题的解:使用dp表或者备忘录,将解存起来复用。
dp表从一维到三维不等。
背包问题:
01背包:总容量有限,如何装使得背包价值最大。物体:(体积、价值)。
dp[i][capacity] = value
树的算法
1. 广度优先搜索BFS
迭代法:队列
def BFS(root):
queue = [root]
while queue:
p = queue.pop(0)
if p.left:
queue.append(p.left)
if p.right:
queue.append(p.right)
2. 深度优先搜索DFS
迭代法:栈
def DFS(root):
p = root
stack = [p]
while p.left:
p = p.left
stack.append(p) # 左子树入栈
while stack:
# 栈顶元素出栈,将其右节点入栈,并重复将该将右节点的所有左节点入栈
p = stack.pop()
if p.right:
p = p.right
stack.append(p) # 栈顶元素右节点入栈
while p.left: # 并将该节点所有左子树节点入栈
p = p.left
stack.append(p)
return leaf
递归法:重复自身(一般递归效率低)
def DFS(root):
if root is None:
return
# root.val # 前序
DFS(root.left)
# root.val # 中序
DFS(root.right)
# root.val # 后序
3. 二叉搜索树BST
每个节点的值大于其左侧子节点的值,小于右节点的值。
4.前缀树(字典树)Trie
图
字符串匹配KMP
位运算
- 面试题56-I.数组中数字出现的次数
- 137.只出现一次的数字Ⅱ
- 1720.解码异或后的数组
- 1486.数组异或操作
- 1734.解码异或后的排列
- 1310.子数组异或查询
- 421.数组中两个数的最大异或值
- 1442. 形成两个异或相等数组的三元组数目
- 477.汉明距离总和
- 234. 2的幂; 342. 4的幂
bin()
转二进制
0x
:16进制,0b
:2进制
python是无?符号二进制
& | | | ^ | ~ | << | >> |
---|---|---|---|---|---|
与 | 或 | 异或XOR ‘⊕’ | 取反 | 左移 | 右移 |
1&1为1,其余为0 | 0|0为0,其余为1 | 相同为0相异为1 |
⭐异或运算规律:转自☞这里
- x ⊕ x = 0 x \oplus x = 0 x⊕x=0;
- x ⊕ y = y ⊕ x x \oplus y = y \oplus x x⊕y=y⊕x(交换律);
- ( x ⊕ y ) ⊕ z = x ⊕ ( y ⊕ z ) (x \oplus y) \oplus z = x \oplus (y \oplus z) (x⊕y)⊕z=x⊕(y⊕z)(结合律);
- x ⊕ y ⊕ y = x x \oplus y \oplus y = x x⊕y⊕y=x(自反性);
- ∀ i ∈ Z \forall i \in Z ∀i∈Z,有 4 i ⊕ ( 4 i + 1 ) ⊕ ( 4 i + 2 ) ⊕ ( 4 i + 3 ) = 0 4i \oplus (4i+1) \oplus (4i+2) \oplus (4i+3) = 0 4i⊕(4i+1)⊕(4i+2)⊕(4i+3)=0。
二分法
1482.制作 m 束花所需的最少天数
981.基于时间的键值存储
找最优值
def binaryFind(data, target):
left, right = min, max (都能取到)
mid = (left + right) // 2
while left <= right:
if is_satisfy(mid,data):
return data[mid]
elif over_satisfy(mid,data):
right = mid - 1
else:
left = mid + 1
return left or right
运用到查找最优值时:第一个不小于目标值的数,或者查找最后一个小于目标值的数。
while left <= right:
最后:left:大于等于目标值的最小值,right:小于等于目标值的最大值。
变形:❓
状态压缩
e.g. 用n位二进制表示n位0-1状态。
取第i位状态:(num >> i) & 1
改第i位状态为1
:num = num | (1 << i)
s
3. 题目列表
剑指offer:
Offer 06.从头到尾打印链表
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
# 从尾到头反过来返回每个节点的值
# 方法1:list存储节点值,最后list翻转
result = []
p = head
while p:
result.append(p.val)
p = p.next
result.reverse() # result[::-1]
return result
# 如果需要逆读取链表
# 方法2:增加额外空间:栈,来存储节点
# 方法3:递归
def visit(p):
if not p:
return []
return visit(p.next)+[p.val]
return visit(head)
Offer 08.双栈实现队列
栈:先进后出
⟶
\longrightarrow
⟶队列:先进先出。
负责进:栈1,负责出:栈2。
直接进栈1,直接出栈2,栈2若为空则将栈1 pop压入栈2。
class CQueue:
def __init__(self):
self.stack_in = []
self.stack_out = []
def appendTail(self, value: int) -> None:
# 队列尾部插入整数
self.stack_in.append(value)
def deleteHead(self) -> int:
# 队列头部删除整数,没有元素则返回 -1
if not self.stack_in and not self.stack_out:
return -1
if not self.stack_out:
while self.stack_in:
self.stack_out.append(self.stack_in.pop())
return self.stack_out.pop()
Offer 10-1斐波那契数列
动态规划DP:F[n]=F[n-1]+F[n-2]
节约空间只使用a、b来存储数据:b = b+a; a = b-a
class Solution:
def fib(self, n: int) -> int:
# DP
if n==0:
return 0
MOD = 10**9+7
a = 0
b = 1
for _ in range(1,n):
a,b = b,(a+b) % MOD
return b
Offer10-2.青蛙跳台阶问题
- 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
- 动态规划DP: 10-1斐波那契数列
class Solution:
def numWays(self, n: int) -> int:
MOD = 10**9 + 7
a = 0
b = 1
for _ in range(n):
a,b = b,(a+b) % MOD
return b
Offer 11.旋转数组的最小数字
- 旋转有重复递增数组
- numbers[mid] > numbers[right] → \rightarrow → left = mid+1
- numbers[mid] < numbers[right] → \rightarrow → right = mid
- right -= 1
class Solution:
def minArray(self, numbers: List[int]) -> int:
left = 0
right = len(numbers)-1
i = 1
# Ns+1 < Nright < Nleft < Ns
while left<right:
mid = (left+right)//2
if numbers[mid] > numbers[right]:
left = mid+1
elif numbers[mid] < numbers[right]:
right = mid
else:
right -= 1
return numbers[left]
Offer 15.二进制中1的个数
- 内置函数,
count
计算1个数
bin(n).count('1')
- 按位遍历
while n:
count += n & 1
n = n>>1
- n = n&(n-1), 迭代的次数为1的个数
while n:
count += 1
n &= n-1
Offer 18.删除链表节点
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteNode(self, head: ListNode, val: int) -> ListNode:
# 单向链表
p = head
if head.val == val: return head.next
while p and p.val != val:
bef,p = p,p.next # 双指针
if p:
bef.next = p.next
return head
Offer 21. 调整数组顺序使奇数位于偶数前面
首尾双指针
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
# 快速排序,前奇后偶
i, j = 0, len(nums)-1
while i < j:
while i < j and nums[i]%2 != 0:
i += 1
while i < j and nums[j]%2 == 0:
j -= 1
if i < j:
nums[i],nums[j] = nums[j],nums[i]
return nums
Offer 22.链表中倒数第k个节点
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
# 输出该链表中倒数第k个节点
# 解法1:list 存储空间增加
# 解法2:先求链表长度n,再走n-k步
save = []
p = head
while p:
save.append(p)
p = p.next
return save[-k]
Offer 24.反转链表
三指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
p2 = head
if head:
p3 = head.next
head.next = None
while p3:
p1,p2,p3 = p2,p3,p3.next
p2.next = p1
return p2
Offer 29. 顺时针打印矩阵
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
# 从外向里以顺时针的顺序依次打印出矩阵每一个数字
m = len(matrix)
if m:
n = len(matrix[0])
else:
return matrix
result = []
for i in range(min(m//2, n//2)):
result += matrix[i][i:n-i] # →
for j in range(i+1,m-i): # ↓
result.append(matrix[j][n-i-1])
result += matrix[m-i-1][n-i-2:i:-1] # ←
for j in range(m-i-1,i,-1): # ↑
result.append(matrix[j][i])
# 剩余情况
i = min(m//2, n//2)
if m - 2*i != 0:
result += matrix[i][i:n-i]
if n - 2*i != 0:
for j in range(i+1,m-i):
result.append(matrix[j][n-i-1])
return result
Offer30.包含min函数的栈
- 题目:实现栈,并且要在O(1)时间取栈最小值
添加辅助栈(非严格单调递减):
- 入栈:当入栈元素比辅助栈顶元素小或者辅助栈为空,入辅助栈。
- 出栈:当出栈元素等于辅助栈顶元素,辅助栈顶出栈。
- 最小值:辅助站顶元素
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.data = []
self.assist = [] # 非严格递减
def push(self, x: int) -> None:
self.data.append(x)
if not self.assist or self.assist[-1]>=x:
self.assist.append(x)
def pop(self) -> None:
if self.data:
if self.assist[-1]==self.data[-1]:
self.assist.pop()
self.data.pop()
def top(self) -> int:
if self.data:
return self.data[-1]
def min(self) -> int: # O(1)时间
# return min(self.data)
return self.assist[-1]
# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(x)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.min()
Offer32-II.从上到下打印二叉树II
BFS
Offer39.数组中出现次数超过一半的数字
哈希字典
class Solution:
def majorityElement(self, nums: List[int]) -> int:
# 找出数组中出现的次数超过数组长度的一半的这个数字
n = len(nums)
count = {}
for item in nums:
count[item] = count.get(item,0)+1
if count[item] > n//2:
return item
Offer42.连续子数组的最大和
暴力:两层for循环
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# 方法1:暴力(超时)
n = len(nums)
F = [0]*(n+1)
F[0] = nums[0]
for i in range(1, n):
F[i] = F[i-1] + nums[i]
result = -101
for i in range(0,n):
for j in range(-1,i):
result = max(result, F[i]-F[j])
return result
DP: (位置i)
- 如果
dp[i-1]<=0
,则dp[i]=nums[i]
; - 若
dp[i-1]>0
,则dp[i] = dp[i-1]+nums[i]
。 - 返回
dp
最大值。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# 时间复杂度为O(n)
# 方法2:DP
n = len(nums)
dp = [0]*n
dp[0] = nums[0]
for i in range(1,n):
dp[i] = max(dp[i-1]+nums[i], nums[i])
# 若当前数加上当前累积子数组和 < 当前数,则从当前数为子数组开端
return max(dp) # 返回所有子数组和最大值
Offer50.第一个只出现一次的字符
class Solution:
def firstUniqChar(self, s: str) -> str:
# 第一个只出现一次的字符
save = {}
for item in s:
save[item] = save.get(item, 0) + 1
for key, val in save.items():
if val == 1:
return key
return ' '
Offer52.两个链表的第一个公共节点
俩列表存储俩链表,从尾到头判断相等。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
if not headA or not headB:
return None
list1 = [headA]
p = headA
while p.next:
p = p.next
list1.append(p)
list2 = [headB]
p = headB
while p.next:
p = p.next
list2.append(p)
i = -1
l1,l2 = len(list1),len(list2)
while i>=-min(l1,l2) and list1[i] == list2[i]:
i -= 1
if i == -1:
return None
return list1[i+1]
Offer53-Ⅰ
class Solution:
def search(self, nums: List[int], target: int) -> int:
if not nums:
return 0
# 方法一:二分查找
n = len(nums)
left, right = 0, n
while left < right:
mid = (left + right) // 2
if nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid - 1
else:
break
count = 0
mid = (left + right) // 2
i = mid
while i<n and nums[i] == target:
count += 1
i += 1
i = mid - 1
while i>=0 and nums[i] == target:
count += 1
i -= 1
return count
# 2:顺序查找
i = 0
count = 0
while i<len(nums) and nums[i] <= target:
if nums[i] == target:
count += 1
i += 1
return count
Offer54.二叉搜索树的第k大节点
class Solution:
def kthLargest(self, root: TreeNode, k: int) -> int:
# 右根左, count累加计数
stack = []
p = root
while p:
stack.append(p)
p = p.right
count = 0
while stack:
p = stack.pop()
count += 1
if count == k:
return p.val
if p.left:
p = p.left
while p:
stack.append(p)
p = p.right
Offer55-I.二叉树的深度
BFS
class Solution:
def maxDepth(self, root: TreeNode) -> int:
# BFS
if not root:
return 0
stack = [root]
count = 0
while stack:
for i in range(len(stack)):
p = stack.pop(0)
if p.left:
stack.append(p.left)
if p.right:
stack.append(p.right)
count += 1
return count
Offer55-Ⅱ.平衡二叉树
递归
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
# 某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树
def DFS(root):
if not root:
return 0, True
l, l1 = DFS(root.left)
r, l2 = DFS(root.right)
label = l1 & l2
if not -1<=l-r<=1:
label = False
return max(l,r)+1, label
_, label = DFS(root)
return label
Offer57.和为s的两个数字
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 递增排序的数组
save = dict(zip(nums, [0]*len(nums))) # list转字典,实现O(1)的查找
for i,_ in save.items():
if target-i in save:
return [i,target-i]
Offer58-I.翻转单词顺序
class Solution:
def reverseWords(self, s: str) -> str:
i = 0
result = ''
while i < len(s):
temp = ''
while i < len(s) and s[i] != ' ':
temp += s[i]
i += 1
if temp:
result = temp + ' ' + result
i += 1
return result[:-1]
Offer58-Ⅱ.左旋转字符串
class Solution:
def reverseLeftWords(self, s: str, n: int) -> str:
# return s[n:]+s[:n]
new = list(s)
l = len(s)
for i in range(l-n):
new[i] = s[i+n]
for i in range(n):
new[l-n+i] = s[i]
return ''.join(new)
Offer61.扑克牌中的顺子
class Solution:
def isStraight(self, nums: List[int]) -> bool:
# 判断是不是一个顺子, 0 可以看成任意数字
nums.sort()
n = len(nums)
i = 0
count = 0
while i < n and nums[i] == 0:
count += 1
i += 1
while i < n-1:
temp = nums[i+1]-nums[i]-1
if temp == -1:
return False
elif temp != 0:
if count < temp:
return False
count -= temp
i += 1
return True
Offer62.圆圈中最后剩下的数字
class Solution:
def lastRemaining(self, n: int, m: int) -> int:
# 逆推,数字个数从2~n
count = 0
for i in range(2, n+1):
count = (count + m) % i
return count
# 直接列表,超时
data = list(range(n))
i = 0
while n > 1:
i = (i + m - 1) % n
n -= 1
del(data[i])
return data[0]
Offer65.不用加减乘除做加法
二进制加法(补码)
class Solution:
def add(self, a: int, b: int) -> int:
# 在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号
# 位运算:^当前位, &进位
x = 0xffffffff # 32位1
print(a, b)
a, b = a & x, b & x # 补码,正数补码不变,负数补码取反+1
print(bin(a), b)
while b:
a, b = a ^ b, (a & b) << 1 & x
print(bin(0x7fffffff))
return a if a <= 0x7fffffff else ~(a ^ x) # 31位1,若>31位1 ~(a ^ x)
14. 最长公共前缀
暴力解法:横向扫描
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
if not strs: # strs空的情况
return ""
temp = strs[0]
# 双重循环
for i in range(1, len(strs)):
temp = temp[:min(len(temp), len(strs[i]))]
for j in range(len(temp)):
if temp[j] != strs[i][j]:
temp = temp[:j]
break
return temp
220.存在重复元素III❓
- 题目描述]:整数数组 nums 和两个整数 k 和 t 。判断是否存在两个不同下标 i 和 j,使得 abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k 。存在返回 true,不存在返回 false。
滑动窗+有序数组(O(1)的查、增、删)
C++中 set/multiset/map,Java中TreeSet、TreeMap,Python中sortedcontainers(转自☞这里 )。
1143.最长公共子序列
- 题目描述:俩字符串text1和text2,返回最长公共子序列的长度。
- 子序列:由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
动态规划
t
e
x
t
1
[
:
i
+
1
]
text1[:i+1]
text1[:i+1]和
t
e
x
t
2
[
:
j
+
1
]
text2[:j+1]
text2[:j+1]的最长公共子序列长度为
d
p
[
i
+
1
]
[
j
+
1
]
dp[i+1][j+1]
dp[i+1][j+1],状态转移方程:
if text1[:i+1]==text2[:j+1]:
dp[i+1][j+1] = dp[i][j]+1
else:
dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j])
3.无重复字符的最长子串 ⭐⭐⭐
注:面试笔试出现概率高
同:剑指 Offer 48. 最长不含重复字符的子字符串
- 题目描述:找出字符串中不含有重复字符的最长子串的长度。
暴力搜索:
滑动窗口:❓
28.实现strStr()
- 题目描述:
字符串匹配KMP
前缀表(用来记录匹配失败后该跳回的位置)
子串中当前位置前缀表值=之前(包括当前位置)的字符串,前缀后缀最大重合数。eg. “ababa”->“aba” 3
匹配失败后,子串指向前一位的前缀值的位置。
推荐视频
面试题56-I.数组中数字出现的次数
- 题目描述:一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
思路:每次对当前列表中未查找的第一个元素遍历列表,若有与该元素相同的元素存在则删除这俩元素,若无相同则保留元素。
python
class Solution(object):
def singleNumbers(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
i = 0
while len(nums) > i:
flag = 0
for j in range(i+1, len(nums)):
if nums[i] == nums[j]:
del nums[j]
del nums[i]
flag = 1
break
if flag == 0:
i += 1
print(i)
return nums
![](https://i-blog.csdnimg.cn/blog_migrate/72da5f63ea728359cc162e6e6cb69926.png)
存在的问题:执行用时过长,时间复杂度没有达到O(n)
官方使用的是分组异或
异或 ^
操作:
- 任何数和0异或后不变
- 相同为0,不同为1
e.g. 若找出数组中唯一一个只出现一次的数,则只需对所有元素进行一次异或操作:
a1 ^ a2 ^ a3 ^ a4 ^ a5… ^ an
两两相同的元素异或之后得0,只剩下出现一次那个数
现在题目是找出两个只出现一次的数,分组异或方法❓
如何分组?先到这里。
137.只出现一次的数字Ⅱ
- 题目描述:只有一个数只出现一次其余都出现三次。
- 相似题目:面试题56-l
位运算
思路转载自这里
将十进制数转换成32位二进制数,各个位数十进制相加,结果不能被3整除的位置为1,能整除为0,得到只出现一次的元素的二进制数。
x二进制数的第i位:(x >> i) & 1
,>>
右移运算符,&
按位与运算符。
相加后若当前位不为3的倍数,则1<<i
左移i位做|
或运算。python没有符号位,i==31最高位时,为-2^31。
状态机❓
二进制表示出现三次(00,01,10)。
拓展:
- 只有一个数只出现一次其余都出现两次:异或(a^ b^ a=b)。
- 只有一个数只出现一次其余都出现奇数次。
554.砖墙
- 题目描述:由 n 行砖块组成的砖墙。这些砖块高度相同(也就是一个单位高)但是宽度不同。每一行砖块的宽度之和应该相等。二维数组 wall,wall[i] 是一个代表从左至右每块砖的宽度的数组。画一条 自顶向下 的、穿过 最少 砖块的垂线,返回穿过砖块的数量。
思路:
- i 每次跳跃最小一块砖长度(i = 当前0列最小的数)
- 计算第 i 列需要穿过的砖块数量:若某行第一块砖长度==i,则不需要穿过。若某行第一块砖长度>=i,则合并第一第二块砖长。
- 重复1.2步骤,直到 i 到达砖墙尾部。
- 所有列中最小的穿过砖块的数量即为所得。
class Solution:
def leastBricks(self, wall: List[List[int]]) -> int:
slice_0 = [i[0] for i in wall] # 0列切片
i = min(slice_0)
min_cross = len(wall)
while i !=wall[0][0] or len(wall[0])!=1: # i==wall[0][0] and len(wall)==1结束
count = 0 # 第i列需要穿过的砖块数量
for j in range(len(wall)): # 计算j行
if wall[j][0]>i:
count+=1
elif wall[j][0]==i:
# count 不变
# 合并 位置0和1
wall[j][0] += wall[j][1]
wall[j].pop(1)
# 更新切片
slice_0[j] = wall[j][0]
elif wall[j][0]<i:
count+=1
# 合并
wall[j][0] += wall[j][1]
wall[j].pop(1)
# 更新切片
slice_0[j] = wall[j][0]
i = min(slice_0)
min_cross = min(min_cross, count)
return min_cross
题解提供思路:哈希❓
7.整数反转
- 题目描述:返回将32位有符号整数
x
数字部分反转后的结果。 − 2 31 ≤ x ≤ 2 31 − 1 -2^{31} \le x \le 2^{31} - 1 −231≤x≤231−1
class Solution:
def reverse(self, x: int) -> int:
# 数字反转
# 32位有符号整数: -2^31 <= x <= 2^31 - 1
border = 2**(31)
if x<0:
multi_base = -1
x = -x
else:
multi_base = 1
count = 0
for i in str(x):
count += multi_base*int(i)
multi_base *= 10
if count > border-1 or count< -border: # 溢出判断
count = 0
return count
若不使用str(x)
和 int(i)
强制转换,数学方式:
c
o
u
n
t
=
c
o
u
n
t
∗
10
+
x
%
10
x
=
x
/
/
10
count = count*10 + x\%10\\ x = x//10
count=count∗10+x%10x=x//10
由于题目里面说明:假设环境不允许存储 64 位整数(有符号或无符号)
所以将溢出判断加入到while
循环里:
while x!=0:
if count > border-1 or count< -border: # 溢出判断
return 0 # return count = 0
count = count*10 + x%10
x = x//10
count *= multi_base
1473.粉刷房子Ⅲ
- 题目描述:m 个房子 n 种颜色(1~n,0为未涂色),houses[i]: 第i个房子的颜色;cost[i][j]: 第 i 个房子涂成 j+1 花费。连续相同颜色尽可能多的房子称为一个街区。找最小花费的涂色方案,使得所有房子都被涂色后组成target个街区。
- houses[i], cost[i][j], m, n, target
动态规划,参考☞这里
class Solution:
def minCost(self, houses: List[int], cost: List[List[int]], m: int, n: int, target: int) -> int:
# f[i][j][k] 第i间房子,颜色j,分区数k
INF = float("inf")
# 初始化
f = [[[0]*(target+1) for j in range(n)] for i in range(m+1)]
# i =0 k!=0时INF
for j in range(n):
for k in range(target+1):
if k != 0:
f[0][j][k] = INF
for i in range(1,m+1):
if houses[i-1]==0: # 未上色
# f[i][j][k] = min(f[i−1][j][k],f[i−1][p][k−1])+cost[i][j]
for j in range(n):
f[i][j][0] = INF ##
for k in range(1,target+1):
f[i][j][k] = f[i-1][j][k]
for p in range(n):
if p != j:
f[i][j][k] = min(f[i][j][k], f[i-1][p][k-1])
f[i][j][k] += cost[i-1][j]
else: # 已上色
# f[i][houses[i]][k] = min(f[i-1][j][k], f[i-1][p][k-1])
for j in range(n):
f[i][j][0] = INF
for k in range(1,target+1):
if j+1==houses[i-1]:
f[i][j][k] = f[i-1][j][k]
for p in range(n):
if p!=j:
f[i][j][k] = min(f[i][j][k], f[i-1][p][k-1])
else:
f[i][j][k] = INF
# m屋子都涂了且有target个街区的数据里面找最小值
min_data = f[m][0][target]
for p in range(1,n):
min_data = min(min_data, f[m][p][target])
if min_data==INF:
return -1
else:
return min_data
i,j,k之间从0开始还是从1开始debug好久。叹气~
最初
i
=
0
→
f
[
0
]
[
]
[
k
!
=
0
]
=
I
N
F
,
f
[
0
]
[
]
[
0
]
=
0
i=0\rightarrow f[0][ ][k!=0] = INF, f[0][ ][0] = 0
i=0→f[0][][k!=0]=INF,f[0][][0]=0;
后面
i
!
=
0
→
f
[
i
]
[
]
[
0
]
=
I
N
F
i!=0\rightarrow f[i][ ][0]=INF
i!=0→f[i][][0]=INF
198.打家劫舍
- 题目描述:在同一晚上不进入两间相邻的房屋,求一夜之内能够偷窃到的最高金额。
动态规划
d
p
[
i
]
=
m
a
x
(
d
p
[
i
−
2
]
+
n
u
m
s
[
i
]
,
d
p
[
i
−
1
]
)
dp[i] = max(dp[i-2]+nums[i], dp[i-1])
dp[i]=max(dp[i−2]+nums[i],dp[i−1])
class Solution:
def rob(self, nums: List[int]) -> int:
"""
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
dp[i] = max(dp[i-2]+nums[i],dp[i-1])
"""
dp = [0]*len(nums)
dp[0] = nums[0]
if len(nums)>1:
dp[1] = max(nums[:2])
for i in range(2,len(nums)):
dp[i] = max(dp[i-2]+nums[i], dp[i-1])
return dp[len(nums)-1]
740.删除并获得点数
- 题目描述:每次选择任意一个nums[i],删除它并获得它的点数。并删除每个等于nums[i]- 1或nums[i] + 1的元素。求能获得的最大点数。
动态规划,相似题198.打家劫舍
d
p
[
i
]
=
m
a
x
(
d
p
[
i
−
2
]
+
i
∗
b
u
c
k
e
t
[
i
]
,
d
p
[
i
−
1
]
)
dp[i] = max(dp[i-2]+i*bucket[i], dp[i-1])
dp[i]=max(dp[i−2]+i∗bucket[i],dp[i−1]),bucket[i]为
i
i
i出现的次数。
class Solution:
def deleteAndEarn(self, nums: List[int]) -> int:
MAX = max(nums)
bucket = [0]*(MAX+1) # 存储每个数字出现的次数
dp = []
for i in nums:
bucket[i]+=1
i = 1
while i<=MAX and bucket[i]==0:
i += 1
dp.append(i*bucket[i]) # dp[0]
if i == MAX:
return dp[0]
i += 1
# dp[1]
if bucket[i]!=0:
dp.append(max(dp[0],i*bucket[i]))
else:
while i<=MAX and bucket[i]==0:
i += 1
dp.append(max(dp[0],dp[0]+i*bucket[i]))
i += 1
while i<=MAX:
dp.append(max(dp.pop(0)+i*bucket[i], dp[0]))
i += 1
return dp[1]
1720.解码异或后的数组
- 题目描述:非负整数数组arr[ : n]通过编码变成encoded[ : n-1]整数数组,encoded[ i ] = arr[ i ] XOR arr[ i+1 ]。已知encoded和arr[0]求arr?
a
⊕
b
=
c
→
a
⊕
c
=
b
→
b
⊕
c
=
a
a \oplus b=c \rightarrow a\oplus c=b \rightarrow b\oplus c=a
a⊕b=c→a⊕c=b→b⊕c=a
所以:
a
r
r
[
i
+
1
]
=
e
n
c
o
d
e
d
[
i
]
⊕
a
r
r
[
i
]
arr[ i+1 ] = encoded[ i ] \oplus arr[ i ]
arr[i+1]=encoded[i]⊕arr[i]
class Solution:
def decode(self, encoded: List[int], first: int) -> List[int]:
a = [first]
for i in range(len(encoded)):
a.append(encoded[i]^a[i])
return a
1486.数组异或操作
- 题目描述:nums[i] = start + 2i,nums长度0~n,返回nums所有元素按位异或后结果。
计算结果的第i位:
- 将nums数组中第i位提取出来
nums[j] >> i & 1
做异或^
运算。 - 将异或结果加入最后的结果中
temp<<i+result
for j in range(n):
temp ^= nums[j] >> i & 1
result += temp<<i
class Solution:
def xorOperation(self, n: int, start: int) -> int:
nums = [0]*n
for i in range(n):
nums[i] = start+2*i
i = 0
result = 0
while nums[n-1]>>i!=0:
temp = 0
for j in range(n):
temp ^= nums[j] >> i & 1
result += temp<<i
i+=1
return result
看了题解,想复杂了,直接异或即可:
class Solution:
def xorOperation(self, n: int, start: int) -> int:
# 直接异或
result = 0
for i in range(n):
result ^= start+2*i
return result
方法二:
数学思维,☞这里。(start)^(start+2)^(start+4)^(start+6)^...^(start+2(n-1))
变为:(s ^ (s+1) ^ (s+2) ^ … ^ (s+n-1)) * 2 + e
其中
s
=
⌊
start
2
⌋
s=\lfloor \frac{\textit{start}}{2} \rfloor
s=⌊2start⌋,
∗
2
*2
∗2表示左移一位,所以
e
e
e 表示运算结果的最低位。
1723.完成所有工作的最短时间❓
- 题目描述:jobs[i]完成第i项工作花费的时间。分给k位工人,每项工作一位工人。求工人最大工作时间(总和)的最小值。
背包问题?图论? ⟹ \Longrightarrow ⟹工作量要分的平均
1. 动态规划+状态压缩
来自☞这里。
状态压缩:用n位二进制整数表示n个工作的分配情况(实际只需要一个变量)。
状态转移方程:
f
[
i
]
[
j
]
=
m
i
n
j
′
∈
j
{
m
a
x
(
f
[
i
−
1
]
[
C
j
j
′
]
,
s
u
m
[
j
′
]
)
}
f[i][j] = min_{j' \in j}\{max(f[i-1][Cjj'], sum[j'])\}
f[i][j]=minj′∈j{max(f[i−1][Cjj′],sum[j′])}。
f
[
i
]
[
j
]
f[i][j]
f[i][j]:给前
i
i
i个人分配工作,分配情况为
j
j
j时完成所有工作的最短时间。
题解python直译竟然还超时了,桑心😟
import math
class Solution:
def minimumTimeRequired(self, jobs: List[int], k: int) -> int:
# 题解思路:动态规划+状态压缩
MAX = float('inf')
n = len(jobs)
sums = [0]*(1 << n)
dp = [[0]*(1 << n) for i in range(k)]
for i in range(1,1<<n):
x = int(math.log(i&(-i),2)) # 2^x = i&(-i) 二进制尾0个数
y = i-(1<<x)
sums[i] = sums[y] + jobs[x]
dp[0] = sums
for i in range(1,k):
for j in range(1<<n):
minn = MAX
x = j
while x!=0:
minn = min(minn, max(dp[i-1][j-x],sums[x]))
x = (x-1)&j
dp[i][j] = minn
return dp[k-1][(1<<n)-1]
1482.制作m束花所需的最少天数
- 题目描述:数组 bloomDay存储第 i朵花会在 bloomDay[i]盛开。需要 m束花,每束花需要相邻的 k朵花。问需要等的最少天数。
二分法:需要等待的最少天数
m
i
n
(
b
l
o
o
m
D
a
y
)
<
d
a
y
<
m
a
x
(
b
l
o
o
m
D
a
y
)
min(bloomDay)<day<max(bloomDay)
min(bloomDay)<day<max(bloomDay)
思路来自(☞゚ヮ゚)☞这里,讲解清晰易懂。
class Solution:
def minDays(self, bloomDay: List[int], m: int, k: int) -> int:
# m束花,每束使用相邻k朵花,day>bloomDay[i]才可以摘取
if m*k>len(bloomDay):
return -1
left = min(bloomDay)
right = max(bloomDay) # [left~mid~right)
while left<right:
mid = (left+right)//2
if is_satisfy(bloomDay,m,k,mid):
right = mid # [left,mid)
else:
left = mid+1 # [mid+1,right)
return left
def is_satisfy(bloomDay,m,k,mid):
count_m = 0
i = 0
while i < len(bloomDay):
while i < len(bloomDay) and bloomDay[i] > mid:
i+=1
count_k = 0
while i < len(bloomDay) and bloomDay[i] <= mid: # 连续花
count_k+=1
i+=1
if count_k==k: # 取了k朵重新开始计数
count_m+=1
if count_m == m:
return True
break
return False
二分法区间边界如何界定??
872.叶子相似的树
DFS
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def leafSimilar(self, root1: TreeNode, root2: TreeNode) -> bool:
# DFS
if DFS(root1)==DFS(root2):
return True
else:
return False
def DFS(root):
leaf = [] # 叶值序列
p = root
stack = [p]
while p.left:
p = p.left
stack.append(p) # 左子树入栈
while stack:
p = stack.pop()
if p.right:
p = p.right
stack.append(p) # 栈顶元素右节点入栈
while p.left: # 并将该节点所有左子树节点入栈
p = p.left
stack.append(p)
elif not p.left:
leaf.append(p.val) # 叶节点
return leaf
1734.解码异或后的排列
- 题目描述:与1720.解码异或后的数组相似: e n c o d e d [ i ] = p e r m [ i ] ⊕ p e r m [ i + 1 ] encoded[i] = perm[i] \oplus perm[i + 1] encoded[i]=perm[i]⊕perm[i+1]。但不给perm[0],已知encoded为1~n(n=len(perm))整数排列,n为奇数,求perm.
e
n
c
o
d
e
d
[
0
]
=
p
e
r
m
[
0
]
⊕
p
e
r
m
[
1
]
encoded[0] = perm[0] \oplus perm[1]
encoded[0]=perm[0]⊕perm[1]
e
n
c
o
d
e
d
[
1
]
=
p
e
r
m
[
1
]
⊕
p
e
r
m
[
2
]
encoded[1] = perm[1] \oplus perm[2]
encoded[1]=perm[1]⊕perm[2]
e
n
c
o
d
e
d
[
2
]
=
p
e
r
m
[
2
]
⊕
p
e
r
m
[
3
]
encoded[2] = perm[2] \oplus perm[3]
encoded[2]=perm[2]⊕perm[3]
e
n
c
o
d
e
d
[
3
]
=
p
e
r
m
[
3
]
⊕
p
e
r
m
[
4
]
encoded[3] = perm[3] \oplus perm[4]
encoded[3]=perm[3]⊕perm[4]
…
\ldots
…
e
n
c
o
d
e
d
[
1
]
⊕
e
n
c
o
d
e
d
[
3
]
⊕
e
n
c
o
d
e
d
[
5
]
.
.
.
⊕
e
n
c
o
d
e
d
[
n
−
2
]
=
p
e
r
m
[
1
]
⊕
p
e
r
m
[
2
]
…
p
e
r
m
[
n
−
1
]
encoded[1]\oplus encoded[3]\oplus encoded[5]...\oplus encoded[n-2] = perm[1]\oplus perm[2]\ldots perm[n-1]
encoded[1]⊕encoded[3]⊕encoded[5]...⊕encoded[n−2]=perm[1]⊕perm[2]…perm[n−1],由此可得
p
e
r
m
[
0
]
perm[0]
perm[0].
class Solution:
def decode(self, encoded: List[int]) -> List[int]:
# n 是奇数
n = len(encoded)+1
perm = [0]
for i in range(n//4*4,n+1):
perm[0] ^= i # 1^2^3...^n
for i in range(0,n//2):
perm[0] ^= encoded[2*i+1]
for j in range(n-1):
perm.append(perm[j]^encoded[j])
return perm
1310.子数组异或查询
F
[
i
]
=
a
r
r
[
0
]
⊕
a
r
r
[
1
]
⊕
a
r
r
[
2
]
.
.
.
⊕
a
r
r
[
i
]
F[i]=arr[0]\oplus arr[1]\oplus arr[2] ... \oplus arr[i]
F[i]=arr[0]⊕arr[1]⊕arr[2]...⊕arr[i]
X
O
R
[
L
,
R
]
=
a
r
r
[
L
]
⊕
a
r
r
[
L
+
1
]
.
.
.
⊕
a
r
r
[
R
]
=
F
[
L
−
1
]
⊕
F
[
R
]
XOR[L,R] = arr[L]\oplus arr[L+1] ... \oplus arr[R]=F[L-1]\oplus F[R]
XOR[L,R]=arr[L]⊕arr[L+1]...⊕arr[R]=F[L−1]⊕F[R]
import numpy as np
class Solution:
def xorQueries(self, arr: List[int], queries: List[List[int]]) -> List[int]:
# 查询数组queries[i] = [Li, Ri]
result = []
n = len(arr)
F = [0]*(n+1) # 只需要一维
F[0] = arr[0]
for j in range(1,n):
F[j] = F[j-1]^arr[j]
for L,R in queries:
result.append(F[L-1]^F[R])
return result
1269.停在原地的方案数
动态规划
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
−
1
]
+
d
p
[
i
−
1
]
[
j
]
+
d
p
[
i
−
1
]
[
j
+
1
]
dp[i][j]=dp[i−1][j−1]+dp[i−1][j]+dp[i−1][j+1]
dp[i][j]=dp[i−1][j−1]+dp[i−1][j]+dp[i−1][j+1]
s
t
e
p
s
=
i
,
p
r
e
s
e
n
t
=
j
steps = i, present = j
steps=i,present=j
i
>
j
i>j
i>j不考虑为0,
i
=
j
i=j
i=j为1。
import numpy as np
class Solution:
def numWays(self, steps: int, arrLen: int) -> int:
# 恰好执行 steps 次操作以后,指针仍然指向索引 0 处的方案数
MODULO = 10**9 + 7
MIN = min(arrLen,steps) # 不加这句空间会爆
dp = [[0]*MIN for _ in range(steps)]
dp[0][0] = 1
for i in range(1,steps):
for j in range(0,MIN):
dp[i][j]=dp[i-1][j] % MODULO # 取模,不然位数会超(溢出)
if j>=1:
dp[i][j]+=dp[i-1][j-1] % MODULO
if j<MIN-1:
dp[i][j]+=dp[i-1][j+1] % MODULO
return (dp[steps-1][0]+dp[steps-1][1]) % MODULO
12.整数转罗马数字
- 转换规则:1,5,10,50,100,500,1000:I,V,X,L,C,D,M
没啥技术含量的代码:
class Solution:
def intToRoman(self, num: int) -> str:
result = ""
Roman_dict = {'M':1000,'CM':900,'D':500,'CD':400,'C':100,'XC':90,'L':50,'XL':40,'X':10,'IX':9,'V':5,'IV':4,'I':1}
for ro,data in Roman_dict.items():
result += ro * (num//data)
num %= data
return result
13.罗马数字转整数
逆:12.整数转罗马数字
没啥技术含量代码:(思路:该数比后一数小,-该数,否则 +该数得结果)
class Solution:
def romanToInt(self, s: str) -> int:
# 罗马数字,将其转换成整数
roman_dict = {'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000}
# IV,IX,XL,XC,CD,CM
result = 0
temp = int(roman_dict[s[0]])
for i in range(1,len(s)):
data = int(roman_dict[s[i]])
if temp < data:
result -= temp
else:
result += temp
temp = data
result += temp
return result
208.实现Trie(前缀树)
- 实现字典树,关键是数据结构
children : list[26], isEnd : boolean
.
注:ord(letter) - 97
ASCII转 int,或者 ord(ch) - ord("a")
.
class Trie:
# 前缀树:用于高效地存储和检索字符串数据集中的键
def __init__(self):
"""
Initialize your data structure here.
"""
self.children = [None]*26 # 26位小写字母
self.isEnd = False # 判断当前是否为字符串结尾
def insert(self, word: str) -> None:
"""
Inserts a word into the trie.
"""
children = self.children
for letter in word:
p = children[ord(letter)-97]
if p:
children = p.children
else:
p = Trie()
children[ord(letter)-97] = p
children = p.children
p.isEnd = True
def search(self, word: str) -> bool:
"""
Returns if the word is in the trie.
"""
children = self.children
for letter in word:
p = children[ord(letter)-97]
if p:
children = p.children
else:
return False
if p.isEnd:
return True
else:
return False
def startsWith(self, prefix: str) -> bool:
"""
Returns if there is any word in the trie that starts with the given prefix.
"""
children = self.children
for letter in prefix:
p = children[ord(letter)-97]
if p:
children = p.children
else:
return False
return True
# Your Trie object will be instantiated and called as such:
# obj = Trie()
# obj.insert(word)
# param_2 = obj.search(word)
# param_3 = obj.startsWith(prefix)
421.数组中两个数的最大异或值
- 题目描述:找
n
u
m
s
[
i
]
⊕
n
u
m
s
[
j
]
nums[i] \oplus nums[j]
nums[i]⊕nums[j] 的最大值。
二重循环枚举会时间超限。
字典树
class Trie:
# 前缀树:用于高效地存储和检索字符串数据集中的键
def __init__(self):
"""
Initialize your data structure here.
"""
self.children = [None]*2
def insert(self, word: str) -> None:
"""
Inserts a word into the trie.
"""
p = self
i = 30
while i>=0:
num = (word >> i) & 1 # 取数
if p.children[num]:
p = p.children[num]
else:
new = Trie()
p.children[num] = new
p = new
i-=1
def check(self, word: str): # 计算树中与word异或结果最大值
p = self
i = 30
x = 0
while i>=0:
num = (word >> i) & 1 # 取数
# num:0,尽量取1;num:1,尽量取0
if num == 0:
if p.children[1]:
p = p.children[1]
x = x*2+1 # 异或结果
else:
p = p.children[0]
x = x*2
else:
if p.children[0]:
p = p.children[0]
x = x*2+1
else:
p = p.children[1]
x = x*2
i-=1
return x
class Solution:
def findMaximumXOR(self, nums: List[int]) -> int:
# nums[i] XOR nums[j] 的最大运算结果
# 最大:相异位数越多
# O(n)时间
# 创建树
root = Trie()
x = 0
for i in range(len(nums)-1):
root.insert(nums[i])
x = max(x, root.check(nums[i+1]))
return x
421.数组中两个数的最大异或值
字典树,参考题解v(☞゚ヮ゚)☞这里
a
i
a_i
ai枚举nums,并通过字典树快速找到能使
a
i
⊕
a
j
a_i\oplus a_j
ai⊕aj最大的
a
j
a_j
aj。
class Trie:
# 前缀树:用于高效地存储和检索字符串数据集中的键
def __init__(self):
"""
Initialize your data structure here.
"""
self.children = [None]*2
def insert(self, word: str) -> None:
"""
Inserts a word into the trie.
"""
p = self
i = 30
while i>=0:
num = (word >> i) & 1 # 取数
if p.children[num]:
p = p.children[num]
else:
new = Trie()
p.children[num] = new
p = new
i-=1
def check(self, word: str): # 计算树中与word异或结果最大值
p = self
i = 30
x = 0
while i>=0:
num = (word >> i) & 1 # 取数
# num:0,尽量取1;num:1,尽量取0
if num == 0:
if p.children[1]:
p = p.children[1]
x = x*2+1 # 异或结果
else:
p = p.children[0]
x = x*2
else:
if p.children[0]:
p = p.children[0]
x = x*2+1
else:
p = p.children[1]
x = x*2
i-=1
return x
class Solution:
def findMaximumXOR(self, nums: List[int]) -> int:
# nums[i] XOR nums[j] 的最大运算结果
# O(n)时间
root = Trie()
x = 0
for i in range(len(nums)-1):
root.insert(nums[i])
x = max(x, root.check(nums[i+1]))
return x
993.二叉树的堂兄弟节点
- 判断是否为堂兄节点:两个节点深度相同,但父节点不同。二叉树具有唯一值。
广度优先变形:层次遍历(每次根据当前队列长度,将节点遍历一遍),好处:不需额外添加深度int.
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isCousins(self, root: TreeNode, x: int, y: int) -> bool:
# 层次遍历
stack = [root] # 队列
while stack:
parent = []
for _ in range(len(stack)): # 当前层节点个数
p = stack.pop(0)
if p.left:
if p.left.val != x and p.left.val != y:
stack.append(p.left)
else:
parent.append(p.val)
if p.right:
if p.right.val != x and p.right.val != y:
stack.append(p.right)
else:
parent.append(p.val)
if parent: # 有俩父且不相等则为True
if len(parent) == 2 and parent[0]!=parent[1]:
return True
else:
return False
return False
1442.形成两个异或相等数组的三元组数目
- 数组arr中取三个下标 i , j , k i,j,k i,j,k, 0 ≤ i < j ≤ k < a r r . l e n g t h 0 \leq i < j \leq k < arr.length 0≤i<j≤k<arr.length。求 a = b a=b a=b取法数量。
- a = a r r [ i ] ⊕ a r r [ i + 1 ] ⊕ . . . ⊕ a r r [ j − 1 ] a = arr[i] \oplus arr[i + 1] \oplus ... \oplus arr[j - 1] a=arr[i]⊕arr[i+1]⊕...⊕arr[j−1]
- b = a r r [ j ] ⊕ a r r [ j + 1 ] ⊕ . . . ⊕ a r r [ k ] b = arr[j] \oplus arr[j + 1] \oplus ... \oplus arr[k] b=arr[j]⊕arr[j+1]⊕...⊕arr[k]
求a、b值见1310.子数组异或查询
a
=
F
[
j
−
1
]
⊕
F
[
i
−
1
]
b
=
F
[
k
]
⊕
F
[
j
−
1
]
a
=
b
⇒
F
[
i
−
1
]
=
F
[
k
]
只
需
确
定
i
,
k
;
j
∈
(
i
,
k
]
.
a = F[j-1]\oplus F[i-1]\\ b = F[k]\oplus F[j-1]\\ a=b\Rightarrow F[i-1]=F[k] \\ 只需确定i,k; j \in (i,k].
a=F[j−1]⊕F[i−1]b=F[k]⊕F[j−1]a=b⇒F[i−1]=F[k]只需确定i,k;j∈(i,k].
class Solution:
def countTriplets(self, arr: List[int]) -> int:
n = len(arr)
F = [0]*(n+1) # F[n]是为了防止找F[0]时溢出F[-1]=0
F[0] = arr[0]
for i in range(1,n):
F[i] = F[i-1]^arr[i]
count = 0
for i in range(n-1):
for k in range(i+1,n):
# a = F[j-1]^F[i-1]
# b = F[k]^F[j-1]
# F[i-1]?=F[k] 只需限定i,k; j \in (i,k].
if F[i-1]==F[k]:
count += k-i
return count
692.前k个高频单词
哈希字典+排序
class Solution:
def topKFrequent(self, words: List[str], k: int) -> List[str]:
# 返回前 k 个出现次数最多的单词
# O(n log k) 时间复杂度和 O(n) 空间复杂度
count = {} # (哈希表)字典统计出现次数
for i in words:
count[i] = count.get(i,0) + 1
# 排序
result = []
for key, val in count.items():
result.append((key, val))
for i in range(k):
temp = []
for j in range(i+1,len(result)):
if result[i][1] < result[j][1] or (result[i][1] == result[j][1] and result[i][0]>result[j][0]):
temp = result[i]
result[i] = result[j]
result[j] = temp
return [result[i][0] for i in range(k)]
1035.不相交的线
动态规划,类似:1143.最长公共子序列
F
[
i
]
[
j
]
F[i][j]
F[i][j] 为
n
u
m
s
1
[
:
i
]
nums1[:i]
nums1[:i] 和
n
u
m
s
2
[
:
j
]
nums2[:j]
nums2[:j] 的最大不相交线个数。
- 这里
F
F
F数组我取的大小是
l
1
∗
l
2
l1*l2
l1∗l2,并单独判断了边界值
F
[
:
]
[
0
]
F[:][0]
F[:][0]和
F
[
0
]
[
:
]
F[0][:]
F[0][:]。
待改进:大小取 ( l 1 + 1 ) ∗ ( l 2 + 1 ) (l1+1)*(l2+1) (l1+1)∗(l2+1),边界值均为0。 F [ i + 1 ] [ j + 1 ] F[i+1][j+1] F[i+1][j+1] 为 n u m s 1 [ : i ] nums1[:i] nums1[:i] 和 n u m s 2 [ : j ] nums2[:j] nums2[:j] 的最大不相交线个数。 - 动态转移方程:
F [ i ] [ j ] = F [ i − 1 ] [ j − 1 ] + 1 , if n u m s 1 [ i ] = = n u m s 2 [ j ] = m a x ( F [ i − 1 ] [ j ] , F [ i ] [ j − 1 ] ) , else . F[i][j] = F[i-1][j-1]+1, \text{ if } nums1[i]==nums2[j]\\ =max(F[i-1][j], F[i][j-1]), \text{ else }. F[i][j]=F[i−1][j−1]+1, if nums1[i]==nums2[j]=max(F[i−1][j],F[i][j−1]), else .
class Solution:
def maxUncrossedLines(self, nums1: List[int], nums2: List[int]) -> int:
# 求最大连线数
l1 = len(nums1)
l2 = len(nums2)
F = [[0]*l2 for _ in range(l1)]
# 边界值
F[0][0] = 1 if nums1[0] == nums2[0] else 0
for i in range(1,l1):
if nums1[i] == nums2[0]:
F[i][0] = 1
else:
F[i][0] = F[i-1][0]
for j in range(1,l2):
if nums1[0] == nums2[j]:
F[0][j] = 1
else:
F[0][j] = F[0][j-1]
# 动态转移
for i in range(1,l1):
for j in range(1,l2):
# F[i][j] = max(F[i-1][j],F[i][j-1])
# if nums1[i] == nums2[j]:
# F[i][j] = max(F[i-1][j-1]+1,F[i][j])
# 上述改进:
if nums1[i] == nums2[j]:
F[i][j] = F[i-1][j-1]+1
else:
F[i][j] = max(F[i-1][j],F[i][j-1])
return F[l1-1][l2-1]
1707.与数组中元素的最大异或值
- 给 n u m s nums nums数组和 q u e r i e s [ x , m ] queries[x,m] queries[x,m],求 m a x ( n u m s [ j ] ⊕ x ) , n u m s [ j ] < m max(nums[j] \oplus x), nums[j]<m max(nums[j]⊕x),nums[j]<m.
字典树: 208.实现Trie(前缀树)
给nums和queries排序,将nums中小于m的数先插入字典树中,再计算字典树中最大异或值。
import numpy as np
import math
class Trie():
def __init__(self):
self.left = None # 0
self.right = None # 1
class Solution:
def maximizeXor(self, nums: List[int], queries: List[List[int]]) -> List[int]:
n = len(nums)
a = len(queries)
# 排序
nums.sort()
index = (np.argsort([m for _,m in queries]))
answer = [0]*a
head = Trie()
l = int(math.log(nums[n-1],2)) # 树高度
def insert(num):
# 插入
i = l
p = head
while i >= 0:
t = num >> i & 1
i -= 1
if t == 0:
if not p.left:
new = Trie()
p.left = new
p = p.left
if t == 1:
if not p.right:
new = Trie()
p.right = new
p = p.right
def search(x):
# 计算最大异或值
# max(nums[j] XOR x), nums[j]<m
i = l
p = head
result = x >> (i+1)
while i >= 0:
t_x = x>>i & 1
i -= 1
if t_x == 0:
if p.right:
p = p.right
result = result*2+1
else:
p = p.left
result *= 2
else:
if p.left:
p = p.left
result = result*2+1
else:
p = p.right
result *= 2
return result
i = 0
for item in index:
# x,m: queries
x, m = queries[item]
while i<n and nums[i] <= m:
insert(nums[i])
i += 1
if i == 0:
answer[item] = -1 # 没有更小值,为-1
else:
answer[item] = search(x)
return answer
131.分割回文串
- 找字符串所有可能的回文子串分割方案(从头分到尾) ————后续题目:Ⅱ、Ⅲ、Ⅳ
递归
待改进: 存储重复结果,后续直接调用(dp)
class Solution:
def partition(self, s: str) -> List[List[str]]:
# 所有可能的回文串分割方案
# length <= 16
# 递归
# F = {} DP没用上
n = len(s)
def is_pstring(s): # 判断是否为回文串
if s == s[::-1]:
return True
else:
return False
def sub_partition(s, n, result): # 结果不断合并并传递到最底层直接返回
if n == 1:
return [result+[s]]
if n == 0:
return [result]
temp = []
for i in range(1, n+1):
if is_pstring(s[:i]): # 提前结束, 判读不成立则不继续进行
t = sub_partition(s[i:],n-i,result+[s[:i]])
temp += t
return temp
result = sub_partition(s,n,[])
return result
664.奇怪的打印机
- 每次在任意位置打印连续 同一字符,可覆盖。求最少打印次数。
DP:最少打印次数
F
[
i
,
j
]
F[i,j]
F[i,j],起始位置
i
i
i,终点位置
j
j
j。
F[i, j] = F[i+1,j-1]+1 if a[i]==a[j] else min(F[i,k]+F[k+1,j]) i<=k<j
F
F
F初始化为
1
1
1,顺序计算
[
F
[
j
,
j
+
i
]
j
=
0
n
−
i
]
i
=
0
n
[F[j,j+i]_{j=0}^{n-i}]_{i=0}^{n}
[F[j,j+i]j=0n−i]i=0n.
class Solution:
def strangePrinter(self, s: str) -> int:
# 最少打印次数
n = len(s)
MAX = float('inf')
F = [[1]*n for _ in range(n)]
for i in range(0,n):
for j in range(0, n-i):
# F(j, j+i)
if s[j] == s[j+i]:
F[j][j+i] = F[j][j+i-1]
else:
temp = MAX
for k in range(j,j+i):
temp = min(temp,F[j][k]+F[k+1][j+i])
F[j][j+i] = temp
return F[0][n-1]
1190.反转每队括号间的子串
栈
遇右括号时将栈内遇左括号前元素都取出并反转去掉括号放回栈内。
??反转操作有重复,简化?
class Solution:
def reverseParentheses(self, s: str) -> str:
# 逐层反转每对匹配括号中的字符串
# 栈
stack = []
for i in range(len(s)):
if s[i] == ')':
temp = []
while stack[-1] !='(':
temp.append(stack.pop())
stack.pop()
while temp:
stack.append(temp.pop(0))
else:
stack.append(s[i])
return ''.join(stack)
477.汉明距离总和
汉明距离:bin(a^b).count('1')
,暴力穷举会超时。
按位统计所有数。
import numpy as np
class Solution:
def totalHammingDistance(self, nums: List[int]) -> int:
# 数组中任意两个数之间汉明距离的总和
# def HammingDistance(a,b): # 计算汉明距离
# return bin(a^b).count('1')
# # 暴力穷举结果时间超限O(n^2)
# count = 0
# for i in range(len(nums)):
# for j in range(i+1,len(nums)):
# count += HammingDistance(nums[i], nums[j])
# return count
n = len(nums)
count = 0
for i in range(30):
temp = 0
for j in range(n):
temp += nums[j]>>i & 1
count += temp*(n-temp) # 统计所有数每一位中,1的个数*0的个数 = 该位汉明距离
return count
234.2的幂
- 判断是否是 2 的幂次方
使用math.log(n,2)
,在n很大时会有很小的余数不能使用。
使用n&(n-1) == 0
, e . g . 1000 & 0111 = = 0 e.g.1000 \& 0111 == 0 e.g.1000&0111==0
class Solution:
def isPowerOfTwo(self, n: int) -> bool:
# 判断是否是 2 的幂次方
# -2^31 <= n <= 2^31 - 1
if n<=0:
return False
return n&(n-1) == 0 # 1000 & 0111 == 0
342.4的幂
- 判断是否是4的幂次方
先判断是否是2的幂次方,
n
&
(
n
−
1
)
=
=
0
n\&(n-1)==0
n&(n−1)==0。
4的幂次方有偶数个0,1在奇数位上。
题解:
-
m
a
s
k
=
(
10101010101010101010101010101010
)
2
mask = (10101010101010101010101010101010)_2
mask=(10101010101010101010101010101010)2,
m
a
s
k
&
n
=
=
0
mask\&n==0
mask&n==0。
0xaaaaaaaa = 0b10101010101010101010101010101010
,0x:16进制,0b:2进制。 -
n
%
3
=
=
1
n\%3==1
n%3==1
class Solution:
def isPowerOfFour(self, n: int) -> bool:
# 4:100; 16:10000; 64:1000,000; 偶数个0
if n&(n-1)==0 and bin(n).count('0')%2==1: # num % 3 == 1
return True
else:
return False
523.连续的子数组和
- nums数组是否有连续子数组,元素总和为 k 的倍数(题目这里0是任何数的倍数)
前缀和+哈希表
一维数组
F
[
i
]
F[i]
F[i] 存储
0
0
0 ~
i
i
i 的和。
class Solution:
def checkSubarraySum(self, nums: List[int], k: int) -> bool:
# 是否有连续子数组元素总和为 k 的倍数
n = len(nums)
F = [0]*n
# 初始化
F[0] = nums[0]
for i in range(1,n):
F[i] = F[i-1]+nums[i]
if F[i]%k==0:
return True
for i in range(1,n):
temp = F[i]
if temp > k:
for j in range(1,i):
temp = F[i]-F[j-1] # i~j:F[i]-F[j-1]
if temp<k:
break
elif temp%k == 0:
return True
# 0*x = 0情况
temp = 0
for i in range(0,n):
if nums[i]==0:
if temp == 1:
return True
temp = 1
else:
temp = 0
return False
525.连续数组
- 相同数量的 0 和 1 的最长连续子数组
前缀和+哈希表,同523.连续的子数组和
遇0减1,遇1加1。相等值位置区间0、1数量相等。
class Solution:
def findMaxLength(self, nums: List[int]) -> int:
# 相同数量的 0 和 1 的最长连续子数组
hashmap = {0:-1}
n = len(nums)
# F = [0]*(n+1)
f = 0
count = 0
F[0] = 1 if nums[0]==1 else -1
for i in range(0, n):
if nums[i] == 0:
# F[i] = F[i-1]-1 # '1'-'0'
f -= 1
else:
# F[i] = F[i-1]+1
f += 1
if F[i] in hashmap:
count = max(count, i-hashmap[f])
else:
hashmap[f] = i
return count
160.相交链表
用俩list存储两个链表的各个节点,从尾部节点到首节点判断相等。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
# 两个单链表相交的起始节点
p1,p2 = headA,headB
list1 = []
list2 = []
while p1:
list1.append(p1)
p1 = p1.next
while p2:
list2.append(p2)
p2 = p2.next
if not (list1 and list2 and list1[-1]==list2[-1]):
return None
while list1 and list2 and list1[-1]==list2[-1]:
list1.pop()
list2.pop()
pass
if list1:
return list1[-1].next
else:
return headA
474.一和零
类 背包问题
方法一:
- 字典
hash_map[x,y] = z
表示x
个0y
个1最大子集的大小为z
. - 对于
strs
中每个字符串都遍历更新字典。
方法二:
- 动态规划:三维
dp[i][j][k]
表示在前 i 个字符串中,使用 j 个 0 和 k 个 1 的情况下最多可以得到的字符串数量。 - d p [ k ] [ i ] [ j ] = d p [ i − 1 ] [ j ] [ k ] ; i < z e r o s and j < o n e s . dp[k][i][j] = dp[i−1][j][k]; i<zeros \text{ and } j<ones. dp[k][i][j]=dp[i−1][j][k];i<zeros and j<ones.
-
d
p
[
i
]
[
j
]
[
k
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
[
k
]
,
d
p
[
i
−
1
]
[
j
−
z
e
r
o
s
]
[
k
−
o
n
e
s
]
+
1
)
;
i
<
z
e
r
o
s
or
j
<
o
n
e
s
.
dp[i][j][k] = max(dp[i−1][j][k],dp[i−1][j−zeros][k−ones]+1); i<zeros \text{ or } j<ones.
dp[i][j][k]=max(dp[i−1][j][k],dp[i−1][j−zeros][k−ones]+1);i<zeros or j<ones.
# 方法一
import copy
class Solution:
def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
# strs 的最大子集的大小,最多有 m 个 0 和 n 个 1
def count(s):
# return 字符串s中 0 和 1 的个数
count = 0
n = len(s)
for i in range(n):
if s[i] == '0':
count += 1
return count, n-count
hash_map = {}
for s in strs:
x,y = count(s)
for key, val in hash_map.copy().items():
(key_0, key_1) = key
k0, k1 = key_0 + x, key_1 + y
if k0 <= m and k1 <= n:
hash_map[k0, k1] = max(hash_map.get((k0, k1),0), 1+val) # 更新字典
if x<=m and y<=n:
hash_map[x, y] = hash_map.get((x, y), 1) # 添加当前值
if hash_map:
return max(hash_map.values())
else:
return 0
494.目标和
list
存储结果:超时
import numpy as np
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
# 构造表达式=target个数, 只能+或-
c = nums.count(0)
while 0 in nums:
nums.remove(0)
n = len(nums)
count = np.zeros(2**n)
for i in range(n):
m = 2**i
for j in range(m):
count[j+m] = count[j]-nums[i]
count[j] += nums[i]
return int(np.sum(count == target))*2**c
- 递归,超时
import numpy as np
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
# 构造表达式=target个数, 只能+或-
# 递归
if len(nums) == 1:
count = 0
if nums[0] == target:
count += 1
if nums[0] == -1*target:
count += 1
return count
return self.findTargetSumWays(nums[1:],target+nums[0]) + self.findTargetSumWays(nums[1:],target-nums[0])
- DP,规律: 能用递归解决的都能用动态规划解决?
d p [ i ] [ j ] = d p [ i − 1 ] [ j − n u m s [ i ] ] + d p [ i − 1 ] [ j + n u m s [ i ] ] dp[i][j] = dp[i-1][j-nums[i]]+dp[i-1][j+nums[i]] dp[i][j]=dp[i−1][j−nums[i]]+dp[i−1][j+nums[i]] or
d p [ i ] [ ( j − 1 ) + n u m s [ j ] ] = d p [ i − 1 ] [ j − 1 ] dp[i][(j-1)+nums[j]] = dp[i-1][j-1] dp[i][(j−1)+nums[j]]=dp[i−1][j−1], d p [ i ] [ ( j + 1 ) + n u m s [ j ] ] = d p [ i − 1 ] [ j − 1 ] dp[i][(j+1)+nums[j]] = dp[i-1][j-1] dp[i][(j+1)+nums[j]]=dp[i−1][j−1]。
存储结构想了很久,稀疏矩阵 → \rightarrow →最终采用字典列表e.g.[{}{}]
import numpy as np
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
# DP
# dp[i][j], 0~i:nums, target:j
# dp[i][j] = dp[i][j+nums[0]]+dp[i][j-nums[0]]
n = len(nums)
m = 2**n
dp = [{} for _ in range(n)]
dp[0][nums[0]] = dp[0].get(nums[0], 0)+1
dp[0][-nums[0]] = dp[0].get(-nums[0], 0)+1
for i in range(1, n):
for tar,val in dp[i-1].items():
dp[i][tar+nums[i]] = dp[i].get(tar+nums[i], 0) + val
dp[i][tar-nums[i]] = dp[i].get(tar-nums[i], 0) + val
if dp[n-1].get(target):
return dp[n-1][target]
else:
return 0
879.盈利计划
- 方法1:
F[i,j] = k
,员工i
人,利润j
,方案个数k
。
每增加一个工作,原F不变,更新: F [ i + g r o u p [ i ] , j + p r o f i t [ i ] ] + = k F[i+group[i], j+profit[i]] += k F[i+group[i],j+profit[i]]+=k。
最后需要遍历F找>minProfit
个数。 - 方法2:DP
# Plan 1
import copy
class Solution:
def profitableSchemes(self, n: int, minProfit: int, group: List[int], profit: List[int]) -> int:
# 至少产生 minProfit 利润的子集,计划数
m = len(group)
F = {(0,0):1}
if group[0]<=n:
F[group[0], profit[0]] = 1
for i in range(1,m):
for (peo,pro), plans in F.copy().items():
if peo+group[i] <= n:
F[peo+group[i], pro+profit[i]] = F.get((peo+group[i], pro+profit[i]), 0) + plans
mod = 10**9 + 7
count = 0
for (_,pro), plans in F.items():
if pro >= minProfit:
count+=plans
return count%mod
518.零钱兑换
DP: F [ i ] = ∑ c o i n F [ i − c o i n ] F[i] = \sum^{coin} F[i-coin] F[i]=∑coinF[i−coin]
- coins: 各硬币分值,求组成 amount 有几种方案。
注意循环顺序,不要重复计算:
- 如果先遍历 coins 再遍历 i~amount 会有重复计算(eg.3(2(1+1)+1, 1+1+1))。
- 所以先遍历 i~amount 再遍历 coins
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
# 凑成总金额的硬币组合数
# F[n] = sum(F[n-coins[i]])
F = [0] * (amount+1)
F[0] = 1
for coin in coins:
for i in range(coin,amount+1):
F[i] += F[i-coin]
return F[amount]
279.完全平方数
DP :方法类似于:518.零钱兑换
F
[
i
]
=
m
i
n
(
F
[
i
−
n
u
m
]
)
+
1
F[i] = min(F[i-num])+1
F[i]=min(F[i−num])+1,
n
u
m
num
num为平方数
class Solution:
def numSquares(self, n: int) -> int:
# 最小个数的完全平方数和等于n
# F[i] = min(F[i-num])+1, num某平方数
F = [float('inf')]*(n+1)
F[0] = 0 # 初始值
i = 1
while i*i<=n:
temp = i*i
for j in range(temp,n+1):
F[j] = min(F[j], F[j-temp]+1)
i+=1
return F[n]
877.石子游戏
- 题目描述:奇数个石子,分为一行偶数堆。每次只能拿两端的一堆。Alex先拿,若赢返回True。
DP:
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]为从第i堆到第j堆,当前玩家与另一个玩家的石子数量之差的最大值。
d
p
[
i
]
[
j
]
=
m
a
x
(
p
i
l
e
s
[
i
]
−
d
p
[
i
+
1
]
[
j
]
,
p
i
l
e
s
[
j
]
−
d
p
[
i
]
[
j
−
1
]
)
dp[i][j] = max(piles[i]-dp[i+1][j], piles[j]-dp[i][j-1])
dp[i][j]=max(piles[i]−dp[i+1][j],piles[j]−dp[i][j−1])
即:当前玩家拿了的一堆 -(另一个玩家 - 未拿当前玩家)= 当前玩家 - 另一个玩家
其实:return True # Alex总是赢
. 若存在li能赢的情况,则Alex总可以选择li的那种拿法也总是能赢。
class Solution:
def stoneGame(self, piles: List[int]) -> bool:
# 总数奇数,偶数堆,石子最多的玩家获胜
n = len(piles)
dp = [[0]*n for _ in range(n)]
for i in range(n):
dp[i][i] = piles[i]
for i in range(1,n):
for j in range(n-i):
dp[j][j+i] = max(piles[j]-dp[j+1][j+i], piles[j+i]-dp[j][j+i-1])
return dp[0][n-1]>0
401.二进制手表
- 已知亮灯数目
turnedOn
,求所有可能表示的时间。 - 时针4位(0-11),秒针6位(0-59)
- 递归
class Solution:
def readBinaryWatch(self, turnedOn: int) -> List[str]:
# 当前亮着的 LED 的数量, 返回二进制手表可以表示的所有可能时间
def Binary(count, total, current):
"""
统计当前计数list并返回
count:亮灯数
total:总灯数
current:当前计数
"""
if count == 0:
return [current]
elif total == 0:
return []
temp = []
current *= 2
for i in range(total-count+1):
temp += Binary(count-1, total-i-1, (current+1)*2**i)
return temp
save = []
for i in range(max(0,turnedOn-6),min(4,turnedOn+1)):
save1 = Binary(i, 4, 0)
save2 = Binary(turnedOn-i, 6, 0)
for s1 in save1:
for s2 in save2:
if s1<=11 and s2<=59:
save.append('{}:'.format(s1)+"%02d" % s2)
return save
38.字符串的排序
- 题目描述:打印出字符串所有的字符排列。
- 回溯:递归
result
列表去重:list(set(result))
(注:set()
无序不重复元素集)
class Solution:
def permutation(self, s: str) -> List[str]:
# 打印出该字符串中字符的所有排列
if len(s)==1: # 终止条件
return [s]
result = []
for i in range(len(s)):
# 每次固定列表中一个位(s[i])为首位, 递归得到剩下结果 t
result += [s[i]+t for t in self.permutation(s[0:i]+s[i+1:])]
return list(set(result)) # 去重
149.直线上最多的点数
- 题目:给所有点坐标,算最多有多少个点在同一条直线上。
算所有两点间的直线。
from fractions import Fraction
class Solution:
def maxPoints(self, points: List[List[int]]) -> int:
# 该方法有重复,暴力
points.sort() # 排序
n = len(points)
line = [{} for _ in range(n-1)]
for i in range(n):
for j in range(i+1,n):
# i,j两点确定直线 y = kx + b
if points[j][0] == points[i][0]:
k,b = 0, points[j][0]
else:
k = Fraction(points[j][1]-points[i][1], points[j][0]-points[i][0]) # 分数表示
b = points[j][1] - k*points[j][0]
line[i][k,b] = line[i].get((k,b), 1) + 1
temp = 1
for i in range(n-1):
temp = max(list(line[i].values())+[temp])
return temp
168.Excel表列名称
- 题目:返回正整数在 Excel 表中相对应的列名称(类似进制表示)
class Solution:
def convertToTitle(self, columnNumber: int) -> str:
# 返回正整数在 Excel 表中相对应的列名称
# 1~26:A~Z (每位1~26; 每位权重:26^2, 26, 1)
result = ''
while columnNumber:
# 题解:
# a0 = (columnNumber - 1) % 26 + 1 # 取1~26位
# result = chr(a0 - 1 + ord("A")) + result # 1~26位字符
# columnNumber = (columnNumber - a0) / 26
if columnNumber % 26 == 0: # 单独判断26
result = chr(ord("Z"))+result
columnNumber -= 26
else:
result = chr(ord("@") + columnNumber%26) + result # 1~25:取26余数
columnNumber //= 26 # 整除26
return result
LCP07.传递信息
广度优先搜索
其余方法:深度优先搜索,dp
class Solution:
def numWays(self, n: int, relation: List[List[int]], k: int) -> int:
# 方案数: k次从0~(n-1)
save = {}
for r in relation:
save[r[0]] = save.get(r[0], [])
save[r[0]].append(r[1])
present = [0]
for i in range(k):
temp = []
for l in present:
if save.get(l):
temp.extend(save[l])
present = temp
count = 0
for i in present:
if i == n-1:
count += 1
return count
1833.雪糕的最大数量
排序+贪心
class Solution:
def maxIceCream(self, costs: List[int], coins: int) -> int:
# 雪糕的最大数量,价格数组 costs 和现金量 coins
# 贪心,选最小
costs.sort()
i = 0
while i<len(costs) and costs[i] <= coins:
coins -= costs[i]
i += 1
return i
726.原子的数量
非右括号入栈,遇右括号把前面最后一个左括号之后的出现次数都乘括号后的数,去掉左括号。循环到字符串尾部。
栈内元素加入字典,排序,变字符串输出。
class Solution:
def countOfAtoms(self, formula: str) -> str:
result = {}
n = len(formula)
i = 0
stack = []
while i < n:
if formula[i] == '(':
stack.append([formula[i], 1])
i += 1
elif formula[i] == ')':
# num = int(formula[i+1])
num = 0
j = i + 1
while j<n and '0'<=formula[j]<='9': # 数字
num = num*10 + int(formula[j])
j += 1
if not num:
num = 1
i = j
j = -1
while stack[j][0] != '(':
stack[j][1] *= num
j -= 1
stack.pop(j)
else:
if i+1<n and 'a'<=formula[i+1]<='z': # 小写字母
count = 0
j = i + 2
while j<n and '0'<=formula[j]<='9': # 数字
count = count*10 + int(formula[j])
j += 1
if not count:
count = 1
stack.append([formula[i:i+2], count])
i = j
else:
count = 0
j = i + 1
while j<n and '0'<=formula[j]<='9': # 数字
count = count*10 + int(formula[j])
j += 1
if not count:
count = 1
stack.append([formula[i], count])
i = j
while stack:
p = stack.pop()
result[p[0]] = result.get(p[0], 0) + p[1]
temp = ''
for key, val in sorted(result.items()):
temp += key
if val > 1:
temp += str(val)
return temp
1711.大餐计数
- 求大餐的数量,大餐:恰好包含两道不同餐品,美味程度之和等于 2 的幂。
暴力会超时
0
<
=
d
e
l
i
c
i
o
u
s
n
e
s
s
[
i
]
<
=
2
20
0 <= deliciousness[i] <= 2^{20}
0<=deliciousness[i]<=220
哈希表,穷举幂指数,若
2
n
−
d
e
l
i
c
i
o
u
s
n
e
s
s
[
i
]
∈
d
e
l
i
c
i
o
u
s
n
e
s
s
[
0
:
n
]
2^n-deliciousness[i] \in deliciousness[0:n]
2n−deliciousness[i]∈deliciousness[0:n],则计数加一。
import math
class Solution:
def countPairs(self, deliciousness: List[int]) -> int:
count = 0
MOD = 10**9 + 7
n = len(deliciousness)
de = {}
maximum = max(deliciousness)
m = int(math.log(2*maximum,2)) + 1
for i in range(n):
temp = 0 if not deliciousness[i] else int(math.log(deliciousness[i], 2))
for j in range(temp, m): # m->22
if 2**j - deliciousness[i] in de:
count += de[2**j - deliciousness[i]]
de[deliciousness[i]] = de.get(deliciousness[i] ,0) + 1
count %= MOD
return count % MOD
930.和相同的二元子数组
- 和为 goal 的 非空 子数组 (连续) 个数, nums[i] 不是 0 就是 1
方法一:F为按位统计和,超时
class Solution:
def numSubarraysWithSum(self, nums: List[int], goal: int) -> int:
n = len(nums)
F = [0]*(n+1)
F[0] = nums[0]
count = 0
for i in range(1, n):
F[i] = F[i-1] + nums[i]
for i in range(n):
if F[i] >= goal:
j = -1
while F[i]-F[j] > goal:
j += 1
while j < i and F[i]-F[j] == goal:
print(i, j)
j += 1
count += 1
return count
方法二:用哈希表记录每一种前缀和出现的次数,假设我们当前枚举到元素
nums
[
j
]
\textit{nums}[j]
nums[j],我们只需要查询哈希表中元素
sum
[
j
]
−
goal
\textit{sum}[j]-\textit{goal}
sum[j]−goal 的数量。
来源:这里
方法三:考虑 nums[i] 不是 0 就是 1,记录1的位置
class Solution:
def numSubarraysWithSum(self, nums: List[int], goal: int) -> int:
# 和为 goal 的 非空 子数组(连续)个数, nums[i] 不是 0 就是 1
n = len(nums)
count = 0
# F[i] = j: 第i个1下标为j
# F[0] = -1, F[-1] = n
F = [-1]
for i in range(n):
if nums[i] == 1:
F.append(i)
F.append(n)
if goal == 0: # 目标0单独计算,累加
for i in range(1, len(F)):
# 累加 F[i]-F[i-1]
count += sum(range(1, F[i]-F[i-1]))
return count
for i in range(goal, len(F)-1):
j = i - goal + 1
count += (F[j] - F[j-1]) * (F[i+1] - F[i]) # 两边0个数*
return count
981.基于时间的键值存储
哈希表+二分查找
class TimeMap:
def __init__(self):
"""
Initialize your data structure here.
"""
self.time_map = defaultdict(list)
def set(self, key: str, value: str, timestamp: int) -> None:
# 输入已经是升序排列
self.time_map[key].append([value, timestamp])
def get(self, key: str, timestamp: int) -> str:
# 二分查找,不大于timestamp的最大值
data = self.time_map[key]
left = 0
right = len(data) - 1
while left <= right:
mid = (left + right) // 2
if data[mid][1] == timestamp:
right = mid
break
elif data[mid][1] > timestamp:
right = mid - 1
else:
left = mid + 1
return data[right][0] if right>=0 else ""
274.H的指数
共有 h 篇论文分别被引用了至少 h 次, 其余的 N - h 篇论文每篇被引用次数 不超过 h 次
class Solution:
def hIndex(self, citations: List[int]) -> int:
citations.sort(reverse=True) # 排序
n = len(citations)
if not n or citations[0] < 1:
return 0
elif n == 1:
return 1
for i in range(1, n):
if citations[i] <= i:
return i
return n
275.H指数Ⅱ
class Solution:
def hIndex(self, citations: List[int]) -> int:
if not citations: # 空情况
return 0
n = len(citations)
i = 1
while i <= n and citations[n-i] >= i: # 只要第i个位置数>=i才考虑下一位
i += 1
return i-1
218.天际线问题⭐⭐
思路:参考☞这里
- 已知:
建筑物信息由三元组构成: (左边缘坐标,右边缘坐标,高度)
天际线关键点为 (坐标,变化后高度)
建筑物已经按照左边缘坐标排好序了,求关键点坐标列表。 - 思路:
按坐标顺序将 (坐标,高度) 加入列表,并排序:坐标递增高度递增。
遍历列表,遇到左边缘坐标,就将高度递增插入高度列表中,遇到右边缘删除该信息。
若在过程中,最大高度发生变化,则为关键点。
class Solution:
def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]:
heights = [] # (坐标,高度)列表
for left, right, h in buildings:
heights.append([left, -h])
heights.append([right, h])
heights.sort(key=lambda x:(x[0], x[1])) # 坐标递增高度递增
points = []
hightest = [0]
for pos, h in heights:
if h < 0: # 增加
i = 0
n = len(hightest)
while i<n and -h<=hightest[i]:
i += 1
hightest.insert(i, -h)
# 高度增高
if i == 0:
points.append([pos, hightest[0]])
else: # 删除
# 二分查找
left, right = 0, len(hightest)-1
while left <= right:
mid = (left + right) // 2
if hightest[mid] == h:
del(hightest[mid])
break
elif hightest[mid] < h:
right = mid - 1
else:
left = mid + 1
# 高度降低
if hightest[0] < h:
points.append([pos, hightest[0]])
return points
1818.绝对差值和
import numpy as np
class Solution:
def minAbsoluteSumDiff(self, nums1: List[int], nums2: List[int]) -> int:
n = len(nums1)
total = [0]*n
count = 0
MOD = 10**9 + 7
for i in range(n):
total[i] = abs(nums1[i]-nums2[i])
index = np.argsort(np.array(total)) # 排序下标从小到大
for i in index[::-1]: #从大到小
if count >= total[i]: # 提前终止条件
break
for j in range(n):
if i != j:
count = max(count, total[i] - abs(nums2[i]-nums1[j])) # 保留最大的数
return (sum(total) - count)% MOD
1931.用三种不同颜色为网格涂色⭐❓
图的问题
面试题10.02.变位词组
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
n = len(strs)
dic = defaultdict(list)
for word in strs:
p = list(word)
p.sort()
dic[''.join(p)].append(word)
return list(dic.values())
1838.最高频元素的频数
class Solution:
def maxFrequency(self, nums: List[int], k: int) -> int:
# 加一
nums.sort()
n = len(nums)
frequency = 1 # 最大可能频数
operand = 0 # 操作数
# 滑动窗口
for i in range(1, n):
j = i - frequency # 窗口尾部,窗口大小为freauency
operand += (nums[i]-nums[i-1]) * frequency # 当前窗口前端加一位之后的总操作数
if operand > k: # 操作次数多了,不考虑更小窗口情况
operand = operand - nums[i] + nums[j]
else:
frequency += 1
while j > 0:
j -= 1 # 窗口尾部增长
operand += nums[i] - nums[j]
if operand > k:
operand -= nums[i] - nums[j]
break
frequency += 1
return frequency
138.复制带随机指针的链表
"""
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head:
return None
save = {None:None} # node : new_node
# 头节点
new_head = Node(head.val)
save[head] = new_head
# 节点生成,next串起来
p = new_head
h = head.next
while h:
q = Node(h.val)
save[h] = q
p.next = q
p = q
h = h.next
p.next = None
# random串起来
p = new_head
while head:
p.random = save[head.random]
head = head.next
p = p.next
return new_head
1738.找出第K大的异或坐标值
class Solution:
def kthLargestValue(self, matrix: List[List[int]], k: int) -> int:
# 坐标(a,b) : matrix[i][j]异或得到(0<=i<=a ,0<=j<=b)
m, n = len(matrix), len(matrix[0])
dp = [0]*(n*(m+1))
for i in range(m):
count = 0
for j in range(n):
count ^= matrix[i][j]
dp[i*n+j] = dp[(i-1)*n+j]^count
dp.sort(reverse=True)
return dp[k-1]
671.二叉树中第二小的节点
非空特殊的二叉树:每个节点的子节点数量只能为 2 或 0,节点的值等于两个子节点中较小的一个。
想法:第一小的节点根节点,若左右节点和根节点值相同则存起来(栈或者队列都行),否则比较最小值。
e.g. 第一小的值1,第二小的值2。
class Solution:
def findSecondMinimumValue(self, root: TreeNode) -> int:
stack = [root]
result = float('inf')
while stack:
p = stack.pop()
if p.left:
if p.left.val == p.val: # 相等点入栈
stack.append(p.left)
else:
result = min(result, p.left.val) # 不等点找最小值
if p.right.val == p.val: # 相等点入栈
stack.append(p.right)
else:
result = min(result, p.right.val) # 不等点找最小值
if result < float('inf'):
return result
else:
return -1
987.二叉树的垂序遍历
class Solution:
def verticalTraversal(self, root: TreeNode) -> List[List[int]]:
stack = [[root,0,0]]
save = defaultdict(list)
while stack: # 层次遍历
p = stack.pop(0)
save[p[2]].append((p[1],p[0].val))
if p[0].left:
stack.append([p[0].left, p[1]+1, p[2]-1])
if p[0].right:
stack.append([p[0].right, p[1]+1, p[2]+1])
result = []
for key, val in sorted(save.items()): # 按列排序
val.sort(key=lambda x:(x[0], x[1])) # 按行排序,相同行按值排序
result.append([i for _,i in val])
return result
611.有效三角形个数
class Solution:
def triangleNumber(self, nums: List[int]) -> int:
# 统计其中可以组成三角形三条边的三元组个数
def binary(left, right, target):
while left <= right:
mid = (left + right)//2
if nums[mid] < target:
left = mid+1
else:
right = mid-1
return right
nums.sort() # 排序
n = len(nums)
count = 0
for i in range(n-2):
for j in range(i+1, n-1):
# 二分查找 从j+1~n-1:nums[i] + nums[j]
count = count + binary(j+1, n-1, nums[i] + nums[j]) - j
return count
847.访问所有节点的最短路径
参考:here
- 队列,广度优先搜索:先把所有图上所有节点入队列,存储格式(e.g.当前点i, 已访问所有节点10001, 所有访问次数1)
- set保存到每个节点的访问状态,防止重复计算,存储格式(e.g.当前点i, 已访问所有节点10001)实现O(1)查找
- 直到找到一个访问了所有点的路径,返回。
class Solution:
def shortestPathLength(self, graph: List[List[int]]) -> int:
# 能够访问所有节点的最短路径的长度——>类旅行商(TSP)问题
# BFS
# 状态压缩:用n位二进制数
n = len(graph)
stack = [[i, 1<<i, 0] for i in range(n)] # 队列,存储 (当前点, 已访问节点, 所有访问次数)
state = {(i, 1<<i) for i in range(n)} # 时间复杂度O(1)
while stack:
p = stack.pop(0)
if p[1] == 2 ** n - 1:
return p[2]
for next_point in graph[p[0]]:
temp = p[1] | (1<<next_point)
if (next_point, temp) not in state:
stack.append([next_point, temp, p[2]+1])
state.add((next_point, temp))
return 0
516.最长回归子序列
求最长回归子序列长度,子序列可以不连续。
DP:
d
p
[
i
]
[
j
]
=
d
p
[
i
+
1
]
[
j
−
1
]
,
if
s
[
i
]
=
s
[
j
]
;
dp[i][j] = dp[i+1][j-1],\text{if }s[i]=s[j];
dp[i][j]=dp[i+1][j−1],if s[i]=s[j];
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
]
[
j
−
1
]
,
d
p
[
i
+
1
]
[
j
]
)
,
otherwise
.
dp[i][j] = max(dp[i][j-1], dp[i+1][j]),\text{otherwise}.
dp[i][j]=max(dp[i][j−1],dp[i+1][j]),otherwise.
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
n = len(s)
dp = [[0]*n for _ in range(n)]
for i in range(n):
dp[i][i] = 1
for i in range(1, n):
for j in range(n-i):
x,y = j, i+j
if s[x] == s[y]:
dp[x][y] = dp[x+1][y-1] + 2
else:
dp[x][y] = max(dp[x][y-1], dp[x+1][y])
return dp[0][n-1]
470.用Rand7()实现Rand10()
题解:这里
318.最大单词长度乘积
class Solution:
def maxProduct(self, words: List[str]) -> int:
# 不含有公共字母两个单词长度相乘的最大值
words_set = [set(i) for i in words]
n = len(words_set)
flag = 0
for i in range(n):
for j in range(i+1,n):
temp = 0
for k in words_set[i]:
if k in words_set[j]:
temp = 1
break
if temp == 0:
flag = max(flag, len(words[i]*len(words[j])))
return flag
python
内部函数(函数内函数)能访问函数外函数不能直接修改函数外变量。
若需要修改则需要加关键字nonlocal
,若加关键字global
只能覆盖原内容。
面向对象编程
- 封装、继承、多态
- 类、实例
内置函数来自☛这里
数学:
math.log(n, 2)
: l o g 2 n log^n_2 log2n
键盘/文件 → \rightarrow → 读入/读出:
- 读入input()
a = int(input()) # input()
b = [int(i) for i in input().split()] # input().split()
import sys
a = sys.stdin.readline() # 读一行
b = a.split() # 空格分割
# 一直输入
import sys
for line in sys.stdin:
b = line.split()
n = int(sys.stdin.readline().strip())
for i in range(n):
line = sys.stdin.readline().strip() # strip()去除首尾空格
values = list(map(int, line.split()))
- 文件
- read(), readline(), readlines()
- write(), writelines()
- truncate()字符串截取??
- open(), close()
- wrax()??
常用:
None
没有Null
(python)MAX = float('inf')
无穷大- 分数表示:防止小数不整除导致的余数不等情况
from fractions import Fraction
Fraction(4,5)
集合:
set()
无序不重复元素集&
交、|
并、-
差、^
补[[i, 1<<i] for i in range(n)]
, set内部是dict实现的,O(1)
查找时间复杂度
字典:dict = {key: value}
defaultdict(default_factory)
在访问当前不存在字典的key时,自动构建key : value (value类型是default_factory)default_factory
:int
, list
-
创建:
dict(zip('abc', [1, 2, 3]))
-
操作:
- remove, update, add, clear, discard, pop
- issubset是否是子集,issuperset是否父集,取并、交、差集……
-
添加:
dict[key]=value
,查找:dict[key]
ordict.get(key[, default])
。 -
取键值对
dict.items()
,dict.values()
,dict.keys()
. -
字典生成式:
{key : value for key, value in my_dictionary.items() if value > 10}
-
注意:
- 无序,不能按下标操作。
- list 和 dict 类型是 unhashable,不可作为key
列表:list:[]
- 表头插入
list.append(num)
,表尾取出list.pop()
- 列表合并:
+
或者list.extend(list2)
- 排序:
list.sort(reverse=True)
True: 大到小 list(range(i,j))
生成 i 到 j 递增列表list(map(int,列表名))
将列表中数据都变成int类型
数据类型转换:
ord(ch)
ASCII- 元组转列表
list()
- 转二进制
bin()
- 二进制转字符
chr()
- list 转 string:
''.join(stack)
,''
内是连接符 - int(),set(), dict(), complex,list(), float()