两数之和:leetcode链接
思路一:暴力破解,两个for循环。遍历每一种可能。
def twoSum(nums: List[int], target: int) -> List[int]:
ans = []
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
ans.append(i)
ans.append(j)
return ans
return ans
执行用时:3692 ms, 在所有 Python3 提交中击败了7.98%的用户
内存消耗:15.5 MB, 在所有 Python3 提交中击败了68.43%的用户
时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)
思路二:排序加双指针
def twoSum(nums: List[int], target: int) -> List[int]:
nums = sorted(nums) # 排序
left_ptr = 0 # 左指针指向第一个元素
right_ptr = len(nums) - 1 # 右指针指向第最后一个元素
ans = []
while left_ptr < right_ptr: # 结束条件
temp_num = nums[left_ptr] + nums[right_ptr]
if temp_num == target:
ans = [left_ptr, right_ptr]
break
elif temp_num > target:# 单调性
right_ptr -= 1
else:
left_ptr += 1
return ans
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
不过解法不适用于该题,因为排序会改变下标。
思路3:空间换时间
使用hash表,每遍历一个元素,就判断是否有target-nums[i]存在。如果存在,返回下标,否则存入hash表。
def twoSum(nums: List[int], target: int) -> List[int]:
ans = []
hashmap = {}
for i in range(len(nums)):
if (target - nums[i]) in hashmap:
ans = [i, hashmap[target - nums[i]]]
else:
hashmap[nums[i]] = i
return ans
执行用时:36 ms, 在所有 Python3 提交中击败了84.01%的用户
内存消耗:16.1 MB, 在所有 Python3 提交中击败了13.49%的用户
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
三数之和:leetcode链接
此题与上题的差异:
思路:外层循环访问每个元素,内层循环使用双指针找到满足条件的数。内层去重判断左界和右界是否和下一位置重复,去除重复解,并同时将 左右指针移到下一位置,寻找新的解。
def threeSum(nums: List[int]) -> List[List[int]]:
nums.sort()
n = len(nums)
ans = []
#外层循环遍历每个元素,对于最后一组情况应该到n-3。
for i in range(n - 2): # 找到第n-3个数
# 去重
if i > 0 and nums[i] == nums[i - 1]:# 需要和上次枚举的数不同
continue
left_ptr = i + 1
right_ptr = n - 1
# 内层循环使用双指针找符合条件的数。
while left_ptr < right_ptr:
temp_num = nums[left_ptr] + nums[right_ptr] + nums[i]
if temp_num == 0:
ans.append([nums[i], nums[left_ptr], nums[right_ptr]])
# 去重
while left_ptr < right_ptr and nums[left_ptr] == nums[left_ptr + 1]:
left_ptr += 1
while left_ptr < right_ptr and nums[right_ptr] == nums[right_ptr - 1]:
right_ptr -= 1
left_ptr += 1
right_ptr -= 1
elif temp_num > 0:#单调性
right_ptr -= 1
else:#单调性
left_ptr += 1
return ans
执行用时:628 ms, 在所有 Python3 提交中击败了74.97%的用户
内存消耗:17.8 MB, 在所有 Python3 提交中击败了58.05%的用户
时间复杂度: O ( n 2 ) O(n^2) O(n2)
四数之和:leetcode链接
要求四个数字之和与目标值相等。
思路一:回溯法,遍历所有可能。此思路本题会超时。
def backtrack(res: List[List[int]], nums: List[int], n: int, tempList: List[int], target: int, start: int,
hashmap: dict):
# 长度超过4退出
if (len(tempList) > 4):
return
#判断是否符合条件
if len(tempList) == 4 and sum(tempList) == target:
if str(tempList) in hashmap:
# 去重
return
else:
hashmap[str(tempList)] = True
return res.append(tempList.copy())
#回溯。。
for i in range(start, n):
# 剪枝
if len(tempList) == 3 and sum(tempList) + nums[i] != target:
continue
tempList.append(nums[i])
backtrack(res, nums, n, tempList, target, i + 1, hashmap)
tempList.pop()
def fourSum(nums: List[int], target: int) -> List[List[int]]:
res = []
hashmap = {}
nums.sort()
backtrack(res, nums, len(nums), [], target, 0, hashmap)
return res
时间复杂度: O ( n 4 ) O(n^4) O(n4)
空间复杂度: O ( n 4 ) O(n^4) O(n4)
思路二:排序 + 双指针
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
quadruplets = list()
if not nums or len(nums) < 4:
return quadruplets
nums.sort()
length = len(nums)
for i in range(length - 3):
# 去重
if i > 0 and nums[i] == nums[i - 1]:
continue
# 不满足条件
if nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target:
break
# 剪枝
if nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target:
continue
# 第二重循环
for j in range(i + 1, length - 2):
# 去重
if j > i + 1 and nums[j] == nums[j - 1]:
continue
# 不满足条件
if nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target:
break
# 剪枝
if nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target:
continue
left, right = j + 1, length - 1
# 双指针
while left < right:
total = nums[i] + nums[j] + nums[left] + nums[right]
if total == target:
quadruplets.append([nums[i], nums[j], nums[left], nums[right]])
while left < right and nums[left] == nums[left + 1]:
left += 1
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
right -= 1
elif total < target:
left += 1
else:
right -= 1
return quadruplets
执行用时:72 ms, 在所有 Python3 提交中击败了91.05%的用户
内存消耗:15.1 MB, 在所有 Python3 提交中击败了40.67%的用户
时间复杂度: O ( n 3 ) O(n^3) O(n3)
空间复杂度: O ( 1 ) O(1) O(1)
最接近的三数之和:leetcode链接
思路:和三数之和类似,只是找的是与target只差绝对值最小的数。
class Solution:
def threeSumClosest(self,nums: List[int], target: int) -> int:
nums.sort()
n = len(nums)
res = nums[0] + nums[1] + nums[2] # 最小三个数之和
# 外层循环遍历每个元素,对于最后一组情况应该到n-3。
for i in range(n - 2): # 找到第n-3个数
# 去重
if i > 0 and nums[i] == nums[i - 1]: # 需要和上次枚举的数不同
continue
left_ptr = i + 1
right_ptr = n - 1
# 内层循环使用双指针找符合条件的数。
while left_ptr < right_ptr:
temp_num = nums[left_ptr] + nums[right_ptr] + nums[i]
if temp_num == target:
return temp_num
if abs(temp_num - target) < abs(res - target):
res = temp_num
if temp_num > target: # 单调性
right_ptr -= 1
elif temp_num < target: # 单调性
left_ptr += 1
return res
执行用时:264 ms, 在所有 Python3 提交中击败了23.96%的用户
内存消耗:15.1 MB, 在所有 Python3 提交中击败了38.14%的用户
时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n ) O(n) O(n)
最大子序列和:leetcode链接
思路一:暴力破解,遍历每一种子序列
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
ans = -math.inf
n = len(nums)
total = 0
for i in range(n):
total = 0
for j in range(i, n):
total += nums[j]
ans = max(total * 1.0, ans)
return int(ans)
超时
时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)
思路二:前缀和
若有 s [ i ] = s [ 0 ] + s [ 1 ] + . . . + s [ i ] s[i] = s[0] + s[1] + ...+s[i] s[i]=s[0]+s[1]+...+s[i],则 s [ j ] − s [ i ] s[j] - s[i] s[j]−s[i]就是i到j的序列和。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
maxSum = nums[0]
minSum = sum = 0
for i in range(n):
sum += nums[i]
maxSum = max(maxSum, sum - minSum)
minSum = min(minSum, sum)
return maxSum
思路二:动态规划,
dp[i] 表示在位置i的最长的子序列
d p [ i ] = m a x ( d p [ i − 1 ] , n u m s [ i ] ) dp[i] = max(dp[i-1],nums[i]) dp[i]=max(dp[i−1],nums[i])
用一个变量记录最大值。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
dp = [0 for i in range(n + 1)]
dp[0] = nums[0]
ans = dp[0]
for i in range(1, n):
dp[i] = max(dp[i - 1] + nums[i], nums[i])
ans = max(dp[i], ans)
return ans
执行用时:264 ms, 在所有 Python3 提交中击败了6.93%的用户
内存消耗:25.7 MB, 在所有 Python3 提交中击败了52.24%的用户
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
实际上不需要所有的值,所以可以不需要dp数组
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
dp = nums[0]
ans = dp
for i in range(1, n):
dp = max(dp + nums[i], nums[i])
ans = max(dp, ans)
return ans
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)
思路3:分治
每次将区间平均分成两部分,最大序列可能有3中情况。
情况1:左序列。
情况2:右序列。
情况3:保护中间元素的序列。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
return self.helper(nums, 0, len(nums) - 1)
def helper(self, nums: List[int], start: int, end: int) -> int:
if start > end:
return float("-inf")
mid = start + (end - start) // 2
## 左序列的值
left_value = self.helper(nums, start, mid - 1)
## 右序列的值
right_value = self.helper(nums, mid + 1, end)
left_max_value = right_max_value = 0
## 跨越中间的情况
total = 0
# 在区间[start,mid-1]中的最大值,倒叙检查正序都可以。
for i in reversed(range(start, mid)):
total += nums[i]
left_max_value = max(left_max_value, total)
total = 0
# 在区间[mid+1,end]中的最大值。
for i in range(mid + 1, end + 1):
total += nums[i]
right_max_value = max(right_max_value, total)
# 跨越中间情况下的值
cross_value = left_max_value + right_max_value + nums[mid]
# 返回三种情况的最大值
return max(left_value, right_value, cross_value)
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( l o g n ) O(logn) O(logn)
最大数:leetcode链接
思路:将数转为字符串,自定义比较函数。
class Solution:
def largestNumber(self, nums: List[int]) -> str:
num_list = [str(e) for e in nums]
def compare(a, b):
if a + b > b + a:
return 1
elif a + b < b + a:
return -1
else:
return 0
num_list.sort(key=functools.cmp_to_key(compare), reverse=True)
return "".join(num_list) if num_list[0] != '0' else '0'
执行用时:44 ms, 在所有 Python3 提交中击败了54.94%的用户
内存消耗:15 MB, 在所有 Python3 提交中击败了30.39%的用户
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( l o g n ) O(logn) O(logn)
分到小数:leetcode链接
模拟手算乘法,当余数没有出现循环时,则余数*10继续运算,否则结束运算。
class Solution:
def fractionToDecimal(self, numerator: int, denominator: int) -> str:
sign = ""
# 符号
if numerator // denominator < 0:
sign = "-"
# 处理整数部分
n, remainder = divmod(abs(numerator), abs(denominator))
hashtable = []
ans = [str(n), "."]
# 如果remainder 不在hash表里说明没有循环。
while remainder not in hashtable:
# 将remainder 加入hashtable,数组实现,下标表示出现循环的起始位置。
hashtable.append(remainder)
# remainder 乘 10
n, remainder = divmod(remainder * 10, abs(denominator))
# 整数部分放入结果。
ans.append(str(n))
# 获取循环开始的位置
index = hashtable.index(remainder)
# 拼接(
ans.insert(index + 2, "(")
ans.append(")")
# 如果循环是(0)则去掉,如果右侧是.则去掉。
return sign + "".join(ans).replace("(0)", '').rstrip(".")
执行用时:44 ms, 在所有 Python3 提交中击败了15.12%的用户
内存消耗:15 MB, 在所有 Python3 提交中击败了81.03%的用户
时间复杂度: O ( d e m o n i a t o r ) O(demoniator) O(demoniator)
空间复杂度: O ( d e m o n i a t o r ) O(demoniator) O(demoniator)
最大整除子集:leetcode链接
子集个数有 2 n 2^n 2n个,暴力不行。
如果要满足题意,即集合S中任意一对数 ( s j , s i ) (s_j,s_i) (sj,si)都有, s j % s i = 0 s_j\%s_i =0 sj%si=0,或者 s i % s j = 0 s_i\%s_j = 0 si%sj=0。
如果是排好序的。则只需要有最大元素与前一个能整除即可。
排序后,如果用map存储每个元素最大的子集情况,key表示子集中最大的值,value表示子集。
class Solution:
def largestDivisibleSubset(self, nums: List[int]) -> List[int]:
nums.sort()
# 声明一个hashmap,key表示子集中最大的元素,value表示子集
# 初始状态为{-1:set()}
hashmap = {-1: set()}
# 遍历数组中的每一个元素。
for e in nums:
# 用来保存容纳e的子集。
temp = []
# hashmap的key是子集中最大的元素
for key in hashmap:
if e % key == 0:
# 存入子集
hashmap[key].add(e)
# 存入temp
temp.append(hashmap[key])
# 移除子集
hashmap[key].remove(e)
# 表示当前元素能够构建的最大子集
hashmap[e] = max(temp, key=len) | {e}
return list(max(hashmap.values(), key=len))
执行用时:248 ms, 在所有 Python3 提交中击败了91.16%的用户
内存消耗:16.1 MB, 在所有 Python3 提交中击败了7.97%的用户
时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n 2 ) O(n^2) O(n2)
思路二:动态规划
class Solution:
def largestDivisibleSubset(self, nums: List[int]) -> List[int]:
nums.sort()
n = len(nums)
f, g = [0] * n, [0] * n
for i in range(n):
# 至少包含自身一个数,因此起始长度为 1,由自身转移而来
length, prev = 1, i
for j in range(i):
if nums[i] % nums[j] == 0:
# 如果能接在更长的序列后面,则更新「最大长度」&「从何转移而来」
if f[j] + 1 > length:
length = f[j] + 1
prev = j
# 记录「最终长度」&「从何转移而来」
f[i] = length
g[i] = prev
# 遍历所有的 f[i],取得「最大长度」和「对应下标」
max_len = idx = -1
for i in range(n):
if f[i] > max_len:
idx = i
max_len = f[i]
# 使用 g[] 数组回溯出具体方案
ans = []
while len(ans) < max_len:
ans.append(nums[idx])
idx = g[idx]
ans.reverse()
return ans
执行用时:292 ms, 在所有 Python3 提交中击败了84.40%的用户
内存消耗:15 MB, 在所有 Python3 提交中击败了94.28%的用户
时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n ) O(n) O(n)
质数排序:leetcode链接
n为100,可以提前算出素数范围表。
排列数,素数在素数索引全排,合数在合适索引全排。若n范围内的素数个数为m,结果为 m ! × ( n − m ) ! m!\times(n-m)! m!×(n−m)!
class Solution:
def numPrimeArrangements(self, n: int) -> int:
dp = [0, 0, 1, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25]
prime_arrange = math.factorial(dp[n])
non_prime_arrange = math.factorial(n - dp[n])
p = 10 ** 9 + 7
return prime_arrange * non_prime_arrange % p
执行用时:36 ms, 在所有 Python3 提交中击败了68.12%的用户
内存消耗:14.9 MB, 在所有 Python3 提交中击败了75.11%的用户
时间复杂度: O ( 1 ) O(1) O(1)
空间复杂度: O ( 1 ) O(1) O(1)
优化空间:阶乘乘法,改为快速模乘。
class Solution:
# n < 100
def __is_prime(self, n: int) -> bool:
i = 0
if n == 1:
return False
prime_table = [2, 3, 5, 7]
flag = True
for e in prime_table:
if n % e == 0:
flag = False
break
return flag
def __get_prime_table(self, n: int) -> List[int]:
prime_table = [2, 3, 5, 7]
for i in range(n + 1):
if self.__is_prime(i):
prime_table.append(i)
return prime_table
def numPrimeArrangements(self, n: int) -> int:
# prime_table = self.__get_prime_table(n)
# dp = [0 for i in range(n + 1)]
# dp[0] = 0
# for i in range(n + 1):
# if i in prime_table:
# dp[i] = dp[i - 1] + 1
# else:
# dp[i] = dp[i - 1]
# print(dp)
dp = [0, 0, 1, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25]
prime_arrange = math.factorial(dp[n])
non_prime_arrange = math.factorial(n - dp[n])
p = 10 ** 9 + 7
mul = prime_arrange * non_prime_arrange % p
return mul