2021-07-14
75、从前序与中序遍历序列构造二叉树
递归,注意有列表为空的情况
# 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 buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if not preorder:
return None
root = TreeNode(preorder[0])
# 均无重复元素
idx = inorder.index(root.val)
root.left = self.buildTree(preorder[1:idx+1],inorder[:idx])
root.right = self.buildTree(preorder[idx+1:],inorder[idx+1:])
return root
76、二叉树展开为链表
给你二叉树的根结点 root ,请你将它展开为一个单链表:展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同。
注意最后的循环连接结点方法。
# 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 flatten(self, root: TreeNode) -> None:
"""
Do not return anything, modify root in-place instead.
"""
def preorder(root):
if not root:
return []
return [root] + preorder(root.left) + preorder(root.right)
l = preorder(root)
for i in range(1,len(l)):
pre,cur = l[i-1], l[i]
pre.left = None
pre.right = cur
77、买股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
此题的股票只能买卖一次。即找整个数组的最大差值。暴力法时间复杂度高,需要两次遍历。用一次遍历,可记录历史最小值,然后每次和历史最小值做差。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
history_min = prices[0]
res = 0
for i in range(1,len(prices)):
res = max(res,prices[i]-history_min)
history_min = min(prices[i],history_min)
return res
78、二叉树中的最大路径和
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。路径和 是路径中各节点值的总和。给你一个二叉树的根节点 root ,返回其 最大路径和 。
对每一个结点来讲,他的左右结点都对他有贡献度。若以该结点为根节点子树是最大路径,那么要计算本结点值和左右节点值的和;若不以该结点为根节点,那么此时这个结点应该是作为别人的子节点,那么只能算一边的和。注意,只有贡献度大于0才对此结点有意义。
# 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 maxPathSum(self, root: TreeNode) -> int:
# 以某一结点为根节点的最大路径,那么此时最大路径和为root.val + root.left.val + root.right.val
# 若不以该结点为根节点时,包含该结点的最大路径和为max((root.val+root.left.val, root.val+root.right.val)
self.max_path = float('-inf')
def max_sonpath(root):
if not root:
return 0
# 只有贡献度大于0才有意义
left_path = max(0,max_sonpath(root.left))
right_path = max(0,max_sonpath(root.right))
# 不以该结点为根节点时,只能算一边
max_sp = max(root.val+left_path,root.val+right_path)
# 计算以该结点为根节点时的和
self.max_path = max(self.max_path,root.val + left_path + right_path)
return max_sp
max_sonpath(root)
return self.max_path
2021-07-16
79、单词拆分
给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。说明:拆分时可以重复使用字典中的单词。你可以假设字典中没有重复的单词。
动态规划:当前i个字符是字典中的值时,如果[i:j]也是字典中的值,那么前j个字符就可以拆分。
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
n = len(s)
# dp[i]表示前i个字符的字符串是否存在于字典中。
dp = [False] * (n+1)
# 初始化
dp[0] = True
# 状态转移
for i in range(n):
for j in range(i+1,n+1):
if dp[i] and s[i:j] in wordDict:
dp[j] = True
return dp[-1]
80、环形链表2
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。说明:不允许修改给定的链表。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
s = set()
while head:
if head not in s:
s.add(head)
head = head.next
else:
return head
return None
81、最长连续序列
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
首先要求时间复杂度为O(N),所以不能暴力先排序然后再筛选。肯定是外部遍历一次数组,内层循环也不能为O(N),所以使用哈希结构来搜索。
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
# 举两个特殊例子来理解:
#[4,3,2,1]/[1,2,3,4]
s = set(nums)
res = 0
for i in s:
# 当比i小的数没有时,说明可以以i开头计算数量而不会重复
# 当比i小的数存在时,说明要不 以这个小数为开头的序列已经算过了,要不 等待之后的这个更小的为开头时再算。
if i-1 not in s:
cur = 1
while i+1 in s:
i += 1
cur += 1
res = max(cur,res)
return res
82、ab串
小明得到一个只包含a,b两个字符的字符串,但是小明不希望在这个字符串里a出现在b左边。现在他可以将”ab”这样的子串替换成”bba”,在原串中的相对位置不变。输出小明最少需要操作多少次才能让一个给定字符串所有a都在b的右边。
举一些特殊的例子来找到规律。比如:abbb,对a后面的每个b,每次ab变换一次,a可以往后移动一个位置,所以从后往前遇到b时,次数应该加一。再比如:aabbb,第二个a先行变到最后之后,此时b的数量比原来多了一倍,所以第一个a再变到最后的话,得往后移2b次。
mod = 100000007
def main(s):
num_b,ans = 0,0
l = len(s)
for i in range(l-1,-1,-1):
if s[i] == "a":
ans = ans+num_b
num_b = 2*num_b
else:
num_b = num_b+1
return ans%mod
s = "abab"
print(main(s))
83、回文数变换
所谓回文数就是一个数字,从左边读和从右边读的结果都是一样的,例如12321。现在有一个只包含1、2、3、4的数字,你可以通过在任意位置增加一位数字或者删除一位数字来将其变换成一个回文数。但是增加或删除不同数字所需要的代价是不一样的。已知增加和删除每个数字的代价如下:
增加一个1,代价:100;删除一个1,代价:120。
增加一个2,代价:200;删除一个2,代价:350。
增加一个3,代价:360;删除一个3,代价:200。
增加一个4,代价:220;删除一个4,代价:320
请问如何通过最少的代价将一个数字变换为一个回文数。当然,如果一个数字本身已经是一个回文数(包括一位数,例如:2),那么变换的代价为0。
这个题和“编辑距离”有点像,编辑距离是a变化多少步可以变为b,这个题是字符串变化多少代价可以变为回文数。动态规划可以很好滴解决这类型问题。首先需要对状态进行定义和状态转移进行推导。若s[i:j]字符串变为回文数的最小代价为dp[i][j],那么当s[i-1]和s[j+1]相同时,增加这两个边界的值后的dp[i-1][j+1]与dp[i][j]是相同的。当s[i-1]和s[j+1]不同时,s[i-1:j+1]变为回文数的情况可以先由s[i-1:j]变为回文串的最小代价加上s[j+1]后的最小代价,也可以由s[i:j+1]来计算。即dp[i][j] = min(dp[i-1][j] + cost(i), dp[i][j-1]+cost(j))。
def price(nums):
add_cost = [0,100,200,360,220]
del_cost = [0,120,350,200,320]
# 定义状态
n = len(nums)
# dp[i][j]表示i到j可以构成回文字符的最小代价
dp = [[0]*n for _ in range(n)]
# 状态初始化
# 因为要用到dp[i+1][j],所以需要提前知道i+1处的情况才能推出i处的情况,所以需要从后往前遍历i
for i in range(n-1,-1,-1):
for j in range(i+1,n):
if s[i] == s[j]:
dp[i][j] = dp[i+1][j-1]
else:
temp1 = min(add_cost[int(s[i])],del_cost[int(s[i])]) + dp[i+1][j]
temp2 = min(add_cost[int(s[j])],del_cost[int(s[j])]) + dp[i][j-1]
dp[i][j] = min(temp1,temp2)
return dp[0][-1]
s = "12321"
print(price(s))
2021-07-19
昨天面试了字节,字节的面试官特别好,就是再和你探讨,向你了解你的项目,循序渐进地引导你去答,不像有的公司的面试官,问你几个问题,然后你答错了答对了都不知道,一场面试下来就很冷漠哈哈哈哈。但是昨天字节的两道做过的题都没做出来。还是练的太少了,一到关键时候就写出不来了。以后多多刷题练习!
84、排序链表
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。进阶:你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?
方法一:递归,思路如下,就是自己写的时候写不出来
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def sortList(self, head: ListNode) -> ListNode:
# 终止条件
if not head or not head.next:
return head
# 切分链表
slow, fast = head, head.next
while fast and fast.next:
fast = fast.next.next
slow = slow.next
# 后面链表的起点
mid = slow.next
# 断开前面链表和后面链表
slow.next = None
# 递归
# 得到当前切分的两个开头
left, right = self.sortList(head), self.sortList(mid)
# 对两段链表进行排序
pre = ListNode(-1)
cur = pre
while left and right:
if left.val <= right.val:
cur.next = left
left = left.next
cur = cur.next
else:
cur.next = right
right = right.next
cur = cur.next
if not left:
cur.next = right
else:
cur.next = left
return pre.next
更有助于写出代码的解释
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def sortList(self, head: ListNode) -> ListNode:
# 递归三要素
# 1、明确这个函数要返回什么?
# 根据分析,这个函数要返回一个已经排好序的链表,由dummy结点的下一个结点引出:
# 所以要设置pre,以及初始化pre的下一个结点,这个再后面
# pre = ListNode(-1)
# pre.next = 最小的数
# 2、寻找递归结束的条件
if not head or not head.next:
return head
# 3、将问题域缩小
# 这里使用分治的思想,将链表一直二分
# 找到当前链表的中点,一分为二
slow = head
fast = head.next
while fast and fast.next:
fast = fast.next.next
slow = slow.next
mid = slow.next
slow.next = None
# 利用本函数的性质,也就是递归三要素第一点,说明以left_begin开头的数组和以right_begin开头的数组已经排好
left_begin = self.sortList(head)
right_begin = self.sortList(mid)
# 排好之后就是要对这“两个有序数组进行合并”了
pre = ListNode(-1)
cur = pre
while left_begin and right_begin:
if left_begin.val >= right_begin.val:
cur.next = right_begin
right_begin = right_begin.next
else:
cur.next = left_begin
left_begin = left_begin.next
cur = cur.next
if not left_begin:
cur.next = right_begin
if not right_begin:
cur.next = left_begin
return pre.next
85、排序数组
题解:python用快排的话,若不随机选取主元素,则遇到顺序的序列会超时。所以要随机选取主元素。
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
def quicksort(nums,start,end):
# 1、明确递归的返回值
# 将start到end排好序
# 2、明确递归的终止条件
if start >= end:
return
# 3、缩小问题域
left = start
right = end
# 随机选取主元素
index = random.randint(start, end)
# 将主元素弄到左边
mid = nums[index]
nums[left],nums[index] = mid, nums[left]
while left < right:
while left < right and nums[right] >= mid:
right -= 1
nums[left] = nums[right]
while left < right and nums[left] < mid:
left += 1
nums[right] = nums[left]
nums[left] = mid
quicksort(nums,start,left)
quicksort(nums,left+1,end)
quicksort(nums,0,len(nums)-1)
return nums
2021-07-20
86、乘积最大子数组
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
动态规划:状态表示为以i为结尾的最大连续子数组的乘积。那么i处的最大乘积有三种可能:第一、是他本身。2、当nums[i]为正数时,有可能是i-1处的最大乘积乘以nums[i]。3、当nums[i]为负数时,有可能是i-1处的最小乘积乘以nums[i]。总得来说,得考虑负负得正的情况。所以需要额外维护一个最小值的状态。
class Solution:
def maxProduct(self, nums: List[int]) -> int:
# 状态定义
# dp_max[i]表示以第i个位置结尾的连续数组的最大乘积
# dp_min[i]表示以第i个位置结尾的连续数组的最小乘积
n = len(nums)
dp_max = [0] * n
dp_min = [0] * n
# 状态初始化
dp_max[0] = nums[0]
dp_min[0] = nums[0]
# 状态转移
for i in range(1,n):
dp_max[i] = max(nums[i],max(dp_min[i-1]*nums[i],dp_max[i-1]*nums[i]))
dp_min[i] = min(nums[i],min(dp_max[i-1]*nums[i],dp_min[i-1]*nums[i]))
return max(dp_max)
87、最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和
动态规划:由于和不存在负负得正的情况,都是直接累加的,所以状态转移就是直接上一个状态与当前转状态是否累和对比。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# 动态规划
# 状态定义
# dp表示以当前位置为结尾的最大和的连续子数组的和
dp = [0] * len(nums)
# 状态初始化
dp[0] = nums[0]
# 状态转移
for i in range(1,len(nums)):
dp[i] = max(nums[i],nums[i]+dp[i-1])
return max(dp)
88、多数元素
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。你可以假设数组是非空的,并且给定的数组总是存在多数元素。进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
方法一:哈希表(O(N),O(N))
class Solution:
def majorityElement(self, nums: List[int]) -> int:
n = len(nums)
dic = collections.defaultdict(int)
for i in nums:
dic[i] += 1
if dic[i] > n // 2:
return i
方法二:摩尔投票法(O(N),O(1))
class Solution:
def majorityElement(self, nums: List[int]) -> int:
# 既然多数元素的数量是超过一半的,那么每个多数元素与其他不同的元素抵消后肯定还会有剩余
# 基准对比数
target = nums[0]
# 基准对比数的有效数量
count = 0
for i in nums:
if i == target:
count += 1
else:
count -= 1
if count < 0:
target = i
count = 0
return target
2021-07-25
89、打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
经典动态规划:
class Solution:
def rob(self, nums: List[int]) -> int:
# 状态定义
dp = [0] * len(nums)
# 状态初始化
dp[0] = nums[0]
# 状态转移
for i in range(1,len(nums)):
dp[i] = max(dp[i-1],dp[i-2]+nums[i])
return max(dp)
2021-07-27
90、岛屿数量
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。
经典dfs:把遍历过的1都置为0
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
n = len(grid)
m = len(grid[0])
directions = [(0,1),(0,-1),(1,0),(-1,0)]
def dfs(i,j):
if grid[i][j] != '1':
return
grid[i][j] = 0
for x,y in directions:
new_i = i + x
new_j = j + y
if 0 <= new_i < n and 0 <= new_j < m:
dfs(new_i,new_j)
res = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == '1':
dfs(i,j)
res += 1
return res
91、课程表
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
BFS,需要先建立课程的对应关系,以及修每个课程前所需要修的课程数。建立一个队列,将不需要先修课程的课程号加入其中。然后开始修这门课,修完后,其对应的其他课的先修数量应该-1,若某一门课在这一过程中所需的先修课程变为0了,说明可以修了,那么把它加入队列中。在这个过程中需要设置一个变量来标记可学习的课程数量。
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
if not prerequisites:
return True
nums_need = [0] * numCourses
dic = collections.defaultdict(list)
# 找到对应关系和初始化的课程
for i,j in prerequisites:
dic[j].append(i)
nums_need[i] += 1
q = []
for i in range(len(nums_need)):
if nums_need[i] == 0:
q.append(i)
# 开始学习
visited = 0
while q:
visited += 1
cur = q.pop()
for i in dic[cur]:
nums_need[i] -= 1
if nums_need[i] == 0:
q.append(i)
return visited == numCourses
2021-07-28
92、实现前缀树
Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。请你实现 Trie 类:
- Trie() 初始化前缀树对象。
- void insert(String word) 向前缀树中插入字符串 word 。
- boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
- boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
遍历到每一层时都用一个字典表示当前节点的子节点,然后遍历时,每次要注意替换成当前节点。
class Trie:
def __init__(self):
"""
Initialize your data structure here.
"""
self.dic = {}
def insert(self, word: str) -> None:
"""
Inserts a word into the trie.
"""
t = self.dic
for i in word:
if i not in t:
t[i] = {}
t = t[i]
t['end'] = 0
def search(self, word: str) -> bool:
"""
Returns if the word is in the trie.
"""
t = self.dic
for i in word:
if i not in t:
return False
t = t[i]
if 'end' in t:
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.
"""
t = self.dic
for i in prefix:
if i not in t:
return False
t = t[i]
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)
93、数组中的第K个最大元素
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
方法一:python排序
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
nums.sort()
return nums[len(nums)-k]
方法二:快排
直接写快排排序然后像方法一 一样是可以的,但是速度会慢,而且多了很多重复的步骤。因为快排每次left的最终停留的位置就是当前的基准位置,所以可以通过判断这个位置和k的关系,然后选择性的排前面还是排后面。另外,对于快排选择基准,每次随机从[start,end]中选取基准要比直接选择左边运行速率快的多。
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
def quicksort(start,end):
left = start
right = end
idx = random.randint(start,end)
nums[left], nums[idx] = nums[idx], nums[left]
mid = nums[left]
while left < right:
while left < right and nums[right] >= mid:
right -= 1
nums[left] = nums[right]
while left < right and nums[left] < mid:
left += 1
nums[right] = nums[left]
nums[left] = mid
if left == len(nums)-k:
return nums[left]
elif left > len(nums)-k:
return quicksort(start,left)
else:
return quicksort(left+1,end)
return quicksort(0,len(nums)-1)
2021-07-29
94、最大正方形
在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内,找到只包含 ‘1’ 的最大正方形,并返回其面积。
动态规划,每个位置的值表示该位置的以该位置为右下角的最大边长。
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
m = len(matrix)
n = len(matrix[0])
dp = [[0] * n for _ in range(m)]
res_x = 0
for i in range(m):
for j in range(n):
if matrix[i][j] == "1":
if i == "0" or j == "0":
dp[i][j] = 1
res_x = max(res_x,dp[i][j])
else:
dp[i][j] = min(dp[i][j-1],dp[i-1][j-1],dp[i-1][j]) + 1
res_x = max(res_x,dp[i][j])
else:
dp[i][j] = 0
return res_x**2
95、二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
递归问题,想清楚这个函数是干嘛的。这个函数是为了返回两个节点的最近公共祖先的。所以问题可以缩小为分别以root的left和root的right为根节点判断其公共祖先。如果两个节点不在同一面,说明返回为None。如果在同一面,则继续细化下去。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root or root == p or root == q:
return root
left = self.lowestCommonAncestor(root.left,p,q)
right = self.lowestCommonAncestor(root.right,p,q)
if not left:return right
if not right:return left
return root
96、回文链表
请判断一个链表是否为回文链表。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
nums = []
while head:
nums.append(head.val)
head = head.next
l = 0
r = len(nums)-1
while l < r:
if nums[l] == nums[r]:
l+=1
r-=1
else:
return False
return True
2021-07-30
97、除自身以外数组的乘积
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
- 提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
- 说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
- 进阶:你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)
本题由于不能使用除法,所以考虑其他方法:
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
# 对于某个位置的一个数,他的除本身外的乘积,可以看作两部分,一部分是
# 该位置左侧的乘积,另一部分是该位置右侧的乘积
# 所以res可以看作两个向量各个位置元素的相乘
left = [0]*len(nums)
right = [0]*len(nums)
res = [0]*len(nums)
left[0] = 1
right[-1] = 1
for i in range(1,len(nums)):
left[i] = nums[i-1]*left[i-1]
for i in range(len(nums)-2,-1,-1):
right[i] = nums[i+1]*right[i+1]
for i in range(len(left)):
res[i] = left[i]*right[i]
return res
根据题意,这个用到了额外的空间。其实对于left和right的构建,可以直接体现在res中。
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
res = [0]*len(nums)
res[0] = 1
for i in range(1,len(nums)):
res[i] = nums[i-1]*res[i-1]
cur = 1
for i in range(len(nums)-1,-1,-1):
res[i] = cur*res[i]
cur *= nums[i]
return res
2021-08-01
98、滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
用暴力sort会超时,时间复杂度为 O((n-k+1)k)=O(nk),所以用一个数据结构:堆。堆的构建过程时间复杂度为nlogn,删除和添加为logn。
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
# 采用(值,索引),值用来构建堆,索引用来判断数字是否在窗口
# 因为python构建是最小堆,所以加一个负号
q = [(-val,i) for i,val in enumerate(nums[:k])]
# 构建堆
heapq.heapify(q)
res = [-q[0][0]]
for i in range(k,len(nums)):
# 先加入新节点
heapq.heappush(q,(-nums[i],i))
# 判断最大值是否不在窗口内
while q[0][1] <= i-k:
heapq.heappop(q)
res.append(-q[0][0])
return res