week 10
美好的一周从敲代码开始
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F6oYTEGf-1689496840099)(C:\Users\86152\AppData\Roaming\Typora\typora-user-images\image-20230712104538447.png)]
文章目录
- **week 10**
- 知识点记录:
- [39. 组合总和](https://leetcode.cn/problems/combination-sum/)
- [40. 组合总和 II](https://leetcode.cn/problems/combination-sum-ii/)
- [122. 买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/)
- [1911. 最大子序列交替和](https://leetcode.cn/problems/maximum-alternating-subsequence-sum/)
- [46. 全排列](https://leetcode.cn/problems/permutations/)
- 每日一题:
知识点记录:
位异或运算(XOR)
是一种逻辑运算符,用于比较两个二进制数的对应位,并根据以下规则计算结果:
- 如果对应位上的两个二进制数相同(都为0或都为1),则结果为0。
- 如果对应位上的两个二进制数不同(一个为0,一个为1),则结果为1。
位异或运算可以用符号 “^” 表示。
以下是一些位异或运算的示例:
a = 10 # 二进制表示为 1010
b = 6 # 二进制表示为 0110
# 位异或运算
result = a ^ b # 二进制表示为 1100,十进制为 12
print(result) # 输出结果为 12
在上述示例中,变量 a
和 b
进行位异或运算,结果为 12。
位异或运算在计算机科学中有多种应用,包括:
- 交换两个变量的值:
a ^= b; b ^= a; a ^= b;
- 判断两个数是否相等:
result = (a ^ b) == 0
- 加密和解密算法中的数据处理
- 校验和和错误检测等领域
位异或运算是一种常用的位运算,可以在处理二进制数据和逻辑运算中发挥重要作用。
可哈希计数collections.Counter
collections.Counter
是 Python 标准库中的一个内置类,用于计数可哈希对象的出现次数。它提供了一种方便的方式来统计可迭代对象中各个元素的频率。
使用 collections.Counter
类可以快速统计可迭代对象中元素的出现次数,并以字典的形式返回统计结果,其中元素作为键,出现次数作为值。该类提供了一些实用的方法,可以进行计数的合并、计数器的相减、获取最常见的元素等操作。
以下是使用 collections.Counter
的示例:
from collections import Counter
# 统计字符串中字符的出现次数
s = "hello world"
counter = Counter(s)
print(counter) # 输出:Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
# 统计列表中元素的出现次数
nums = [1, 2, 3, 1, 2, 3, 4, 5]
counter = Counter(nums)
print(counter) # 输出:Counter({1: 2, 2: 2, 3: 2, 4: 1, 5: 1})
# 访问出现次数最多的元素
most_common = counter.most_common(2)
print(most_common) # 输出:[(1, 2), (2, 2)]
在上述示例中,我们通过创建 Counter
对象并传入可迭代对象,可以快速统计元素的出现次数。通过调用 most_common()
方法,可以获取出现次数最多的元素及其对应的次数。
collections.Counter
是一个非常有用的工具,可以方便地进行频率统计和元素计数的操作,适用于许多数据处理和算法问题。
字母之间进行运算:
在 Python 中,可以使用内置的 ord()
和 chr()
函数将字母与 ASCII 码进行转换。通过将字母转换为 ASCII 码,可以进行一些运算操作。
下面是一些示例操作:
# 字母向后移动 n 个位置
char = 'A'
n = 3
new_char = chr(ord(char) + n)
print(new_char) # 输出:D
# 字母向前移动 n 个位置
char = 'D'
n = 3
new_char = chr(ord(char) - n)
print(new_char) # 输出:A
# 字母间的差值
char1 = 'E'
char2 = 'B'
diff = ord(char1) - ord(char2)
print(diff) # 输出:3
通过对字母的 ASCII 码进行加减操作,可以实现字母的移动或计算字母之间的差值。注意要确保结果仍然是合法的字母。
内置函数bisect_right
右二等分
bisect_right
是 Python 中模块的函数bisect
。它用于在排序列表中查找目标值的插入点。
以下是如何使用的示例bisect_right
:
from bisect import bisect_right
nums = [1, 3, 5, 7, 9]
target = 4
idx = bisect_right(nums, target)
print(idx) # Output: 2
在此示例中,bisect_right(nums, target)
返回应插入的索引target
以维护 list 的排序顺序nums
。由于target
是 4 并且它应该插入到列表中的值 3 之后,因此返回的索引是 2。
在代码上下文中,您可以使用bisect_right
来查找目标字母应插入到字母排序列表中的索引。
回溯算法
回溯算法是一种通过搜索所有可能的解空间来求解问题的算法。它常用于求解组合、排列、子集等问题。回溯算法通过尝试所有可能的选择,并在搜索过程中进行剪枝,从而避免不必要的搜索。
回溯算法的基本思想是递归和回退。它通过递归地尝试所有的选择,每次选择一个元素,并进入下一层进行搜索。如果搜索成功,即找到了满足问题条件的解,就将解保存下来。如果搜索失败,即不能满足问题条件,就进行回退,撤销上一步的选择,继续搜索其他可能的选择。
回溯算法一般包含以下步骤:
- 定义问题的解空间:确定问题的解空间,即所有可能的解组成的空间。
- 确定选择列表:对于每一步,确定可以做出的选择列表,即当前可选的路径或分支。
- 确定选择条件:确定对于每个选择是否满足问题的限定条件,即剪枝条件。
- 定义路径和结果:定义路径,即记录当前的选择路径;定义结果,即保存满足问题条件的解。
- 回溯搜索:使用递归回溯的方式搜索解空间,对于每一步,根据选择列表和选择条件进行选择和剪枝。
- 处理结果:当搜索到满足问题条件的解时,将其保存到结果中,或进行其他相应的操作。
- 回退选择:在回溯过程中,当搜索失败或搜索结束后,需要回退到上一步,撤销当前的选择,继续搜索其他可能的选择。
回溯算法的关键在于定义好选择列表、选择条件、路径和结果,并通过递归回溯的方式遍历解空间。在搜索过程中,可以通过剪枝来减少不必要的搜索,提高算法的效率。
需要注意的是,回溯算法一般用于求解所有可能的解,因此解空间可能很大,可能需要较长的时间进行搜索。在实际应用中,可以根据问题的特点进行优化,避免不必要的搜索。同时,回溯算法通常使用递归实现,需要注意递归的终止条件和边界条件,以避免无限递归或越界访问的问题。
39. 组合总和
题目描述:
给你一个 无重复元素 的整数数组 candidates
和一个目标整数 target
,找出 candidates
中可以使数字和为目标数 target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于 150
个。
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
res = [] # 结果储存
candidates.sort() # 对候选数进行排序
if candidates[0] > target:
return res
def backtrack(curr, choices, start):
if sum(curr) == target: # 当前结果的和等于目标值
res.append(curr) # 将当前结果添加到结果集中
return
if sum(curr) > target: # 当前结果的和大于目标值
return
for i in range(start, len(choices)): # 减少一些计算
num = choices[i]
# 递归调用回溯函数,将当前选择的数字添加到当前结果中,并将剩余的候选数作为新的选择列表
backtrack(curr + [num], choices, i)
backtrack([], candidates, 0) # 初始结果为空列表,候选数为给定的数列,从索引0开始选择
return res # 返回结果集
解题思路:
回溯法求解:通过不断选择一个候选数,并将其添加到当前结果中,然后递归地处理剩余的候选数,直到当前结果的和等于目标值或大于目标值。如果当前结果的和等于目标值,则将当前结果添加到结果集中。
为了避免重复的组合,每次递归调用时,通过切片操作将当前选择的数字之后的候选数作为新的选择列表。所以要对数组先经行排序,这样可以确保在每一层递归中,选择的数字不会与之前已选择的数字重复。
最终,将得到的结果集作为函数的返回值返回。时间开销较大
40. 组合总和 II
题目描述:
给定一个候选人编号的集合 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。
**注意:**解集不能包含重复的组合。
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
candidates.sort()
res = [] # 储存结果
def backtrack(curr, target, choices, start):
if target == 0: # 当目标值减到0
res.append(curr) # 将当前结果添加到结果集中
return
if target < 0: # 当目标值小于0,废弃该分支
return
for i in range(start, len(choices)):
if i > start and choices[i] == choices[i-1]: # 避免重复的组合
continue
num = choices[i]
# 递归调用回溯函数,将当前选择的数字添加到当前结果中,目标值减去选择的数字,选择列表从下一个位置开始
backtrack(curr + [num], target - num, choices, i + 1)
backtrack([], target, candidates, 0)
return res
解题思路:
回溯算法来枚举所有可能的组合,并通过剪枝操作避免了重复的组合。
122. 买卖股票的最佳时机 II
题目描述:
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices) # 股票价格的天数
res = 0 # 最大利润初始化为0
for i in range(1, n): # 遍历股票价格列表
if prices[i] > prices[i-1]: # 如果当天的股票价格高于前一天的价格
res += prices[i] - prices[i-1] # 将差值加到最大利润上
return res
解题思路:
贪心算法:在每个阶段选择局部最优解以期望最终获得全局最优解
通过比较相邻两天的股票价格来判断是否进行买卖操作,并将利润累加到最大利润中。由于可以多次买卖股票,我们可以在价格上升的时候买入,价格下降的时候卖出,这样可以获得最大利润。股票总交易可以看作数次小交易的和。
1911. 最大子序列交替和
题目描述:
一个下标从 0 开始的数组的 交替和 定义为 偶数 下标处元素之 和 减去 奇数 下标处元素之 和 。
- 比方说,数组
[4,2,5,3]
的交替和为(4 + 5) - (2 + 3) = 4
。
给你一个数组 nums
,请你返回 nums
中任意子序列的 最大交替和 (子序列的下标 重新 从 0 开始编号)。
一个数组的 子序列 是从原数组中删除一些元素后(也可能一个也不删除)剩余元素不改变顺序组成的数组。比方说,[2,7,4]
是 [4,**2**,3,**7**,2,1,**4**]
的一个子序列(加粗元素),但是 [2,4,2]
不是。
class Solution:
def maxAlternatingSum(self, nums: List[int]) -> int:
res = 0 # 存储最大交替和
pre = 0 # 前一个元素的值
for num in nums:
if num > pre:
res += num - pre # 当前元素大于前一个元素,将差值累加到最大交替和中
pre = num # 更新前一个元素的值
return res
解题思路:
贪心算法:将其看成买卖股票的最佳时机,每次都选择能使结果最大化的操作。通过比较当前元素与前一个元素的大小关系,我们可以确定是否将当前元素的差值添加到最大交替和中。因为是偶数位上的数
减去奇数位上的数
示例 3:
输入:nums = [6,2,1,2,4,5]
输出:10
解释:最优子序列为 [6,1,5] ,交替和为 (6 + 5) - 1 = 10 。
本例子中就是[0, 6, 1, 5] 0 买, 6 出售, 1买, 5出售。
46. 全排列
题目描述:
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res = [] # 储存结果集
def backtrack(curr, choices): # 定义回溯函数,参数为当前结果和待选择数集
if not choices: # 如果待选择数集为空,说明已经选择完毕,将当前结果添加到结果集中
res.append(curr)
return
for i in range(len(choices)): # 遍历每一个数
num = choices[i] # 选择的数字
backtrack(curr + [num], choices[:i] + choices[i+1:]) # 递归调用回溯函数,将选择的数字添加到当前结果中,并更新待选择数集
backtrack([], nums) # 调用回溯函数,初始结果为空列表,待选择数集为给定的数字列表
return res # 返回结果集
解题思路:
回溯法求解:在回溯函数中,通过不断选择一个数字,并将其添加到当前结果中,然后递归地处理剩余的待选择数集,直到待选择数集为空。每次递归结束后,将当前结果添加到结果集中。选择所有的开始数字并重复回溯过程,就可以得到所有可能的全排列。
每日一题:
2544. 交替数字和 ——难度简单
题目描述:
给你一个正整数 n
。n
中的每一位数字都会按下述规则分配一个符号:
- 最高有效位 上的数字分配到 正 号。
- 剩余每位上数字的符号都与其相邻数字相反。
返回所有数字及其对应符号的和。
class Solution:
def alternateDigitSum(self, n: int) -> int:
res = 0
sign = 1
while n:
res += (n % 10) * sign
n //= 10
sign = -sign
return res * -sign
解题思路:
示例 3:
输入:n = 886996
输出:0
解释:(+8) + (-8) + (+6) + (-9) + (+9) + (-6) = 0
从事例可以得出,从最后一位开始遍历,sign = 1
,循环结束时,
len(n)
如果是偶数,sign = -1
, 结果res * -sign
=res
len(n)
如果是奇数,sign = 1
, 结果res * -sign
=-res
,就是说起始位置是n
的末尾应该是sign = -1
931. 下降路径最小和——难度中等
题目描述:
给你一个 n x n
的 方形 整数数组 matrix
,请你找出并返回通过 matrix
的下降路径 的 最小和 。
下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col)
的下一个元素应当是 (row + 1, col - 1)
、(row + 1, col)
或者 (row + 1, col + 1)
。
示例 1:
输入:matrix = [[2,1,3],[6,5,4],[7,8,9]]
输出:13
解释:如图所示,为和最小的两条下降路径
示例 2:
输入:matrix = [[-19,57],[-40,-5]]
输出:-59
解释:如图所示,为和最小的下降路径
class Solution:
def minFallingPathSum(self, matrix: List[List[int]]) -> int:
n = len(matrix) # 矩阵的大小
dp = [[0] * n for _ in range(n)] # dp数组
dp[0] = matrix[0] # 初始化dp数组
for i in range(1, n):
for j in range(n):
# 计算当前位置的最小路径和
if j == 0: # 左边界
dp[i][j] = matrix[i][j] + min(dp[i-1][j], dp[i-1][j+1])
elif j == n - 1: # 右边界
dp[i][j] = matrix[i][j] + min(dp[i-1][j], dp[i-1][j-1])
else: # 正常情况
dp[i][j] = matrix[i][j] + min(dp[i-1][j], dp[i-1][j-1], dp[i-1][j+1])
return min(dp[n-1])
解题思路:
最开始想的是贪心算法,第一行的最小的位置 (row, col)
的,加上第二行的 (row + 1, col - 1)
、(row + 1, col)
或者 (row + 1, col + 1)
最小的一个,只通过一般的测试点,然后改用动规
的方法,每一行都需要找到最小数的合并结果,输出dp数组
最后一行的的最小值就是我们要的答案。注意:python数组
对边界溢出较为敏感,要考虑到边界也就是特殊情况的处理。
p[i-1][j-1])
else: # 正常情况
dp[i][j] = matrix[i][j] + min(dp[i-1][j], dp[i-1][j-1], dp[i-1][j+1])
return min(dp[n-1])
#### 解题思路:
最开始想的是贪心算法,第一行的最小的位置 `(row, col)` 的,加上第二行的 `(row + 1, col - 1)`、`(row + 1, col)` 或者 `(row + 1, col + 1)` 最小的一个,只通过一般的测试点,然后改用`动规`的方法,每一行都需要找到最小数的合并结果,输出`dp数组`最后一行的的最小值就是我们要的答案。注意:`python数组`对边界溢出较为敏感,要考虑到边界也就是特殊情况的处理。