剑指 Offer 50. 第一个只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格‘ ‘。 s 只包含小写字母。
示例 1:
输入:s = “abaccdeff”
输出:‘b’
示例 2:
输入:s = “”
输出:’ ’
限制:
0 <= s 的长度 <= 50000
使用字典统计每个字符的出现频次,因为目前较新的python3版本中的字典其实已经是有序字典,所以不用担心key值出现顺序混乱的问题。
2.2.1 代码
def firstUniqChar(s: str) -> str:
value_dict = {}
for item in s:
value_dict[item] = value_dict.get(item, 0) + 1
for key,value in value_dict.items():
if value == 1:
return key
return ' '
def firstUniqChar(self, s: str) -> str:
position = dict()
q = collections.deque()
for i, ch in enumerate(s):
if ch not in position:
position[ch] = i
q.append(ch)
else:
position[ch] = -1
while q and position[q[0]] == -1:
q.popleft()
return ' ' if not q else q[0]
946. 验证栈序列
给定 pushed 和 popped 两个序列,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true;否则,返回 false 。
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
提示:
0 <= pushed.length == popped.length <= 1000
0 <= pushed[i], popped[i] < 1000
pushed 是 popped 的排列。
思路:
开辟一个栈stack来模拟入栈出栈的操作,对于pushed里的每一个元素,都进行一次入栈,
入栈之后比较当前的栈顶和队列头是否相同,如果相同,则栈顶和队头同时POP。
当pushed里的每一个元素都扫描结束时,如果stack为空,说明pushed和popped数组匹配成功,返回True;如果不为空,就说明不对,返回False。
class Solution(object):
def validateStackSequences(self, pushed, popped):
"""
:type pushed: List[int]
:type popped: List[int]
:rtype: bool
"""
stack = []
i = 0
for item in pushed:
stack.append(item)
while(stack and popped[i] == stack[-1]):
stack.pop()
i += 1
return stack == []
#下面是修改前
class Solution(object):
def validateStackSequences(self, pushed, popped):
"""
:type pushed: List[int]
:type popped: List[int]
:rtype: bool
"""
l = len(pushed)
stack = list()
for i in range(0, l):
stack.append(pushed[i])
while(stack and stack[-1] == popped[0]):
stack = stack[:-1]
popped = popped[1:]
return stack == []
692. 前K个高频单词
给一非空的单词列表,返回前 k 个出现次数最多的单词。
返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。
示例 1:
输入: [“i”, “love”, “leetcode”, “i”, “love”, “coding”], k = 2
输出: [“i”, “love”]
解析: “i” 和 “love” 为出现次数最多的两个单词,均为2次。
注意,按字母顺序 “i” 在 “love” 之前。
示例 2:
输入: [“the”, “day”, “is”, “sunny”, “the”, “the”, “the”, “sunny”, “is”, “is”], k = 4
输出: [“the”, “is”, “sunny”, “day”]
解析: “the”, “is”, “sunny” 和 “day” 是出现次数最多的四个单词,
出现次数依次为 4, 3, 2 和 1 次。
注意:
假定 k 总为有效值, 1 ≤ k ≤ 集合元素数。
输入的单词均由小写字母组成。
def topKFrequent(self, words, k):
"""
:type words: List[str]
:type k: int
:rtype: List[str]
"""
res = Counter(words)
res = sorted(res.items(),key = lambda x : (-x[1],x[0]))
return [c for c,v in res][:k]
分析
这道题相信大家第一时间都能想到将单词列表转化为哈希计数表,但困难的就是如何针对该哈希表进行排序。
这里介绍两种方法,各有所长,供大家参考:
如果大家之前做过 179.最大数
那么相信你一定对cmp_to_key这个方法不会陌生,它对于Python3提供了sorted过程中的两两比较功能。
我们只需要单独创建一个函数,针对字典K进行函数式排序即可。
如果我们不了解cmp_to_key,那么这道题怎么解?老老实实用嵌套列表。
我们将Counter统计好的字典,按照k,v的形式添加进一个列表,然后在对列表排序的时候,对数字与字符串进行分别排序即可。
from collections import Counter
from functools import cmp_to_key
class Solution:
def topKFrequent(self, words, k):
d = Counter(words)
def cmp(a, b):
if d[a] > d[b]:
return -1
elif d[a] == d[b]:
return -1 if sorted([a, b])[0] == a else 1
else:
return 1
return sorted(d.keys(), key=cmp_to_key(cmp))[:k]
class Solution:
def topKFrequent(self, words: List[str], k: int) -> List[str]:
frequency = collections.Counter(words)
# 大顶堆:最小的在下面,最大的在上面,弹出k个
queue = [(-freq, word)for word,freq in frequency.items()]
heapq.heapify(queue)
return [heapq.heappop(queue)[1] for _ in range(k)]
315. 计算右侧小于当前元素的个数
给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
示例:
输入: [5,2,6,1]
输出: [2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1).
2 的右侧仅有 1 个更小的元素 (1).
6 的右侧有 1 个更小的元素 (1).
1 的右侧有 0 个更小的元素.
第一种思路:
双重循环暴力解。
会超时。
时间复杂度:O(N^2)
空间复杂度:O(1)
class Solution(object):
def countSmaller(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
counts = [0 for _ in range(len(nums))]
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] > nums[j]:
counts[i] += 1
return counts
’
二分查找。
如果我们从左向右处理nums,则因为每个数的右边都是未知的,所以非常麻烦不好弄,
但如果我们从右往左处理nums,在处理每个数的时候,其右边的所有数我们都已经处理过,所以都已知,
因此可以维护一个processed_num的有序数组,里面存放了所有的从右往左已经见过的数,但是是有序的形式,
这样我们只要使用二分查找找到当前数,在processed_num对应的插入位置,就可以知道其右边有多少个数比它的值要小。
时间复杂度:O(N (logN + N)) = O(N^2)
空间复杂度:O(N)
非常神奇的一点在于,虽然这个算法也是平方级的时间复杂度,但它跑的比很多O(NlogN)的算法还要快……
原因据说是因为bisect这个库是用 c 实现的,所以跑起来飞快……
class Solution(object):
def countSmaller(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
# 从左往右处理,右边是未知的,所以不好弄
# 从右往左处理,则对于每个数,其右边的数都已知
processed_num = []
res = []
for num in nums[::-1]:
idx = bisect.bisect_left(processed_num, num)
res.append(idx)
processed_num.insert(idx, num)
return res[::-1]
’
第三种思路:
BST,
我们可以构建并维护一种特殊的二叉搜索树,
除了常规的left, right 和 val之外,再额外多一项属性 left_subtree_cnt 代表当前节点的左子树的节点总数。
这样对于二叉搜索树的每个节点,读它的 left_subtree_cnt 就可以很快知道有多少个数比它要小。
时间复杂度:O(NlogN)
空间复杂度:O(N)
class TreeNode(object):
def __init__(self, val):
self.left = None
self.right = None
self.val = val
self.left_subtree_cnt = 0
class Solution(object):
def countSmaller(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
# 从左往右处理,右边是未知的,所以不好弄
# 从右往左处理,则对于每个数,其右边的数都已知
res = [0 for _ in nums]
root = None
for i, num in enumerate(nums[::-1]):
root = self.insert(root, num, i, res)
return res[::-1]
def insert(self, root, val, i, res):
if not root: #如果当前root为空
root = TreeNode(val)
elif root.val >= val: # 如果应该插入左子树
root.left_subtree_cnt += 1
root.left = self.insert(root.left, val, i, res)
elif root.val < val: # 如果应该插入右子树
res[i] += root.left_subtree_cnt + 1 # 1 代表当前root
root.right = self.insert(root.right, val, i, res)
return root
166. 分数到小数
给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以字符串形式返回小数。
如果小数部分为循环小数,则将循环的部分括在括号内。
示例 1:
输入: numerator = 1, denominator = 2
输出: “0.5”
示例 2:
输入: numerator = 2, denominator = 1
输出: “2”
示例 3:
输入: numerator = 2, denominator = 3
输出: “0.(6)”
使用divmod获取商和余数. 然后通过余数求小数.
不断将余数*10, 再次调用divmod, 则算出来的商就是小数.
如果重复出现小数, 则说明为循环.
python divmod() 函数把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a // b, a % b)。
class Solution:
def fractionToDecimal(self, numerator: int, denominator: int) -> str:
# 求整数
n, remainder = divmod(abs(numerator), abs(denominator))
sign = '-' if numerator*denominator < 0 else ''
fraction = [sign+str(n)]
if remainder == 0:
return ''.join(fraction)
fraction.append('.')
dic = {}
# 求小数
while remainder != 0:
# 出现过, 则说明进入循环.
if remainder in dic:
fraction.insert(dic[remainder], '(')
fraction.append(')')
break
dic[remainder] = len(fraction)
n, remainder = divmod(remainder*10, abs(denominator))
fraction.append(str(n))
return ''.join(fraction)
238. 除自身以外数组的乘积
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
示例:
输入: [1,2,3,4]
输出: [24,12,8,6]
1
2
提示: 题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)
解题思路
思路:左右乘积列表
先看题目的提示,保证数组中任意元素的全部前缀后缀(甚至整个数组)的乘积都在 32 位整数范围内,那这里就可以不考虑数据溢出的问题。
再看说明,这个说明中表示,不能够使用除法。因为,如果要求除当前元素数组的乘积,只要先求得整个数组的乘积,除以当前元素,那么就是要求的答案。(当然,这里有个问题,如果当前元素是 0 的话,这里就要注意。不过题目不建议往这个方向考虑解决的方法,那么这里就不展开去说明了。)
现在看本篇幅使用的方法:左右乘积列表,这里需要先构造 left,right 两个数组,分别存储当前元素左侧的乘积以及右侧的乘积。
具体的构造方法:
初始化两个数组 left,right。其中 left[i] 表示 i 左侧全部元素的乘积。right[i] 表示 i 右侧全部元素的乘积。
开始填充数组。这里需要注意 left[0] 和 right[lenght-1] 的值(length 表示数组长度,right 数组是从右侧往左进行填充)。
对于 left 数组而言,left[0] 表示原数组索引为 0 的元素左侧的所有元素乘积,这里原数组第一位元素左侧是没有其他元素的,所以这里初始化为 1。而其他的元素则为:left[i] = left[i-1] * nums[i - 1]
同样的 right 数组,right[length-1] 表示原数组最末尾的元素右侧所有元素的乘积。但因为最末尾右侧没有其他元素,所以这里 right[length-1] 也初始化为 1。其他的元素则为:right[i]=right[i+1]*nums[i+1]
至于返回数组 output 数组,再次遍历原数组,索引为 i 的值则为:output[i] = left[i] * right[i]
主要在于 left 和 right 数组的构造
具体的实现代码见【code 1】 部分。
上面的方法实现之后,时间复杂度为 O(N),空间复杂度也是 O(N) (N 表示数组的长度)。因为 构造 left 和 right 数组,两者的数组长度就是 N。
题目中的进阶部分,希望能够尝试使用常数空间复杂度完成本题。(这里不计输出数组的空间)
方法还是使用 左右乘积列表 的方法,但是这里不在单独构建 left 和 right 数组。直接在输出数组中进行构造。
具体的思路:
先将输出数组当成 left 数组进行构造
然后动态构造 right 数组计算结果
具体的做法:
先初始化 output 数组,先当成 left 数组进行构造,那么 output[i] 就表示 i 左侧所有元素的乘积。(具体的构造方法同上)
但是,这里我们不能够再单独构造 right 数组(前面说了,空间复杂度须为常数)。这里我们使用的方法是,在遍历输出答案的时候,维护一个变量 right,而变量 right 表示右侧元素的乘积。遍历更新 output[i] 的值为 output[i] * right,同时更新 right 的值为 right * nums[i] 表示遍历下一个元素右侧的元素乘积。
具体实现代码见【code 2】。
# code 1
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
length = len(nums)
left = [0] * length
right = [0] * length
output = [0] * length
# 先填充 left 数组
# left 数组表示遍历时 i 左侧的乘积
# 因为数组第一个元素左侧没有其他元素,所以 left 数组第一个元素为 1
# left 数组接下来的元素则为原数组的第一位元素与 left 数组第一位元素的乘积,依次类推
left[0] = 1
for i in range(1, length):
left[i] = nums[i-1] * left[i-1]
# 同样的 right 数组从右往左进行填充
# 同样数组末尾元素右侧没有其他元素,所以末尾元素值为 1
# 右边往左的元素则为原数组与 right 数组末尾往前一位元素的乘积,依次类推
right[length-1] = 1
for i in range(length-2, -1, -1):
right[i] = nums[i+1] * right[i+1]
# 重新遍历,输出 output 数组
# output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积
# 也就是 output[i] 的值为 left[i] * right[i]
for i in range(length):
output[i] = left[i] * right[i]
return output
# code 2
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
length = len(nums)
output = [0] * length
# 先构建 output 为左侧乘积列表
# 同样初始化第一个元素为 1
output[0] = 1
for i in range(1, length):
output[i] = output[i-1] * nums[i-1]
# 维护一个变量 right
# 变量更新 output[i] 的值为 output[i] * right
# 同时更新 right = right * nums[i] 表示遍历到下个元素右侧的乘积(此时遍历从右向左)
# 初始化 right 为 1
right = 1
for i in range(length-1, -1, -1):
output[i] = output[i] * right
right = right * nums[i]
return output
306. 累加数
描述:累加数 是一个字符串,组成它的数字可以形成累加序列。
一个有效的 累加序列 必须 至少 包含 3 个数。除了最开始的两个数以外,字符串中的其他数都等于它之前两个数相加的和。
给你一个只包含数字 ‘0’-‘9’ 的字符串,编写一个算法来判断给定输入是否是 累加数 。如果是,返回 true ;否则,返回 false 。
说明:累加序列里的数 不会 以 0 开头,所以不会出现 1, 2, 03 或者 1, 02, 3 的情况。
示例1
输入:“112358”
输出:true
解释:累加序列为: 1, 1, 2, 3, 5, 8 。1 + 1 = 2, 1 + 2 = 3, 2 + 3 = 5, 3 + 5 = 8
示例2
输入:“199100199”
输出:true
解释:累加序列为: 1, 99, 100, 199。1 + 99 = 100, 99 + 100 = 199
提示
1 <= num.length <= 35
num 仅由数字(0 - 9)组成
枚举所有可能出现的情况,按照满足题意的方式去组合字符串,最终判断是否能够组合出和原字符串相同的字符串,如果出现则返回True。
class Solution:
def isAdditiveNumber(self, num: str) -> bool:
n = len(num)
for i in range(1, n // 2 + 1):
for j in range(i + 1, n // 3 * 2 + 1):
a = int(num[:i])
b = int(num[i:j])
# 数值转字符串拼接
res = f'{a}{b}'
while len(res) < n:
a, b, res = b, a + b, res + str(a + b)
if res == num:
return True
return False
133. 克隆图
给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。
图中的每个节点都包含它的值 val(int) 和其邻居的列表(list[Node])。
class Node {
public int val;
public List<Node> neighbo rs;
}
测试用例格式:
简单起见,每个节点的值都和它的索引相同。例如,第一个节点值为 1(val = 1),第二个节点值为 2(val = 2),以此类推。该图在测试用例中使用邻接列表表示。
邻接列表 是用于表示有限图的无序列表的集合。每个列表都描述了图中节点的邻居集。
给定节点将始终是图中的第一个节点(值为 1)。你必须将 给定节点的拷贝 作为对克隆图的引用返回。
输入:adjList = [[2,4],[1,3],[2,4],[1,3]]
输出:[[2,4],[1,3],[2,4],[1,3]]
解释:
图中有 4 个节点。
节点 1 的值是 1,它有两个邻居:节点 2 和 4 。
节点 2 的值是 2,它有两个邻居:节点 1 和 3 。
节点 3 的值是 3,它有两个邻居:节点 2 和 4 。
节点 4 的值是 4,它有两个邻居:节点 1 和 3 。
提示:
节点数不超过 100 。
每个节点值 Node.val 都是唯一的,1 <= Node.val <= 100。
无向图是一个简单图,这意味着图中没有重复的边,也没有自环。
由于图是无向的,如果节点 p 是节点 q 的邻居,那么节点 q 也必须是节点 p 的邻居。
图是连通图,你可以从给定节点访问到所有节点。
思路:DFS、BFS
在这道题中,题目要求的是图的深拷贝。题目所述的图的深拷贝,其实就是要构建与原图结构,值均一样的图,但是 其中的节点不能再是原图的引用。
题目开头说了,给定的是一个节点的引用。但后面的提示也提及,图是连通的,可以从给定的节点中去访问所有节点。那么我们在进行访问搜索的时候,完成图的深拷贝。
深度优先搜索(DFS)
这里先说下需要注意的地方,因为题目中明确说了,图是无向图,图中的边是无向边。例如示例 4:
这里节点 1 和节点 2 存在无向边,也就是说节点 1 可以到节点 2,而节点 2 页可以到 节点 1。所以我们遍历搜索的时候要注意标记,否则的话容易陷入死循环。
下面是具体算法:
前面说在遍历访问的时候进行标记,这里我们借助哈希表来已经被访问和克隆的节点。其中键表示的是原图的节点,而值表示的是克隆图中对应的节点;
从给定的节点开始向下搜索,如果节点存在于哈希表中,那么直接返回哈希表中的对应的节点;
如果节点并没有被标记,那么创建克隆节点,存储到哈希表中;
递归调用每个节点的邻接点,将结果放到克隆邻接点列表中。
具体的代码见【代码实现 # 深度优先搜索】
广度优先搜索(BFS)
使用广度优先搜索,这里同样需要注意无向边的问题。在这里,同样使用哈希表来存储已被访问原图的节点以及对应克隆节点。下面是具体的算法:
使用哈希表来存储已被访问原图的节点以及对应克隆节点;
克隆给定的节点,存储到哈希表中。同时借助辅助队列,先将给定的节点放到队列。
出队,访问该节点的所有邻接点。如果节点不在哈希表中,那么克隆当前节点的邻接点存入哈希表中。同时将此邻接点入队,并将此邻接点放到克隆图中对应节点的邻接表中。
重复直至队列为空,表明图遍历结束。
# 深度优先搜索
"""
# Definition for a Node.
class Node:
def __init__(self, val = 0, neighbors = []):
self.val = val
self.neighbors = neighbors
"""
class Solution:
def cloneGraph(self, node: 'Node') -> 'Node':
marked = {}
def dfs(node):
if not node:
return node
# 如果存在于哈希表中,直接返回哈希表存储的值
if node in marked:
return marked[node]
# 不存在哈希表中,那么克隆节点,将其放入哈希表中
clone_node = Node(node.val, [])
marked[node] = clone_node
# 遍历节点的邻接点,相邻接点放到邻接列表中
for neighbor in node.neighbors:
clone_node.neighbors.append(dfs(neighbor))
return clone_node
return dfs(node)
# 广度优先搜索
"""
# Definition for a Node.
class Node:
def __init__(self, val = 0, neighbors = []):
self.val = val
self.neighbors = neighbors
"""
class Solution:
def cloneGraph(self, node: 'Node') -> 'Node':
from collections import deque
marked = {}
def bfs(node):
if not node:
return node
# 克隆节点,放到哈希表中
clone_node = Node(node.val, [])
marked[node] = clone_node
# 先将给定的节点入队
queue = deque()
queue.append(node)
# 出队,开始遍历
while queue:
cur_node = queue.popleft()
for neighbor in cur_node.neighbors:
# 如果邻接点不在哈希表中,克隆邻接点存入哈希表中,并将邻接点入队
if neighbor not in marked:
marked[neighbor] = Node(neighbor.val, [])
queue.append(neighbor)
# 更新当前节点的邻接列表,注意是克隆节点
marked[cur_node].neighbors.append(marked[neighbor])
return clone_node
return bfs(node)
118. 杨辉三角
解法1:
【解析】
① 首先初始化了一个列表“ret”,用来存放所有行的数字。
② 每一行的数字也以列表的形式存储,每一行的列表都初始化为row。
③ “i”表示某一行,即第1行, 第2行等等。j 表示特定行的某一个数字。
④ 根据性质①可知,杨辉三角每一行的开始和结尾都是1, 故当
j == 0:表示该行的第一个数字
j == i :表示该行的最后一个数字
时候,直接给列表加1即可。
⑤ 除去每一行的首位元素末位元素直接为1外,其余所有元素根据性质③中的公式来计算即可。ret[i-1] : 定位到存放上一行所有数字的列表;ret[i - 1][j]:定位到第 i-1 行 的 第 j 个元素。同理ret[i - 1][j-1]
class Solution:
def generate(self, numRows: int) -> List[List[int]]:
ret = list()
for i in range(numRows):
row = list()
for j in range(0, i + 1):
if j == 0 or j == i:
row.append(1)
else:
row.append(ret[i - 1][j] + ret[i - 1][j - 1])
ret.append(row)
return ret
解法2:
该解法来自Leecode本题的热门题解,十分讨巧所所以也值得一写!如果说法1是老老实实的解法那么法2就是一种十分讨巧的方法,话不多说直接上图!
简单描述一下:每行 =该行的上一行 + 该行上一行向后移动一位。是不是非常神奇呢!高手在民间哈哈哈!在代码层的实现只需要在空余位置补0就可以啦,也就是在每行的首位和末位补0。上图中给补0的位置都标红了。
class Solution:
def generate(self, numRows: int) -> List[List[int]]:
if numRows == 0:
return []
res = [[1]]
while len(res) < numRows:
newline = [a+b for a,b in zip(res[-1]+[0],[0]+res[-1])]
res.append(newline)
return res
986. 区间列表的交集
给定两个由一些 闭区间 组成的列表,firstList 和 secondList ,其中 firstList[i] = [starti, endi] 而 secondList[j] = [startj, endj] 。每个区间列表都是成对 不相交 的,并且 已经排序 。
返回这 两个区间列表的交集 。
形式上,闭区间 [a, b](其中 a <= b)表示实数 x 的集合,而 a <= x <= b 。
两个闭区间的 交集 是一组实数,要么为空集,要么为闭区间。例如,[1, 3] 和 [2, 4] 的交集为 [2, 3] 。
输入:firstList = [[0,2],[5,10],[13,23],[24,25]], secondList = [[1,5],[8,12],[15,24],[25,26]]
输出:[[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]
示例 2:
输入:firstList = [[1,3],[5,9]], secondList = []
输出:[]
示例 3:
输入:firstList = [], secondList = [[4,8],[10,12]]
输出:[]
示例 4:
输入:firstList = [[1,7]], secondList = [[3,10]]
输出:[[3,7]]
思路:
先从最简单的两个区间的交集开始考虑,
假设我们有 L1 = [s1, e1], L2 = [s2, e2],
那么它们的交集就应该是 [max(s1, s2), min(e1, e2)]。
下一步考虑对两个区间取了交集之后,到底谁该往后挪一步找下一个区间,
贪心告诉我们,应该移动 e 较小的那个 List, 因为长的 List 还有可能跟其他的 List 取到交集。
时间复杂度:O(N)
空间复杂度:O(1)
class Solution:
def intervalIntersection(self, firstList: List[List[int]], secondList: List[List[int]]) -> List[List[int]]:
p1, p2 = 0, 0
res = []
while p1 < len(firstList) and p2 < len(secondList):
interscetion = [max(firstList[p1][0], secondList[p2][0]), min(firstList[p1][1], secondList[p2][1])]
if interscetion[0] <= interscetion[1]:
res.append(interscetion)
if firstList[p1][1] <= secondList[p2][1]:
p1 += 1
else:
p2 += 1
return res
233. 数字 1 的个数
给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。
示例1:
输入:n = 13
输出:6
示例2:
输入:n = 0
输出:0
提示:
0 <= n <= 2 * 109
思路
动态规划
我首先想到的是枚举,但是毫无疑问超时了,数位dp看的是题解写的。
枚举
思路:dp[i]表示1~i里已经出现的1的总数,状态方程:dp[i] = dp[i - 1] + 这个数中出现的1的次数
class Solution {
public:
int cacul_1(int num) {
int ans = 0;
while(num) {
ans += num % 10 == 1 ? 1 : 0;
}
return ans;
}
int countDigitOne(int n) {
vector<int>dp(n + 1,0);
for(int i = 1; i <= n; i++) {
dp[i] = dp[i - 1] + cacul_1(i);
}
return dp[n];
}
};
数位dp
这个思路是考虑每一个数位的情况,分别有0,1,其它三种情况。假设有220,则个位出现1的数有:1,11,21…211一共22个;如果是221,则有1,11,21…211 + 221共23个;如果是223则也是23个
解题
class Solution:
def digitOneInNumber(self, num: int) -> int:
digit, res = 1, 0
high, cur, low = num // 10, num % 10, 0
while high != 0 or cur != 0:
if cur == 0: res += high * digit
elif cur == 1: res += high * digit + low + 1
else: res += (high + 1) * digit
low += cur * digit
cur = high % 10
high //= 10
digit *= 10
return res
907. 子数组的最小值之和
给定一个整数数组 arr,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组。
由于答案可能很大,因此 返回答案模 10^9 + 7 。
示例
示例 1:
输入:arr = [3,1,2,4]
输出:17
解释: 子数组为
[3],[1],[2],[4],[3,1],[1,2],[2,4],[3,1,2],[1,2,4],[3,1,2,4]。 最小值为 3,1,2,4,1,1,2,1,1,1,和为 17。
示例 2:
输入:arr = [11,81,94,43,3]
输出:444
提示:
1 <= arr.length <= 3 * 104
1 <= arr[i] <= 3 * 104
思路
题目简而言之就是求给定数组的子数组的所有子数组中最小元素的和。
该问题我们可以转换为单调栈的问题,
我们需要找到每个元素 arr[i] 以该元素为最右且最小的子序列的数目 left[i],以及以该元素为最左且最小的子序列的数目 right[i],则以 arr[i] 为最小元素的子序列的数目合计为 left[i]×right[i]
只需要求出数组中当前元素 x(下标i) 左边第一个小于 x 的元素(下标j)以及右边第一个小于等于 x 的元素(下标k)
则以为arr[i]为最小元素对答案的贡献就是arr[i] * (i-j) * (k-i),最后整合所有元素即可
MOD = 10 ** 9 + 7
class Solution:
def sumSubarrayMins(self, arr: List[int]) -> int:
n = len(arr)
monoStack = []
# arr[i]左右两边小于该元素的元素数量和
left = [0] * n
right = [0] * n
# 寻找左边
for i, x in enumerate(arr):
while monoStack and x <= arr[monoStack[-1]]:
monoStack.pop()
left[i] = i - (monoStack[-1] if monoStack else -1)
monoStack.append(i)
monoStack = []
# 寻找右边
for i in range(n - 1, -1, -1):
while monoStack and arr[i] < arr[monoStack[-1]]:
monoStack.pop()
right[i] = (monoStack[-1] if monoStack else n) - i
monoStack.append(i)
ans = 0
# 整合答案
for l, r, x in zip(left, right, arr):
ans = (ans + l * r * x) % MOD
return ans