之前做的二十多道题都没有写笔记,从今天开始写笔记,记录做MarsCode AI算法题的思路和方法
11.4 简单找出整型数组中占比超过一半的数
问题描述
小R从班级中抽取了一些同学,每位同学都会给出一个数字。已知在这些数字中,某个数字的出现次数超过了数字总数的一半。现在需要你帮助小R找到这个数字。
最初思路
这道题非常的简单,我是采用一个暴力的方式对数组进行遍历,复杂度也只有O(n2)
- 第一步把数组排序,使用python内置的
sorted()函数,记得要把返回值传给array - 第二部对数组进行遍历,从第一个数字开始遍历,如果遇到了新的数字就将times重置为1,否则加1,用num记录当前遍历的数字,times记录当前数字已出现的次数。如果times要大于一半的数了就记录到wanted_num中。 注意:如果算上sort的复杂度,如果是冒泡排序?O(n2)
代码
def solution(array):
# Edit your code here
array = sorted(array) # 排序
n = len(array) # 字符串长度
num = array[0] # 记录当前遍历的数字
want_num = num
times = 1 # 记录当前数字出现的次数
for i in range(1,len(array)):
if array[i] == num:
times += 1
if times>=n/2:
want_num = num
else:
num = array[i]
times = 1
return want_num
对以上方法的改进
其实题目说了找的众数已经超过了总数的一半,所以排序后的数列中间一定就是要求的数~根本不需要后面我那么大费周章,一行代码即可!:
def solution(array):
# Edit your code here
array = sorted(array) # 排序
return array[int(len(array)/2)]
MarsCode AI对我的点播——摩尔投票
AI提到了另一种方法:摩尔投票
摩尔投票算法(Moore's Voting Algorithm)是一种在数组或序列中查找出现次数超过一半的主要元素的算法。这个算法的关键思想是通过不同元素之间的抵消来找到可能的主要元素候选者,并在最后验证候选者是否满足要求。时间和空间复杂度都非常低!O(n)和O(1)
需要用candidate存放候选数字,vote存放投票
依次遍历array中的元素,如果当前元素与候选数一致,则投票数加一,否则投票数减一,直到投票数为0,更换新的候选人。
以下是摩尔投票的实现:
def solution(array):
# Edit your code here
candidate = array[0]
vote = 0
for i in range(len(array)):
if vote == 0:
candidate = array[i]
vote = 1
elif array[i]==candidate:
vote += 1
elif array[i]!=candidate:
vote -= 1
return candidate
11.5 简单小F的永久代币卡回本计划
问题描述
小F最近迷上了玩一款游戏,她面前有一个永久代币卡的购买机会。该卡片的价格为 a 勾玉,每天登录游戏可以返还 b 勾玉。小F想知道她至少需要登录多少天,才能让购买的永久代币卡回本。
思路
这道题非常简单,就是要得到的结果向上取整即可 代码:
def solution(a: int, b: int) -> int:
# write code here
return int(a/b) if a%b==0 else int(a/b)+1
浅浅的改进
python中的/会返回浮点类型,而可以使用//来取整,从而省略int类型强制转换 代码:
def solution(a: int, b: int) -> int:
# write code here
return a//b if a%b==0 else a//b+1
11.8 简单比赛配对问题
问题描述
小R正在组织一个比赛,比赛中有 n 支队伍参赛。比赛遵循以下独特的赛制:
- 如果当前队伍数为 偶数,那么每支队伍都会与另一支队伍配对。总共进行
n / 2场比赛,且产生n / 2支队伍进入下一轮。 - 如果当前队伍数为 奇数,那么将会随机轮空并晋级一支队伍,其余的队伍配对。总共进行
(n - 1) / 2场比赛,且产生(n - 1) / 2 + 1支队伍进入下一轮。
小R想知道在比赛中进行的配对次数,直到决出唯一的获胜队伍为止。
思路
根据题目要求计算当前轮次的比赛数并不断更新队伍数,一个循环即可解决,这里还用到了之前提到的//来得到除法整数结果:
def solution(n: int) -> int:
# write code here
games = 0
while n>1:
if n%2 == 1:
# 当前队伍数为奇数
g = (n-1)//2
n = g+1 # 下一轮比赛的数量
else:
g = n//2
n = g # 下一轮比赛的数量
games += g
return games
改进
其实这道题根本不用循环进行比赛,由于最终需要角逐出一支队伍,其他队伍全部被淘汰,由于只要淘汰就一定是进行了配对,所以是会进行n-1次配对来淘汰n-1支队伍:
def solution(n: int) -> int:
# write code here
return n-1
有时候一些问题需要换一个方向来思考,结果就会显而易见了。
11.9 简单小U的数字插入问题
问题描述
小U手中有两个数字 a 和 b。第一个数字是一个任意的正整数,而第二个数字是一个非负整数。她的任务是将第二个数字 b 插入到第一个数字 a 的某个位置,以形成一个最大的可能数字。
你需要帮助小U找到这个插入位置,输出插入后的最大结果。
思路
这道题可以直接使用暴力解决,要注意的几个点是:
- 边界情况—— b为零的时候,不需要进行插入判断,放在最后肯定是最大的
- 把数字转换为字符串,方便插入,但在比较时又要转回为数字
- 遍历a字符串的过程中,使用enumerate(a_str)来遍历,这样做缺少了对把b直接添加到a末端的情况的判断,所以代码最后加上了这个判断。
- 用maxres存放最大结果,pos存放最大结果的插入b的位置 代码如下:
def solution(a: int, b: int) -> int:
if b==0:
return a*10
maxres = a
for index,n in enumerate(a_str):
res = a_str[:index]+str(b)+a_str[index:]
if int(res)>maxres:
maxres = int(res)
pos = index
res = int(str(a)+str(b))
if res>maxres:
return res
return int(a_str[:pos]+str(b)+a_str[pos:])
11.11 简单我好想逃却逃不掉
题目描述:
曾经的我不过是一介草民,混迹市井,默默无名。直到我被罗马的士兵从家乡捉走丢进竞技场……
对手出现了,我架紧盾牌想要防御,只觉得巨大的冲击力有如一面城墙冲涌而来,击碎了我的盾牌,我两眼发昏,沉重的身躯轰然倒地。
——我好想逃。
但罗马最大的竞技场,哪有这么容易逃得掉。工程师们早就在地上装了传送机关,虽不会伤人,却会将站在上面的人传到它指向的位置。若是几个传送机关围成一个环,不小心踩在上面的人就会被一圈圈地反复传送……想到这里,我不由得打了个寒颤。必须避开这些危险的地方!
你的任务是找出在迷宫的哪些位置,当玩家移动到此处时,无论接下来如何移动都无法再到达出口。
思路
这道题其实就是一个递归走迷宫的问题。
- 起始点有M×N个,需要求的所有图上的点出发,是否能走到出口,因此最外层的循环是M×N的。
- 想要判断一个点是否能走到出口,就要遍历从该点出发的所有路径,只要有一条能够到达出口,就不是死路,如果遍历完所有路径仍然没有走到出口,该点就被标记为“危险位置”。
- 走迷宫通过递归完成,不过要同时传入一个visited列表,记录“来时的路”。走到传送点时,往对应的方向走一步,走到普通格子则遍历未走过的其他方向。
- 什么时候让递归返回false呢?走到迷宫之外了或是走回来时的路了,就说明该条路径已经没有意义。
代码:
def solution(N, M, data):
# 构建迷宫矩阵
maze = data.copy()
# 方向映射
directions = {
'U': (-1, 0), # 上
'D': (1, 0), # 下
'L': (0, -1), # 左
'R': (0, 1) # 右
}
def is_valid(x, y): # 检查是否到达边界
return 0 <= x < N and 0 <= y < M
def can_reach_exit(start_x, start_y, visited):# 从一个起点出发,递归地找终点
if not is_valid(start_x, start_y): # 判断当前点是否越界
return False
# 如果已经访问过,说明该路径已经没有意义
if (start_x, start_y) in visited:
return False
# 到达出口
if maze[start_x][start_y] == 'O':
return True
# 记录当前位置为已访问
visited.add((start_x, start_y))
# 如果是传送点
if maze[start_x][start_y] in directions:
dx, dy = directions[maze[start_x][start_y]] # 取出该传送点的方向并转换为方向向量(0,1),(1,0)...
next_x, next_y = start_x + dx, start_y + dy # 计算下一步到达的位置
return can_reach_exit(next_x, next_y, visited) # 继续遍历下一个位置
# 如果是普通点,尝试四个方向
for dx, dy in directions.values():
next_x, next_y = start_x + dx, start_y + dy # 计算该方向的下一步的位置
if can_reach_exit(next_x, next_y, visited.copy()): #
return True #
return False
# 统计无法到达出口的位置数量
count = 0
for i in range(N):
for j in range(M):
if maze[i][j] != 'O': # 不考虑出口本身
if not can_reach_exit(i, j, set()):
count += 1
return count
11.12 简单小D的‘abc’变换问题
问题描述
小D拿到了一个仅由“abc”三种字母组成的字符串。她每次操作会对所有字符同时进行以 下变换:
- 将‘a’变成’bc‘
- 将’b‘变成‘ca’
- 将‘c’变成’ab‘ 小D将重复该操作 k 次。你的任务是输出经过 k 次变换后,得到的最终字符串。 例如: 对于初始字符串“abc",执行 2 次操作后,字符串将变为“caababbcbcca"
思路
又是一道可以用递归解决的问题,对于字符串“abc”的k次变换,可以看成将'a'进行k次变换得到的字符串+将"b"进行k次变换得到的字符串+将"c"进行k次变换得到的字符串 每次递归传入当前变换的字符和剩余要变换的次数 递归的结束条件是剩余变换次数为0,此时直接返回当前字符;否则根据当前传入的字符继续进行变换。(判断结束时应该返回什么时可以想想当变换次数为0时函数应该返回什么,例如对”a“变换0次,返回的就是”a“本身)代码如下:
代码
def solution(s: str, k: int) -> str:
# write code here
# 尝试使用递归解决
def change(c: str, n: int)->str:
# 递归结束条件
if n==0:# 变换次数为0时结束变换
return c
if c=="a":return change("b",n-1)+change("c",n-1) # 遇到a则继续对bc进行变换
elif c=="b":return change("c",n-1)+change("a",n-1)
return change("a",n-1)+change("b",n-1)
res = ""
for i in s: # 遍历字符串,对每个字符进行变换
res += change(i,k)
return res