1.无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
方法:滑动窗口
class Solution(object):
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
#滑动窗口
# stack=[]
max=0
first_pos=0 #记录滑动窗口的起点
while first_pos<len(s):
stack=[]
pos=first_pos
while pos<len(s) and s[pos] not in stack :
stack.append(s[pos])
pos+=1
first_pos=pos-len(stack)+1 #更新滑动窗口的起点,去掉栈内重复的首字母即可
if len(stack)>max:
max=len(stack)
return max
2. 两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def addTwoNumbers(self, l1, l2):
"""
:type l1: ListNode
:type l2: ListNode
:rtype: ListNode
"""
s3=ListNode(None)
result=s3
jinwei=0
while l1 or l2:
x=l1.val if l1 else 0
y=l2.val if l2 else 0
ret=x+y+jinwei
s3.next=ListNode(ret%10)
s3=s3.next
if ret>=10:
jinwei=1
else:
jinwei=0
if l1:
l1=l1.next
if l2:
l2=l2.next
if jinwei==1: #特例如l1=[9,9,9,9,9,9,9] l2=[9,9,9,9]
s3.next=ListNode(1)
return result.next
3.整数反转
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−2**31, 2**31 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
class Solution(object):
def reverse(self, x):
"""
:type x: int
:rtype: int
"""
s=str(x)
minus=0
res=[]
if s[0]=="-":
minus=1
if minus==0:
for i in range(-1,-(len(s)+1),-1):
res.append(s[i])
else:
for i in range(-1,-len(s),-1):
res.append(s[i])
if minus:
result=int("-"+"".join(res))
else:
result=int("".join(res))
if result>2147483647 or result<-2147483648: #[−2**31, 2**31 − 1]
return 0
return result
4. 用最少数量的箭引爆气球
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
-在x = 6处射出箭,击破气球[2,8]和[1,6]。
-在x = 11处发射箭,击破气球[10,16]和[7,12]。
class Solution(object):
def findMinArrowShots(self, points):
"""
:type points: List[List[int]]
:rtype: int
"""
#必须射出的最小弓箭数
#先排列
points.sort(key=lambda ballon:ballon[1])
pos=points[0][1] #记录首次射出箭的位置
ans=1 #首次射出一支箭
for i in range(len(points)):
if points[i][0]>pos: #当出现气球的Xstart要超出已有箭的射程范围
pos=points[i][1] #更新箭的射出位置
ans+=1 #添加一支箭
#如果有完全被重叠的小区间 符合要求嘛?按照Xend排序的,符合要求
#比如points =[[1,10],[2,3],[7,8]]
#sort完points =[[2,3],[7,8],[1,10]]
return ans
5. 两数之和 II - 输入有序数组
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
class Solution(object):
def twoSum(self, numbers, target):
"""
:type numbers: List[int]
:type target: int
:rtype: List[int]
"""
#得到的index记得加1
##二分法
n=len(numbers)
left,right=0,n-1
while left<right:
if numbers[left]+numbers[right]==target: ##关于是否相等的判断放到后面可以提高速度,因为之前的判断一般都是>target或者<target
return [left+1,right+1]
elif numbers[left]+numbers[right]>target:
right-=1
else:
left+=1
6. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
class Solution(object):
def rob(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
# #dp[i]代表偷窃到第i个房子时,可以偷窃到的最大金额(第i个房子还没有偷)
n=len(nums)
dp=[0]*(n+1)
dp[1]=nums[0]
for i in range(2,n+1):
dp[i]=max(dp[i-1],dp[i-2]+nums[i-1])
#dp[i-2]+nums[i-1]表示不偷i-2但偷i-1;dp[i-1]表示i-1前偷窃到的最大金额
#之前有个小疑惑,就是最大金额为什么不可以是dp[i-3]+nums[i],这里要理解dp[i]表示第i个房子还没有偷时已偷到手的最大金额
return dp[n]
7.寻找峰值
峰值元素是指其值严格大于左右相邻值的元素。
给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞ 。
你必须实现时间复杂度为 O(log n) 的算法来解决此问题。
本题可以二分法找到一个大于左右相邻元素的target
但是疑问在于不对数组进行排序的前提下,二分法怎么保证总可以找到一个峰值元素
1. 由于 nums[-1] = nums[n] = -∞,所以只要nums数组不为空,总存在一个峰值
2. 每次二分找到中间的数,比较它和左右相邻数的大小,如果它比左右数都要大,lucky,峰值就这么被找到了。如果不是,设令右边的值大于它,那么可以推断得到它的右边一定存在一个峰值,因为 nums[n] = -∞,继续往右边找总能找到一个峰值;如果右边的值小于它,左边的值大于它,由于nums[-1] = -∞,那么左边总有一个峰值,继续往左边找
class Solution(object):
def findPeakElement(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
"方法一:虽然时间复杂度是o(n),但是值得学习的代码"
# 1.生成随机数,在数组中随机位置开始找
# 2.为了方便考虑nums[-1] = nums[n] = -∞,虽然可以重新设一个数组,但是为降低空间复杂度,用一个函数
# n=len(nums)
# #例外case
# if n==1:
# return nums
# idx=random.randint(0,n-1)
# def get(idx):
# if idx==-1 or idx==n:
# return float("-inf")
# else:
# return nums[idx]
# while not (get(idx-1)<get(idx) and get(idx)>get(idx+1)):
# if get(idx)<get(idx+1):
# idx+=1
# else:
# idx-=1
# return idx
"方法二:二分查找"
n=len(nums)
#例外case
def get(idx):
if idx==-1 or idx==n:
return float("-inf")
else:
return nums[idx]
# left<=right,等于right的情况
left,right,ans=0,n-1,-1
while left<=right:
mid=(left+right)//2
if (get(mid-1)<get(mid) and get(mid)>get(mid+1)):
return mid
if get(mid)<get(mid+1):
left=mid+1
else:
right=mid-1
8.打家劫舍 三
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
代码来源力扣评论区
# 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 rob(self, root: TreeNode) -> int:
def _rob(root):
if not root:
return 0,0
ls,ln=_rob(root.left)
rs,rn=_rob(root.right)
#ls:偷左子树能带来的最大收益,ln表示不偷左子树能带来的最大收益,rs,rn同理
return root.val+ln+rn, max(ls,ln)+max(rs,rn)
#root.val+ln+rn表示偷父节点不偷左右子节点;max(ls,ln)+max(rs,rn)表示不偷父节点,左右节点可偷可不偷,选择收益最大的情况
return max(_rob(root))
9. 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
三数之和的求解思想是:首先遍历质数数组,下标为i。在每次遍历中(即 i 固定),设定left,right=i+1,len(nums)-1,然后如果 nums[i]+nums[left]+nums[right]<0,那么left+=1;如果nums[i]+nums[left]+nums[right]>0,那么right-=1;如果相等,除了要保存[nums[i],nums[left],nums[right]]还需要left+=1,right-=1,因为在同样的 i 下,可能还存在nums[i]+nums[left]+nums[right]==0的组合,要直到right<=left才能停下来,继续for循环。
这里去除重复解是难点。
采取的方法是
- if(i>0 and nums[i]==nums[i-1]): #当出现已经处理过的相同的nums[i],跳过
continue - while left<right and nums[left]==nums[left+1]: #判断左界和右界是否和下一位置重复,去重复解
left+=1
while left<right and nums[right]==nums[right-1]:
right-=1
class Solution(object):
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
nums.sort()
res=[]
for i in range(len(nums)):
if nums[i]>0:
return res
if(i>0 and nums[i]==nums[i-1]): #去重复解
continue
left,right=i+1,len(nums)-1
while left<right:
if nums[i]+nums[left]+nums[right]<0:
left+=1
elif nums[i]+nums[left]+nums[right]>0:
right-=1
else:
res.append([nums[i],nums[left],nums[right]])
while left<right and nums[left]==nums[left+1]: #去重复解
left+=1
while left<right and nums[right]==nums[right-1]:
right-=1
left+=1
right-=1
##还有答案中不可包含重复三元组,需要去重
return res
10.2241. 设计一个 ATM 机器
一个 ATM 机器,存有 5 种面值的钞票:20 ,50 ,100 ,200 和 500 美元。初始时,ATM 机是空的。用户可以用它存或者取任意数目的钱。
取款时,机器会优先取 较大 数额的钱。
比方说,你想取 $300 ,并且机器里有 2 张 $50 的钞票,1 张 $100 的钞票和1 张 $200 的钞票,那么机器会取出 $100 和 $200 的钞票。
但是,如果你想取 $600 ,机器里有 3 张 $200 的钞票和1 张 $500 的钞票,那么取款请求会被拒绝,因为机器会先取出 $500 的钞票,然后无法取出剩余的 $100 。注意,因为有 $500 钞票的存在,机器 不能 取 $200 的钞票。
请你实现 ATM 类:
ATM() 初始化 ATM 对象。
void deposit(int[] banknotesCount) 分别存入 $20 ,$50,$100,$200 和 $500 钞票的数目。
int[] withdraw(int amount) 返回一个长度为 5 的数组,分别表示 $20 ,$50,$100 ,$200 和 $500 钞票的数目,并且更新 ATM 机里取款后钞票的剩余数量。如果无法取出指定数额的钱,请返回 [-1] (这种情况下 不 取出任何钞票)。
输入:
[“ATM”, “deposit”, “withdraw”, “deposit”, “withdraw”, “withdraw”]
[[], [[0,0,1,2,1]], [600], [[0,1,0,1,1]], [600], [550]]
输出:
[null, null, [0,0,1,0,1], null, [-1], [0,1,0,0,1]]
解释:
ATM atm = new ATM();
atm.deposit([0,0,1,2,1]); // 存入 1 张 $100 ,2 张 $200 和 1 张 $500 的钞票。
atm.withdraw(600); // 返回 [0,0,1,0,1] 。机器返回 1 张 $100 和 1 张 $500 的钞票。机器里剩余钞票的数量为 [0,0,0,2,0] 。
atm.deposit([0,1,0,1,1]); // 存入 1 张 $50 ,1 张 $200 和 1 张 $500 的钞票。
// 机器中剩余钞票数量为 [0,1,0,3,1] 。
atm.withdraw(600); // 返回 [-1] 。机器会尝试取出 $500 的钞票,然后无法得到剩余的 $100 ,所以取款请求会被拒绝。
// 由于请求被拒绝,机器中钞票的数量不会发生改变。
atm.withdraw(550); // 返回 [0,1,0,0,1] ,机器会返回 1 张 $50 的钞票和 1 张 $500 的钞票。
错误代码
纯纯暴力解题,超出时间限制
class ATM(object):
def __init__(self):
self.total=0 ##维护总money
self.banknotesCount=[0]*5 #维护各面值的钞票数
def deposit(self, banknotesCount):
"""
:type banknotesCount: List[int]
:rtype: None
"""
self.total+=banknotesCount[0]*20
self.total+=banknotesCount[1]*50
self.total+=banknotesCount[2]*100
self.total+=banknotesCount[3]*200
self.total+=banknotesCount[4]*500
for i in range(5):
self.banknotesCount[i]+=banknotesCount[i]
def withdraw(self, amount):
"""
:type amount: int
:rtype: List[int]
"""
if amount>self.total:
return [-1]
res=[0]*5
while amount>=500 and self.banknotesCount[4]>0:
amount-=500
self.banknotesCount[4]-=1
res[4]+=1
while amount>=200 and self.banknotesCount[3]>0:
amount-=200
self.banknotesCount[3]-=1
res[3]+=1
while amount>=100 and self.banknotesCount[2]>0:
amount-=100
self.banknotesCount[2]-=1
res[2]+=1
while amount>=50 and self.banknotesCount[1]>0:
amount-=50
self.banknotesCount[1]-=1
res[1]+=1
while amount>=20 and self.banknotesCount[0]>0:
amount-=20
self.banknotesCount[0]-=1
res[0]+=1
if amount==0:
return res
else:
for i in range(5):
self.banknotesCount[i]+=res[i]
# print(self.banknotesCount)
return [-1]
优化版本
class ATM:
def __init__(self):
self.cnt = [0] * 5 # 每张钞票剩余数量
self.value = [20, 50, 100, 200, 500] # 每张钞票面额
def deposit(self, banknotesCount: List[int]) -> None:
for i in range(5):
self.cnt[i] += banknotesCount[i]
def withdraw(self, amount: int) -> List[int]:
res = [0] * 5
# 模拟尝试取出钞票的过程
for i in range(4, -1, -1):
res[i] = min(self.cnt[i], amount // self.value[i])
amount -= res[i] * self.value[i]
if amount:
# 无法完成该操作
return [-1]
else:
# 可以完成该操作
for i in range(5):
self.cnt[i] -= res[i]
return res
11. 322. 零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
class Solution(object):
def coinChange(self, coins, amount):
"""
:type coins: List[int]
:type amount: int
:rtype: int
"""
##此题优化过程
#1. 这种找路径,找方法的题一般可以使用回溯法来解决,回溯法也可以说是树形图法,解题的时候使用类似于树状图的结构,使用 自顶而下 的方法。
#2. 而在回溯法中,如果含有很多的重复的计算的时候,就可以使用记忆化的搜索,将可能出现的重复计算大状态使用一个数组来保存其值,在进行重复的计算的时候,就可以直接的调用数组中的值,较少了不必要的递归。
#3. 使用了记忆化搜索后,一般还可以进行优化,在记忆化搜索的基础上,变成 自底而上 的动态规划。
# 以上说明链接:https://leetcode.cn/problems/coin-change/solution/javadi-gui-ji-yi-hua-sou-suo-dong-tai-gui-hua-by-s/
##回溯算法(超时)
if amount==0:
return 0
res=[]
def backtracking(coins,cnt,tmp):
if tmp==amount:
res.append(cnt)
return
if tmp>amount:
return
for coin in coins:
cnt+=1
tmp+=coin
backtracking(coins,cnt,tmp)
cnt-=1
tmp-=coin
backtracking(coins,0,0)
if res:
return min(res)
else:
return -1
#记忆化搜索
#在回溯的基础上将可能出现的重复计算大状态使用一个数组来保存其值
#动态规划
dp=[float("inf")]*(amount+1)
dp[0]=0
for coin in coins:
for x in range(coin,amount+1):
dp[x]=min(dp[x],dp[x-coin]+1)
return dp[amount] if dp[amount]!=float("inf") else -1
记忆化搜索需要用到@functools.lru_cache()装饰器,在 Python 的 3.2 +版本中引入,即 functool 模块中的 lru_cache 装饰器,可以直接将函数或类方法的结果缓存住,后续调用则直接返回缓存的结果。
故此处的记忆化缓存用python3来实现
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
@functools.lru_cache(amount) ##该函数是一个装饰器,为函数提供缓存功能。在下次以相同参数调用时直接返回上一次的结果。
def dp(rem) -> int:
if rem < 0: return -1
if rem == 0: return 0
mini = int(1e9) ##
for coin in self.coins:
res = dp(rem - coin)
if res >= 0 and res < mini:
mini = res + 1
return mini if mini < int(1e9) else -1
self.coins = coins
if amount < 1: return 0
return dp(amount)
12.22. 括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
输入:n = 3
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]
class Solution(object):
def generateParenthesis(self, n):
"""
:type n: int
:rtype: List[str]
"""
#DFS
res=[]
def dfs(paths,left,right):#left:剩余左括号总数 right:剩余右括号总数
#首先要满足left<=n 且 right<=n
if left>n or right>left: #并且 规律是 剩余左括号总数要小于等于右括号,因为第一个一定是左括号
return
if len(paths)==2*n:
res.append(paths)
return
dfs(paths+"(",left+1,right)
dfs(paths+")",left,right+1)
dfs("",0,0)
return res
13. 2302. 统计得分小于 K 的子数组数目
一个数字的 分数 定义为数组之和 乘以 数组的长度。
比方说,[1, 2, 3, 4, 5] 的分数为 (1 + 2 + 3 + 4 + 5) * 5 = 75 。
给你一个正整数数组 nums 和一个整数 k ,请你返回 nums 中分数 严格小于 k 的 非空整数子数组数目。
子数组 是数组中的一个连续元素序列。
输入:nums = [2,1,4,3,5], k = 10
输出:6
解释:
有 6 个子数组的分数小于 10 :
- [2] 分数为 2 * 1 = 2 。
- [1] 分数为 1 * 1 = 1 。
- [4] 分数为 4 * 1 = 4 。
- [3] 分数为 3 * 1 = 3 。
- [5] 分数为 5 * 1 = 5 。
- [2,1] 分数为 (2 + 1) * 2 = 6 。
注意,子数组 [1,4] 和 [4,3,5] 不符合要求,因为它们的分数分别为 10 和 36,但我们要求子数组的分数严格小于 10 。
class Solution(object):
def countSubarrays(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
#滑动窗口
##枚举左端点,找符合条件的右端点数目,加到res中
## 超出时间限制 150/167
left=0
right=0
n=len(nums)
res=0
while left<n:
if sum(nums[left:right+1])*(right-left+1)>=k:
left+=1
right=left
else:
right+=1
res+=1
if right>n-1:
left+=1
right=left
return res
##优化
##双指针使用前提:
# 1. 子数组(连续);
# 2. 有单调性。本题元素均为正数,这意味着只要某个子数组满足题目要求,在该子数组内的更短的子数组同样也满足题目要求。
# 做法:枚举子数组右端点,去看对应的合法左端点的个数,那么根据上面的前提 2,我们需要求出合法左端点的最小值。
ans = s = lo = 0
for hi, num in enumerate(nums):
s += num
while s * (hi - lo + 1) >= k:
s -= nums[lo]
lo += 1
ans += hi - lo + 1
return ans
14. 347. 前 K 个高频元素
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
解法:哈希表+优先队列
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
//1.map记录元素出现的次数
unordered_map<int,int>map;//两个int分别是元素和出现的次数
for(auto& c:nums){ //传递引用比拷贝的速度快
map[c]++;
}
//2.利用优先队列,将出现次数排序
//自定义优先队列的比较方式,小顶堆
struct myComparison{
bool operator()(pair<int,int>&p1,pair<int,int>&p2){
return p1.second>p2.second;//小顶堆是大于号
}
};
//创建优先队列
priority_queue<pair<int,int>,vector<pair<int,int>>,myComparison> q;
//遍历map中的元素
//1.管他是啥,先入队列,队列会自己排序将他放在合适的位置
//2.若队列元素个数超过k,则将栈顶元素出栈(栈顶元素一定是最小的那个)
for(auto& a:map){
q.push(a);
if(q.size()>k){
q.pop();
}
}
//将结果导出
vector<int>res;
while(!q.empty()){
res.emplace_back(q.top().first);
q.pop();
}
return res;
}
};