2.3
回溯算法,bfs搜索,研究了好久,最后还是看题解
class Solution:
def getMaximumGold(self, grid):
m = len(grid)
n = len(grid[0])
mon=[]
def dfs(x, y, gold):
rec = grid[x][y] #保存当前位置的数据
gold = gold + rec
mon.append(gold)
grid[x][y] = 0 #将走过的路变为零,防止走回头路
for nx, ny in ((x - 1, y), (x + 1, y), (x, y + 1), (x, y - 1)):
if 0 <= nx < m and 0 <= ny < n and grid[nx][ny] != 0:
dfs(nx, ny, gold)
grid[x][y] = rec #复原,回溯
for i in range(m):
for j in range(n):
if grid[i][j] != 0:
dfs(i, j, ans)
return max(mon)
纯暴力,时间复杂度O(k*n)
class Solution:
def maxSlidingWindow(self, nums, k: int):
max_nums=[]
for i in range(len(nums)):
max_win=max(nums[i:i+k]) #获取每个窗口的最大值
max_nums.append(max_win)
if i+k>=len(nums):
break
return max_nums
优化一下下,时间复杂度最快O(n),没有进入过取max更新的方法,最慢O(k*n),一直在取max()
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
def ret_max(max_num,new_num,win): #不再是直接取max,而是根据加进来的数据判断是否要取max
if max_num in win and new_num<=max_num: #加进来的数没原来的数大,而且原来的数还在里面,不更新max_num
return max_num
elif new_num>max_num: #加进来的数比原来的数大,更新
return new_num
else:
return max(win) #一直到最大的数被踢出窗口都没更新数据,直接在现在的窗口取max更新数据
arr=nums[0:k]
max_nums=[(max(arr))]
for i in nums[k:]:
arr.pop(0)
arr.append(i)
win_max=ret_max(max_nums[-1],i,arr)
max_nums.append(win_max)
return max_nums
学习一下优先队列
因为heap构造优先队列默认构造小根堆,把最小的放前面,所以可以把往里面加的数取反,这样最小的数就会一直在前面
class Solution:
def maxSlidingWindow(self, nums, k: int):
q=[(-nums[i],i) for i in range(k)]
heapq.heapify(q)
ans=[-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)
ans.append(-q[0][0])
return ans
2.4
图+回溯
from collections import defaultdict
from typing import List
class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
tickets_dict = defaultdict(list)
for item in tickets:
tickets_dict[item[0]].append(item[1]) #构建一张图
path = ["JFK"]
def backtracking(start_point):
# 终止条件,走过的路的数量比总共的机票多一个起点就行
if len(path) == len(tickets) + 1:
return True
tickets_dict[start_point].sort() #字典序排序
for _ in tickets_dict[start_point]:
end_point = tickets_dict[start_point].pop(0) #走过的路删除,避免死循环
path.append(end_point)
if backtracking(end_point): #只要有一个返回了True,结束
return Ture
path.pop() #回溯
tickets_dict[start_point].append(end_point) #复原图
backtracking("JFK")
return path
回溯算法递归不能细想,细想就炸了,自己写了好久,总是到了终点就停,没有走完全部路径
2.5
纯暴力,时间复杂度O(n^2),只要把两个排序后,比较一下大小就行,一个孩子依次从小到大吃所有饼干,吃到可以满足的,删除这块饼干并返回让下一个孩子吃
class Solution:
def findContentChildren(self, g: List[int], s: List[int]) -> int:
g.sort()
s.sort()
cot=0
for i in g:
for j in s:
if i<=j:
cot+=1
s.remove(j)
break
return cot
优化,双指针,时间复杂度O(n)
class Solution:
def findContentChildren(self, g: List[int], s: List[int]) -> int:
g.sort()
s.sort()
cot = 0
i,j=0,0
while True:
if i>=len(g) or j>=len(s):
break
if g[i]<=s[j]:
cot+=1
j+=1
i+=1
continue
j+=1
return cot
class Solution:
def wiggleMaxLength(self, nums):
#设置两个数,保存两次数据一个持续更新,一个只有在满足摆动序列的情况下更新
preC, curC, res = 0, 0, 1 #如果序列长度为1的话,就进不了循环,所以将序列初始长度设置为1
for i in range(len(nums) - 1):
curC = nums[i + 1] - nums[i] #相邻两个的差值
print(curC,preC)
if curC * preC <= 0 and curC != 0:
res += 1
preC = curC
return res
O(n^2)的算法,把范围内的数加起来,然后找最大的就行
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
max_num=nums[0]
sums=0
for i in range(len(nums)):
for j in range(i,len(nums)):
sums=sums+nums[j]
if sums>max_num:
max_num=sums
sums=0
return max_num
看大佬的思路优化了一下下,时间复杂度O(n),没必要在全部加完后才进行初始化,从下一个点开始,只要在发现前面加起来的数小于0了,直接舍去前面所有的数,直接归零,重复,如果后面的数没有前面大,那么就不更新最大的数
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
max_rec=nums[0] #这里不用最小的数,只要取第一个数作为初始值就行
cot=0
for i in nums:
cot = cot + i
if cot >max_rec:
rec=cot
if cot<0:
cot=0 #如果小于0了,归零,从新开始累加
return rec
2.6
只要第二天大于第一天,那么立刻结算利润
class Solution:
def maxProfit(self, prices: List[int]) -> int:
ans=0
for i in range(len(prices)-1):
if prices[i+1]-prices[i]>0:
ans=prices[i+1]-prices[i]+ans
return ans
一开始被绕进去了,没有立刻结算,而是维护了买入和要卖出的时候的价钱,卖出的时候再结算利润,所以代码写的很复杂
class Solution:
def maxProfit(self, prices: List[int]) -> int:
start_num=0
profit=0
now_num=0
bol=True
for i in range(len(prices)-1):
if prices[i+1]-prices[i]>0:
if bol:
start_num=prices[i]
bol=False
now_num=prices[i+1]
elif prices[i+1]-prices[i]<=0 and not bol:
profit=now_num-start_num+profit
start_num=0
bol=True
if now_num==prices[-1] and prices[-1]!=prices[-2]:
profit = now_num - start_num + profit
return profit
每一步都跳一下看看,维护一个跳的最远的位置,作为当前的位置,
判断是否能跳:当现在的位置被开始跳的下标超过,那么就说明这一步到不了了,不能跳
class Solution:
def canJump(self, nums: List[int]) -> bool:
cot = 0
for i in range(len(nums)):
if i <= cot: #这一步想了好久都没想到,判断是否能跳的条件
cot = max(nums[i] + i, cot) # nums[i]+i,下一步跳的位置,cot现在的位置
if i + nums[i] >= len(nums)-1:
return True
return False
2.7
这题之前写过,没写出来
class Solution:
def jump(self, nums):
cot = 0
max_jump = 0
now_jump = 0
if len(nums)==1: #如果长度为1那么不用走,直接就是终点,返回0
return 0
for i in range(len(nums)):
max_jump = max(nums[i] + i, max_jump) #持续更新在(i,nums[i]+i)范围内最大的数
if i == now_jump: #走到了范围内能走的最远的路
now_jump = max_jump #把当前位置更新为在i,nums[i]+i范围内最大的数
cot+=1 #跳一步
if now_jump >= len(nums) - 1: #如果已经到了终点返回
return cot
先跳,跳的同时维护一个能走的范围内的最大的值,走到了范围外时,准备走下一步,这下一步的步数就是一直在维护的范围内的最大值,
由每一步的最大值得出整体的最优解,果然贪心算法还是得多刷题,不然根本想不到怎么实现
class Solution:
def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
cot = 0
nums.sort()
for i in range(k):
for j in range(len(nums)):
if nums[j] < 0:
nums[j] = -nums[j]
cot += 1
break
if (k-cot)%2!=0 and cot<k:
min_num=min(nums)
nums.remove(min_num)
nums.append(-min_num)
return sum(nums)
时间复杂度(O(kn+nlogn),勉强能过
优化(我都用上排序了我还用两个for循环嵌套干嘛)
class Solution:
def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
cot = 0
nums.sort()
for j in range(len(nums)):
if nums[j] < 0:
nums[j] = -nums[j]
cot += 1
if cot==k: #优化点,只算次数,消耗完次数立刻返回
return sum(nums)
if (k-cot)%2!=0 and cot<k:
min_num=min(nums)
nums.remove(min_num)
nums.append(-min_num)
return sum(nums)
时间复杂度变为(O(n+nlogn),nlog是排序所需要的时间
总感觉用内置的排序算法有点作弊
2.8
第一题,当时写的
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
for i in range(0, len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return i,j
当时想的管他那么多,先上两个for循环,结果一看时间
现在想起来了,过来优化一下,达不到O(n)的水平,但比O(n^2)快多了
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
for i in range(0, len(nums)):
a=target-nums[i]
if a in nums[i+1:]:
nums.remove(nums[i])
return [i,nums.index(a)+1]
嗯,舒服了
今天来题难的,9月份就想解决的一题
先去玩回数独
首先要写一个判断能不能填的方法,返回bool,随时调用
def is_valid(x,y,val,borad):
for i in range(9):
if str(key)==borad[x][i]: #行
return False
if str(key)==borad[i][y]: #列
return False
row_x=(x//3)*3
col_y=(y//3)*3 #获取数字所在宫的位置
for i in range(row_x,row_x+3): #宫
for j in range(col_y,col_y+3):
if str(val)==borad[i][j]
return False
#都没有说明能填,返回True
return True
def backtracking(borad ):
for i in range(9):
for j in range(9):
if borad[i][j]!='.':
continue #有数字,不能填
for k in range(1,10):
if is_valid(i,j,k,borad):
borad[i][j]=str(k)
if backtracking(borad): #填一次数走一遍判断流程,不能填的复原,能填的先填上,下一个数再走一遍流程
return True
borad[i][j]='.' #不能填,复原
return False #如果1-9都填过了,没一个返回True 那么直接返回False,回退一步
return True
这种方法就是拿数字一个一个去试,这个合规,那么就填下一个,如果下一个一个数字都填不进去,那么上一个数字一定是违规的,回退一步
时间复杂度非常高,纯暴力,
总体代码:
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
def backtracking(borad ):
for i in range(9):
for j in range(9):
if borad[i][j]!='.':
continue #有数字,不能填
for k in range(1,10):
if is_valid(i,j,k,borad):
borad[i][j]=str(k)
if backtracking(borad):
return True
borad[i][j]='.' #不能填,复原
return False #如果1-9都填过了,没一个返回True 那么直接返回False,回退一步
return True
def is_valid(x,y,val,borad):
for i in range(9):
if str(val)==borad[x][i]: #行
return False
if str(val)==borad[i][y]: #列
return False
row_x=(x//3)*3
col_y=(y//3)*3 #获取数字所在宫的位置
for i in range(row_x,row_x+3): #宫
for j in range(col_y,col_y+3):
if str(val)==borad[i][j]:
return False
#都没有说明能填,返回True
return True
backtracking(board)
这题写了我两天,还去玩了几把数独,终于看懂了别人代码,写了出来
2.10
纯暴力
def canCompleteCircuit(self, gas: List[int], cost: List[int]):
for i in range(len(gas)):
rest = gas[i] #记录起点送的油
add = [k for k in gas[i + 1:len(gas)]] + [k for k in gas[0:i + 1]] #每一站加油的路线
reduce = [k for k in cost[i:len(cost)]] + [k for k in cost[0:i]] #每一站要花掉的油,因为消耗的油是cost[i] 所以要比要加的油慢一步
for l in range(len(add)):
if rest - reduce[l] < 0: #如果走不到则返回
break
rest = rest - reduce[l] + add[l]
if add[l] == add[-1] and reduce[l] == reduce[-1]: #到了终点,退出
return i
return -1 #都走完了,则说明无法到达,返回-1
一顿操作猛如虎,一看击败5%
优化
换种思路,首先,如果sum(gas)-sum(cost),那么就不可能到达终点,直接返回-1
因为耗油总和是大于零的,所以必定可以到达终点,从0开始一个一个当成起点,如果有一个站到不了,那么就说明在这个站之前的数都是不能当作起点的,所以下一个起点应该从i+1开始,直到跑完全程
class Solution:
def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
start = 0 #起点
curSum = 0 #剩余油量
if sum(gas) - sum(cost) < 0: #如果跑不完直接返回
return -1
for i in range(len(gas)):
curSum += gas[i] - cost[i] #计算剩余由量
if curSum < 0:
curSum = 0 #剩余油量归零
start = i + 1 #起点从i+1从新开始
return start
2.11
def maxProfit(self, prices: List[int], fee: int) -> int:
result = 0
minPrice = prices[0]
for i in range(1, len(prices)):
if prices[i] < minPrice:
minPrice = prices[i] #买入
elif prices[i] - fee > minPrice: # 售价加上卖出所需要的手续费还大于最小售价,那么就可能有得赚,卖出
result += prices[i] - minPrice - fee # 计算利润,minPrice = prices[i] - fee,那么总体就是:
# prices[i] -(minPrice-fee) - fee=prices[i] - minPrice
# 有可能会多次计算利润,当最后一次计算利润的时候才是真正的卖出
minPrice = prices[i] - fee #当前卖出并不一定是最大获利,所以要记录当前卖出能赚多少钱
return result
这题比上次做的不需要手续的难了很多,因为要考虑三种情况了,因为有手续费的存在,所以要在最少交易次数的情况下拿到最多的钱,上一题只要能赚就能卖,minPrice = prices[i] - fee 这一步很巧妙,如果我自己来绝对想不到
2.12
模拟过程:
class Solution:
def lemonadeChange(self, bills: List[int]) -> bool:
nums = []
for i in range(len(bills)):
nums.append(bills[i])
zhao = bills[i] - 5
if sum(nums) < zhao:
return False
if zhao== 0:
continue
if zhao == 5:
if 5 in nums:
nums.remove(5)
else:
return False
if zhao == 15:
if 10 in nums:
nums.remove(10)
if 5 in nums:
nums.remove(5)
else:
return False
else:
if nums.count(5) >= 3:
nums.remove(5)
nums.remove(5)
nums.remove(5)
else:
return False
return True
感觉没用什么算法,只有判断和删除,硬要说算法的话就是收到20的先找10块钱的,感觉这是生活经验,时间复杂度O(n^2)
不带零钱卖什么柠檬水,哪有柠檬水卖这么贵的,现在都是用的微信支付宝,既然是排队购买,就不能让有零钱的先来吗
吐槽完毕,现在开始优化
不需要用一个整个数组去存放得到的钱,因为只要三种钱,而20的毫无卵用,所以只需要设置两个变量,five,ten来记录自己得到的钱方便找钱就行
class Solution:
def lemonadeChange(self, bills: List[int]) -> bool:
five=0
ten=0
for i in range(len(bills)):
zhao = bills[i] - 5
if zhao== 0:
five+=1
continue
if zhao == 5:
ten+=1
if five>0:
five-=1
else:
return False
if zhao == 15:
if ten>0:
ten-=1
if five>0:
five-=1
else:
return False
else:
if five >= 3:
five-=3
else:
return False
return True
摸了
2.14
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
if not n: return []
board = [['.'] * n for _ in range(n)]
res = []
def isVaild(board,row, col):
#判断同一列是否冲突,不用判断同一行,因为单层递归只会选择同一行里的元素
for i in range(len(board)):
if board[i][col] == 'Q':
return False
# 判断左上角是否冲突
i = row -1
j = col -1
while i>=0 and j>=0:
if board[i][j] == 'Q':
return False
i -= 1
j -= 1
# 判断右上角是否冲突
i = row - 1
j = col + 1
while i>=0 and j < len(board):
if board[i][j] == 'Q':
return False
i -= 1
j += 1
return True
def backtracking(board, row, n):
if row == n:
temp_res = []
for temp in board: #终止条件
temp_str = "".join(temp) #把二维列表转为一维
temp_res.append(temp_str)
res.append(temp_res)
for col in range(n):
if not isVaild(board, row, col):
continue
board[row][col] = 'Q'
backtracking(board, row+1, n)
board[row][col] = '.' #回溯
backtracking(board, 0, n)
return res