《算法通关之路》学习笔记,记录一下自己的刷题过程,详细的内容请大家购买作者的书籍查阅。
两数之和
力扣第1题
给定一个整数数组nums和一个整数目标值target,请你在该数组中找出 和为目标值target的那两个整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
'''
解法一:双指针法
时间复杂度:O(nlogn)
'''
class Solution:
def twoSum(self, nums: list[int], target: int) -> list[int]:
new_nums = []
for idx, num in enumerate(nums):
new_nums.append((num, idx))
new_nums = sorted(new_nums, key=lambda d: d[0])
l = 0
r = len(new_nums) - 1
while(l < r):
if (new_nums[l][0] + new_nums[r][0] < target):
l += 1
elif (new_nums[l][0] + new_nums[r][0] > target):
r -= 1
else:
return [new_nums[l][1], new_nums[r][1]]
return []
## 试运行
nums, target= [2,7,11,15], 9
solu = Solution()
solu.twoSum(nums, target)
‘’’
补充:以tuple或list的某个值为key进行排序的方法
‘’’
a_list = [(1, 2), (1, 1)]
sorted(a_list, key=lambda d: d[1], reverse=True) # reverse=True从大到小
'''
解法二:dict空间换时间
时间复杂度:O(n)
'''
class Solution:
def twoSum(self, nums: list[int], target: int) -> list[int]:
num_dict = dict()
for idx, num in enumerate(nums):
if num_dict.get(target - num) is not None:
return [idx, num_dict[target - num]]
else:
num_dict[num] = idx
return []
## 试运行
nums, target= [2,7,11,15], 9
solu = Solution()
solu.twoSum(nums, target)
三数之和
力扣第15题
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0。
请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
'''
解法:双指针法
时间复杂度:O(n2)
'''
class Solution:
def threeSum(self, nums: list[int]) -> list[list[int]]:
nums.sort()
n = len(nums)
res = list()
for idx in range(n - 2):
if idx > 0 and nums[idx] == nums[idx-1]: ##去重
continue
l , r = idx + 1, n - 1
while l < r:
if nums[idx] + nums[l] + nums[r] == 0:
res.append([nums[idx], nums[l], nums[r]])
while(l < r and nums[l] == nums[l + 1]): ##去重
l += 1
while(l < r and nums[r] == nums[r - 1]): ##去重
r -= 1
l += 1
r -= 1
elif nums[idx] + nums[l] + nums[r] < 0:
l += 1
else:
r -= 1
return res
## 试运行
nums = [0,-1,1,-1]
solu = Solution()
solu.threeSum(nums)
‘’’
enumerate or range ?
使用enumerate同时获得index和value比range获得index后取得value的效率高;
range中存在len时,效率会降低;
在只需要value的情况下,直接使用in list的效率更高
‘’’
四数之和
力扣第18题
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
1.0 <= a, b, c, d < n
2.a、b、c 和 d 互不相同
3.nums[a] + nums[b] + nums[c] + nums[d] == target
4.你可以按任意顺序返回答案 。
'''
解法一:双指针法
时间复杂度:O(n3)
'''
class Solution:
def fourSum(self, nums: list[int], target: int) -> list[list[int]]:
nums.sort()
n = len(nums)
res = list()
for idx_a in range(n - 3):
# 去重
if idx_a > 0 and nums[idx_a] == nums[idx_a - 1]:
continue
for idx_b in range(idx_a + 1, n - 2):
# 去重
if idx_b > idx_a + 1 and nums[idx_b] == nums[idx_b - 1]:
continue
l, r = idx_b + 1, n - 1
while l < r:
if nums[idx_a] + nums[idx_b] + nums[l] + nums[r] == target:
res.append((nums[idx_a], nums[idx_b], nums[l], nums[r]))
l += 1
r -= 1
while l < r and nums[l] == nums[l-1]: # 去重
l += 1
while l < r and nums[r] == nums[r+1]: # 去重
r -= 1
elif nums[idx_a] + nums[idx_b] + nums[l] + nums[r] > target:
r -= 1
else:
l += 1
return res
# 试运行
nums, target = [1,0,-1,0,-2,2], 0
solu = Solution()
solu.fourSum(nums, target)
'''
解法二:回溯(超时)
时间复杂度:O(n4) T(n)=n(n-1)(n-2)(n-3)
'''
class Solution:
def fourSum(self, nums: list[int], target: int) -> list[list[int]]:
res = list()
temp = list()
nums_set = set()
n = len(nums)
nums.sort()
def dfs(nums: list[int], remain: int, start: int):
if len(temp) > 4:
return
if remain == 0 and len(temp) == 4:
if tuple(temp) in nums_set:
return
else:
nums_set.add(tuple(temp))
return res.append(temp.copy())
for i in range(start, n):
temp.append(nums[i])
dfs(nums, remain-nums[i], i + 1)
temp.pop()
dfs(nums, target, 0)
return res
# 试运行
nums, target = [1,0,-1,0,-2,2], 0
solu = Solution()
solu.fourSum(nums, target)
'''
解法三:分治法(比双指针快)
时间复杂度:O(n3)
'''
class Solution:
def fourSum(self, nums: list[int], target: int) -> list[list[int]]:
nums.sort()
res = list()
temp = list()
def findNsum(nums: list[int], target: int, N: int, temp:list[int]):
if len(nums) < N or N < 2:
return
# 两数之和
if N == 2:
l, r = 0, len(nums) - 1
while l < r:
if nums[l] + nums[r] == target:
res.append(temp + [nums[l], nums[r]])
l += 1
r -= 1
while l < r and nums[l] == nums[l - 1]:
l += 1
while r > l and nums[r] == nums[r + 1]:
r -= 1
elif nums[l] + nums[r] < target:
l += 1
else:
r -= 1
# 缩减问题规模
else:
for i in range(len(nums)):
if i == 0 or i > 0 and nums[i-1] != nums[i]:
findNsum(nums[i+1:], target-nums[i], N-1, temp+[nums[i]])
return
findNsum(nums, target, 4, temp)
return res
# 试运行
nums, target = [1,0,-1,0,-2,2], 0
solu = Solution()
solu.fourSum(nums, target)
四数相加II
力扣第454题
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
'''
解法一:dict空间换时间
时间复杂度:O(n2)
'''
class Solution:
def fourSumCount(self, nums1: list[int], nums2: list[int], nums3: list[int], nums4: list[int]) -> int:
num_dict = dict()
res = 0
for i in nums1:
for j in nums2:
num_dict[i + j] = num_dict.get(i + j, 0) + 1
for i in nums3:
for j in nums4:
res += num_dict.get(-(i + j), 0)
return res
# 试运行
nums1, nums2, nums3, nums4 = [1,2], [-2,-1], [-1,2], [0,2]
solu = Solution()
solu.fourSumCount(nums1, nums2, nums3, nums4)
最接近的三数之和
力扣第16题
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
'''
解法一:双指针
时间复杂度:O(n2)
'''
class Solution:
def threeSumClosest(self, nums: list[int], target: int) -> int:
nums.sort()
n = len(nums)
res = nums[0] + nums[1] + nums[2] # res赋初值
for idx in range(n - 2):
if idx > 0 and nums[idx] == nums[idx-1]: ##去重
continue
l , r = idx + 1, n - 1
while l < r:
tmp_sum = nums[idx] + nums[l] + nums[r]
if tmp_sum == target:
return tmp_sum
if abs(tmp_sum - target) < abs(res - target):
res = tmp_sum
elif tmp_sum < target:
l += 1
while l < r and nums[l] == nums[l-1]: ##去重
l += 1
else:
r -= 1
while l < r and nums[r] == nums[r+1]: ##去重
r -= 1
return res
# 试运行
nums, target = [-1,2,1,-4], 1
solu = Solution()
solu.threeSumClosest(nums, target)
最大子序列和
力扣第53题
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
'''
解法一:暴力法(超时)
时间复杂度:O(n2)
'''
class Solution:
def maxSubArray(self, nums: list[int]) -> int:
n = len(nums)
maxSum = float('-inf') # 负无穷的表示法
for i in range(n):
for j in range(i, n):
tmp_sum = sum(nums[i:j+1])
if tmp_sum > maxSum:
maxSum = tmp_sum
return maxSum
# 试运行
nums = [-2,1,-3,4,-1,2,1,-5,4]
solu = Solution()
solu.maxSubArray(nums)
'''
解法二:分治法
时间复杂度:O(nlogn)
'''
class Solution:
def maxSubArray(self, nums: list[int]) -> int:
def helper(nums: list[int], l: int, r: int):
if l > r:
return float('-inf')
mid = (l + r) // 2
left = helper(nums, l, mid - 1)
right = helper(nums, mid + 1, r)
left_suffix_max_sum = right_prefix_max_sum = 0
total = 0
for i in reversed(range(l, mid)):
total += nums[i]
left_suffix_max_sum = max(left_suffix_max_sum, total)
total = 0
for i in range(mid + 1, r + 1):
total += nums[i]
right_prefix_max_sum = max(right_prefix_max_sum, total)
cross_max_sum = left_suffix_max_sum + right_prefix_max_sum + nums[mid]
return max(cross_max_sum, left, right)
return helper(nums, 0, len(nums)-1)
# 试运行
nums = [-2,1,-3,4,-1,2,1,-5,4]
solu = Solution()
solu.maxSubArray(nums)
'''
解法三:动态规划
时间复杂度:O(n)
'''
class Solution:
def maxSubArray(self, nums: list[int]) -> int:
n = len(nums)
dp = [0] * (n + 1)
dp[0] = float('-inf')
for i in range(1, n + 1):
dp[i] = max(nums[i-1], nums[i-1] + dp[i-1])
return max(dp)
# 优化空间复杂度
class Solution:
def maxSubArray(self, nums: list[int]) -> int:
n = len(nums)
max_sum = max_cur_sum = float('-inf')
for i in range(1, n + 1):
max_cur_sum = max(nums[i-1], nums[i-1] + max_cur_sum)
if max_cur_sum > max_sum:
max_sum = max_cur_sum
return max_sum
# 试运行
nums = [-2,1,-3,4,-1,2,1,-5,4]
solu = Solution()
solu.maxSubArray(nums)
'''
解法四:前缀和
时间复杂度:O(n)
'''
class Solution:
def maxSubArray(self, nums: list[int]) -> int:
n = len(nums)
max_sum = nums[0]
# min_sum 最小前缀和表示序列和不再继续扩展的最后一个位置
min_sum = tmp_sum = 0
for i in range(n):
tmp_sum += nums[i]
max_sum = max(max_sum, tmp_sum - min_sum)
min_sum = min(min_sum, tmp_sum)
return max_sum
# 试运行
nums = [-2,1,-3,4,-1,2,1,-5,4]
solu = Solution()
solu.maxSubArray(nums)
最大数
力扣第179题
给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。
注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。
'''
解法一:排序
时间复杂度:O(nlogn)
'''
class Solution:
def largestNumber(self, nums: list[int]) -> str:
nums_str = list(map(str, nums))
import functools
def comp(a, b):
if a + b > b + a:
return 1
elif a + b < b + a:
return -1
else:
return 0
nums_str.sort(reverse=True, key=functools.cmp_to_key(comp))
return ''.join(nums_str) if nums_str[0] != '0' else '0'
# 试运行
nums = [10,2]
solu = Solution()
solu.largestNumber(nums)
分数到小数
力扣第166题
给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以 字符串形式返回小数 。
如果小数部分为循环小数,则将循环的部分括在括号内。
如果存在多个答案,只需返回 任意一个。
对于所有给定的输入,保证 答案字符串的长度小于 104 。
'''
解法一:短除法
时间复杂度:O(l)
'''
class Solution:
def fractionToDecimal(self, numerator: int, denominator: int) -> str:
# divmod() 函数把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a // b, a % b)
n, remainder = divmod(abs(numerator), abs(denominator)) # 去除符号
sign = ''
if(numerator // denominator < 0):
sign = '-'
res = [str(n), '.']
seen = []
# 短除法
while remainder not in seen:
seen.append(remainder)
n, remainder = divmod(remainder * 10, abs(denominator))
res.append(str(n))
# 处理循环节
index = seen.index(remainder)
res.insert(index + 2, '(')
res.append(')')
# 去除不循环时末尾的(0)还有整除时末尾的小数点
return sign + ''.join(res).replace('(0)', '').rstrip('.')
# 试运行
numerator, denominator = 4, 333
solu = Solution()
solu.fractionToDecimal(numerator, denominator)
最大整除子集
力扣第368题
给你一个由 无重复 正整数组成的集合 nums ,请你找出并返回其中最大的整除子集 answer ,子集中每一元素对 (answer[i], answer[j]) 都应当满足:
answer[i] % answer[j] == 0 ,或 answer[j] % answer[i] == 0
如果存在多个有效解子集,返回其中任何一个均可。
'''
解法一:动态规划
时间复杂度:O(n2)
'''
class Solution:
def largestDivisibleSubset(self, nums: list[int]) -> list[int]:
S = {-1: list()}
for x in sorted(nums):
S[x] = max((S[d] for d in S if x % d == 0), key=len) + [x]
return max(S.values(), key=len) # 取list中长度最大的那一个
# 试运行
nums = [1,2,4,8]
solu = Solution()
solu.largestDivisibleSubset(nums)
质数排列
力扣第1175题
请你帮忙给从 1 到 n 的数设计排列方案,使得所有的「质数」都应该被放在「质数索引」(索引从 1 开始)上;你需要返回可能的方案总数。
让我们一起来回顾一下「质数」:质数一定是大于 1 的,并且不能用两个小于它的正整数的乘积来表示。
由于答案可能会很大,所以请你返回答案 模 mod 10^9 + 7 之后的结果即可。
'''
解法一:求质数+阶乘
时间复杂度:O(n^3/2
'''
import math
class Solution:
def numPrimeArrangements(self, n: int) -> int:
count = 1
for num in range(3, n+1):
flag = True
for div in range(2, int(math.sqrt(num)) + 1):
if num % div == 0:
flag = False
break
if flag == True:
count += 1
res = 1
for i in range(1, count + 1):
res = (i * res) % (1e9 + 7)
for i in range(1, n - count + 1):
res = (i * res) % (1e9 + 7)
return int(res) # 其中一个或两个数是浮点数,则求余的结果将是浮点数
# 试运行
n = 100
solu = Solution()
solu.numPrimeArrangements(n)