目录
- 不邻接植花
- 分隔数组以得到最大和
- 找到小镇的法官
- 钥匙和房间
- 找到最终的安全状态
- 重新安排行程
1042. 不邻接植花
有 N 个花园,按从 1 到 N 标记。在每个花园中,你打算种下四种花之一。
paths[i] = [x, y]
描述了花园 x 到花园 y 的双向路径。
另外,没有花园有 3 条以上的路径可以进入或者离开。
你需要为每个花园选择一种花,使得通过路径相连的任何两个花园中的花的种类互不相同。
以数组形式返回选择的方案作为答案 answer,其中 answer[i]
为在第 (i+1)
个花园中种植的花的种类。花的种类用 1, 2, 3, 4
表示。保证存在答案。
示例 1:
输入:N = 3, paths = [[1,2],[2,3],[3,1]]
输出:[1,2,3]
示例 2:
输入:N = 4, paths = [[1,2],[3,4]]
输出:[1,2,1,2]
示例 3:
输入:N = 4, paths = [[1,2],[2,3],[3,4],[4,1],[1,3],[2,4]]
输出:[1,2,3,4]
处。
解题思路: 邻接矩阵(adjacency matrix)是图ADT最基本的实现方式。
- 首先用邻接表path_map构建地图,这里采用的是列表中的下表表示花园,列表中的元素还是列表,表示和这个列表相连接的花园。
- result存放结果,下标是花园,值是花的种类。
代码如下:
class Solution(object):
def gardenNoAdj(self, N, paths):
"""
:type N: int
:type paths: List[List[int]]
:rtype: List[int]
"""
# 思路: 对于当前的某一个花园,剔除掉与它邻接花园的花的种类,从剩下的种类中选一种即可。
# 1. 构建邻接矩阵G; 2. 用res列表保存当前花园花的种类
res = [0]*N
G = [[] for _ in range(N)]
for x, y in paths:
G[x - 1].append(y - 1)
G[y - 1].append(x - 1)
for i in range(N):
# 对于当前花园, 排除掉邻接的花园的花种就ok了,然后pop出一种
res[i] = ({1,2,3,4} - {res[j] for j in G[i]}).pop()
return res
1043. 分隔数组以得到最大和
给出整数数组 A,将该数组分隔为长度最多为 K 的几个(连续)子数组。分隔完成后,每个子数组的中的值都会变为该子数组中的最大值。
返回给定数组完成分隔后的最大和。
示例:
输入:A = [1,15,7,9,2,5,10], K = 3
输出:84
解释:A 变为 [15,15,15,9,10,10,10]
动态规划:
class Solution(object):
def maxSumAfterPartitioning(self, A, K):
"""
:type A: List[int]
:type K: int
:rtype: int
"""
n = len(A)
curmax = 0
res = [0 for _ in range(n)]
for i in range(len(A)):
if i < K:
curmax = max(A[i], curmax)
res[i] = curmax*(i+1)
else:
curmax = 0
for j in range(1, K+1):
curmax = max(A[i-j+1], curmax)
res[i] = max(res[i], res[i-j]+curmax*j)
return res[n-1]
997. 找到小镇的法官
在一个小镇里,按从 1 到 N 标记了 N 个人。传言称,这些人中有一个是小镇上的秘密法官。
如果小镇的法官真的存在,那么:
- 小镇的法官不相信任何人。
- 每个人(除了小镇法官外)都信任小镇的法官。
- 只有一个人同时满足属性 1 和属性 2 。
给定数组 trust
,该数组由信任对 trust[i] = [a, b]
组成,表示标记为 a 的人信任标记为 b 的人。
如果小镇存在秘密法官并且可以确定他的身份,请返回该法官的标记。否则,返回 -1。
示例 1:
输入:N = 2, trust = [[1,2]]
输出:2
示例 2:
输入:N = 3, trust = [[1,3],[2,3]]
输出:3
示例 3:
输入:N = 3, trust = [[1,3],[2,3],[3,1]]
输出:-1
示例 4:
输入:N = 3, trust = [[1,2],[2,3]]
输出:-1
示例 5:
输入:N = 4, trust = [[1,3],[1,4],[2,3],[2,4],[4,3]]
输出:3
解题思路:
- 分别用两个数组存储,一个用来存储入度数,也就是相信你的人的个数,数组下标就是代表该人,1到N
- 另外一个用来存储出度数,也就是你相信的人的个数
- 入度数等于N-1,出度数为0,则这个人就是法官,否则无法确定法官。
class Solution(object):
def findJudge(self, N, trust):
"""
:type N: int
:type trust: List[List[int]]
:rtype: int
"""
a=[0 for x in range(N+1)] #相信你的人
b=[0 for x in range(N+1)] #你相信的人
for num in trust:
a[num[1]]+=1 #储存为相信他的人
b[num[0]] +=1
for i ,num in enumerate(a):
if(i!=0 and num==N-1):
if b[i]==0:
return i
return -1
841. 钥匙和房间
有 N 个房间,开始时你位于 0 号房间。每个房间有不同的号码:0,1,2,…,N-1,并且房间里可能有一些钥匙能使你进入下一个房间。
在形式上,对于每个房间 i 都有一个钥匙列表 rooms[i]
,每个钥匙 rooms[i][j]
由 [0,1,...,N-1]
中的一个整数表示,其中 N = rooms.length
。 钥匙 rooms[i][j] = v
可以打开编号为 v 的房间。
最初,除 0 号房间外的其余所有房间都被锁住。
你可以自由地在房间之间来回走动。
如果能进入每个房间返回 true,否则返回 false。
示例 1:
输入: [[1],[2],[3],[]]
输出: true
解释:
我们从 0 号房间开始,拿到钥匙 1。
之后我们去 1 号房间,拿到钥匙 2。
然后我们去 2 号房间,拿到钥匙 3。
最后我们去了 3 号房间。
由于我们能够进入每个房间,我们返回 true。
示例 2:
输入:[[1,3],[3,0,1],[2],[0]]
输出:false
解释:我们不能进入 2 号房间。
解题思路:
有向图的深度优先搜索。
设置两个数组notVisited
、visited,
分别存放还未遍历过的房间号和已经遍历过的房间号。如果notVisited
数组为空说明我们已经遍历过所有房间,直接返回,如果当前房间已经遍历过了也直接返回。然后遍历钥匙串中的房间号。
代码如下:
代码如下:
class Solution(object):
def canVisitAllRooms(self, rooms):
"""
:type rooms: List[List[int]]
:rtype: bool
"""
notVisited = [i for i in range(1, len(rooms))]
visited = []
def dfs(curRoom, keys):
if len(notVisited) == 0:
return
if curRoom in visited:
return
visited.append(curRoom)
for key in keys:
if key in notVisited:
notVisited.remove(key)
dfs(key, rooms[key])
dfs(0, rooms[0])
return True if len(notVisited) == 0 else False
802. 找到最终的安全状态
思路分析:典型的图深度优先搜索。
dfs访问图,当前访问路径上节点状态待定,为-1:
- 访问完当前路径,未发现环,则所有节点状态标记为1(已访问节点)。
- 发现环,所有节点状态不变,为-1(环上节点)。
class Solution(object):
def eventualSafeNodes(self, graph):
"""
:type graph: List[List[int]]
:rtype: List[int]
"""
nodes_status = [0] * len(graph)
safe_nodes = []
for i in range(len(graph)):
if nodes_status[i] == 0:
self.dfs(graph, i, nodes_status, safe_nodes)
return sorted(safe_nodes)
def dfs(self, graph, i, nodes_status, safe_nodes):
nodes_status[i] = 1
flag = False
for sub_node in graph[i]:
if nodes_status[sub_node] == 0:
if self.dfs(graph, sub_node, nodes_status, safe_nodes):
flag = True
elif nodes_status[sub_node] == 1:
flag = True
if not flag:
safe_nodes.append(i)
nodes_status[i] = 2
return flag
332. 重新安排行程
class Solution(object):
def findItinerary(self, tickets):
"""
:type tickets: List[List[str]]
:rtype: List[str]
"""
tickets.sort()
hel = [False for i in range(len(tickets))]
def dfs(s,res):
if len(res)==len(tickets)+1:
return res
for i in range(len(tickets)):
if not hel[i] and tickets[i][0]==s:
hel[i] = True
r = dfs(tickets[i][1],res+[tickets[i][1]])
if not r:
hel[i] = False
else:return r
return dfs('JFK',['JFK'])
图的定义,在python中使用字典来定义
树和图的遍历,主要区别是树有根节点,所以指定了,而图需要我们自身指定开始遍历的节点,起始节点的不同会导致遍历节点的不同。
# 定义一个图的结构
from collections import deque
graph = {
'A': ['B', 'C'],
'B': ['A', 'C', 'D'],
'C': ['A', 'B', 'D', 'E'],
'D': ['B', 'C', 'E', 'F'],
'E': ['C', 'D'],
'F': ['D']
}
# bfs 广度优先搜索:层序遍历,以入度为切入点的广度优先搜索算法
def BFS(graph, s, target): # graph图 ,s指的是开始节点
search_queue = deque() # 创建一个队列
search_queue += graph[s]
searched = [] # 记录访问过的节点
while search_queue:
node = search_queue.popleft() # 先进先去
if node not in searched: # 未遍历的节点
if node == target:
return True
else:
search_queue += graph[node]
searched.append(node)
return False
# 课程表
import collections
class Solution:
def canFinish(self, numCourses, prerequisites):
# 储存有向图,用字典存放,键对应值list的先修课程
edges = collections.defaultDict(list) # 键不存在,返回的是空list
# 新建一个数组存放每个节点的入度
indeg = [0] * numCourses
# 整型变量用于记录能够学习的课程
result = []
# 对入度list,和图填充真实的数据
for info in prerequisites:
edges[info[1]].append(info[0])
indeg[info[0]] += 1
q = collections.deque([u for u in range(numCourses) if indeg[u] == 0]) # 队列
while q:
u = q.popleft()
result.append(u)
for v in edges[u]:
indeg[v] -= 1
if indeg[v] == 0:
q.append(v)
if len(result) == numCourses:
return True
else :
return False
dfs 深度优先搜索:回溯法。一直往前走,走不下去往回跳。以出度为切入点的深度优先算法
class Solution:
def canFinish(self, numCourse, prerequisites):
# 储存有向图,键为值对应的的先修课程
edges = collections.defaultDict(list)
# 标记每个节点的状态:0=未搜索,1=搜索中,2=已完成
visited = [0] * numCourse # 多入度、多出度的情况
result = []
# 判断有向图是否有环
invalid = False
for info in prerequisites:
edges[info[1]].append(info[0])
def dfs(u): # 递归层调用,层层返回,每一层对应下,对应一层结果返还
nonlocal invalid
visited[u] = 1
# 中间是子节点的递归调用
for v in edges[u]: # 子节点标记为2的continue
if visited[v] == 0:
dfs[v] # 递归,将最深的节点加入到result里面
if invalid: # 如果有错误,一层层返回函数结果
return
elif visited[v] == 1: # 子节点标记为1,有环
invalid = True
return # 返回上一层行数,并且把错误标记置为True
# 函数出口,即遍历到最后的节点时,需要跳出函数
visited[u] = 2 # 当没有子节点的时候节点的标记置为2
result.append(u) # 返回上一步函数
for i in range(numCourse):
if not invalid and not visited[i]:
dfs(i)
return len(result) == numCourse
记忆化搜索算法
记忆化搜索实际上是递归来实现的,但是递归的过程中有许多的结果是被反复计算的,这样会大大降低算法的执行效率。
而记忆化搜索是在递归的过程中,将已经计算出来的结果保存,当之后的计算用到的时候直接取出结果,避免重复运算
# 329 求二维矩阵中的最长递增路径。一般来说DFS需要有固定的起点,但是这个问题,二维矩阵中的每一个位置都算作起点
# 方法1 记忆化DFS ,记忆:以该元素作为开始元素的最长路径
# 矩阵走格子
class Solution:
def longestIncresasingPath(self, matrix):
if not matrix:
return 0
len_r = len(matrix)
len_c = len(matrix[0])
dirs = [[1, 0], [-1, 0], [0, 1], [0, -1]] # 定义方向矩阵
ans = 0
mon = [[0] * len_c for _ in range(len_r)] # 记忆矩阵,用于集合每个元素开始的最长递增路径
for nr in range(len_r):
for nc in range(len_c):
ans = max(ans, self.dfs(matrix, nr, nc, dis, mon))
return ans
def dfs(self, matrix, r, c, dirs, mon): # 递归函数的参数
if mon[r][c]!=0: # 函数出口1:这一步的位置结果已经标记
return mon[r][c]
longest = 1
# 循环迷宫
for d in dirs:
x = r + d[0] # 横坐标
y = c + d[1] # 纵坐标
# 判断是否可以走下一步
if x >= 0 and x <= len(matrix) and y >= 0 and y < len(matrix[0]) and matrix[r][c] < matrix[x][y]: # 边界和递增判断
longest = max(longest, 1 + self.dfs(matrix, x, y, dirs, mon)) # 每往前递归一层,长度+=1
mon[r][c] = longest # 出口二,一直走到终点,递归结束,将结果返回上一层函数
return mon[r][c] # 并且将路径循环计算的结果反向一层层保存记忆