2020-07算法刷题集
前言
前段时间一直忙于上课与其它事情,一直都没有抽出时间来刷算法题,现在看来,浪费了许多时间,非常后悔。
错过了昨天,今天将是一个非常好的起点。所以重新开始刷算法题。
(2020-07-25)这段时间每天刷着一道简单难度和一道中等难度的题目,花费的时间也不多。只是总在询问自己刷题的意义在什么地方?今天有了能让自己满意的答案——享受于每一个逻辑和细节处理,享受每一个字符,每一句代码所造成的影响。或许有时会急躁于一个个疏忽,但最终都会明白,急躁并不能解决问题,静下来才能知道问题出在哪里。问题的形成和解决是一个过程,需要经验,需要冷静,同样也需要跨越自己现在的位置。
- 来源
力扣(LeetCode)-1154-一年中的第几天 - 问题描述
- 给你一个按 YYYY-MM-DD 格式表示日期的字符串 date,请你计算并返回该日期是当年的第几天。
通常情况下,我们认为 1 月 1 日是每年的第 1 天,1 月 2 日是每年的第 2 天,依此类推。每个月的天数与现行公元纪年法(格里高利历)一致
- 示例
示例 1:
输入:date = “2019-01-09”
输出:9
示例 2:
输入:date = “2019-02-10”
输出:41
- …
- 算法分析
- 获取关键
- 输入:字符串,以“-”分隔,公元纪年法
- 返回:第几天
- 处理逻辑
- 这道题都没什么好说的,有些以下常识即可完成
- 一年有12个月,有两类,一种是平年,二月有28天,一种是闰年,二月有29天
- 平闰年的区别:闰年可以被4整除。但是,如果是100的倍数的话,必须被400整除,才是闰年。例如1900年可以被4整除,但由于是100的倍数,所以应该被除于400,不能被整除,所以是平年,二月有28天;2000年能被400整除,是闰年。二月有29天
- 1、3、5、7、8、10、12这些月份有31天
- 2、4、6、9、11这些月份有30天
- 细节处理
- 100整倍年需要除于400,其它也没啥了
- 代码实现
class Date(object):
def __init__(self,dateStr):
""" 2010-10-09 """
self.date = dateStr
def run(self):
year = int(self.date[:4])
month = int(self.date[5:7])
day = int(self.date[-2:])
""" 1、3、5、7、8、10、12 """
daysList = [31,28,31,30,31,30,31,31,30,31,30,31]
if year % 100 == 0:
if year % 400 == 0:
""" 闰年 """
daysList[1] = 29
else:
if year % 4 == 0:
daysList[1] = 29
result = 0
for i in range(month-1):
result += daysList[i]
result += day
return result
if __name__ == "__main__":
dateObj = Date('2019-02-10')
print(dateObj.run())
#41
运行结果:
- 思考
题目关键词:关于日期的常识
这道题没什么难度,但是如果不具备日期的常识,将无法解题。这是比较关键的。所以,应该多刷一些关于常识的题目,积累逻辑处理的流程之外,更重要的是知道这些常识。
- 来源
力扣(LeetCode)-592-分数加减运算 - 问题描述
- 给定一个表示分数加减运算表达式的字符串,你需要返回一个字符串形式的计算结果。 这个结果应该是不可约分的分数,即最简分数。 如果最终结果是一个整数,例如 2,你需要将它转换成分数形式,其分母为 1。所以在上述例子中, 2 应该被转换为 2/1。
- 示例 1:
输入:"-1/2+1/2"
输出: “0/1”
- 示例 2:
输入:"-1/2+1/2+1/3"
输出: “1/3”- …
- 算法分析
- 获取关键
- 输入:字符串,加减
- 返回:最简,字符串,分数
- 处理逻辑
- 从字符串中获取分数对象,并获取对应的运算符,+或者-
- 对所有对象进行通分,即需要求得所有分数对象的分母的最小公倍数
经过这一步处理后,所有的分数的分母将会统一,为最小公倍数- 对所有的分数对象的分子进行乘积处理,乘积的值是最小公倍数除于对应分母的结果
求出所有分子的和
求出分子和与最小公倍数的最大公因数,最大公因数可以通过以下方法获得。
假 设 存 在 两 个 对 象 a , b a , b 的 最 小 公 倍 数 使 用 [ a , b ] 表 示 , 最 大 公 因 数 使 用 ( a , b ) 表 示 将 会 有 : a ∗ b = ( a , b ) ∗ [ a , b ] 假设存在两个对象a,b\\ a,b的最小公倍数使用[a,b]表示,最大公因数使用(a,b)表示\\ 将会有:a*b = (a,b)*[a,b] 假设存在两个对象a,ba,b的最小公倍数使用[a,b]表示,最大公因数使用(a,b)表示将会有:a∗b=(a,b)∗[a,b]
将分子和最小公倍数同时除以最大公因数
返回结果
- 细节处理
- 如果最终的分子和为0时,可以直接返回“0/1”,,不需要求出分子和最小公倍数的最大公因数,否则正常处理
- 代码实现
class Solution:
def beautyResultList(self,worklist):
resultList = []
for i in worklist:
if i != '':
eleList = i.split('/')
resultList.append((int(eleList[1]),int(eleList[0])))
return resultList
def commonList(self,worklist,commonNum):
for i in range(len(worklist)):
value = commonNum/worklist[i][0]*worklist[i][1]
worklist[i] = value
return worklist
def gcd(self,n1,n2):
"""greatest common divisor function """
return self.gcd(n2, n1 % n2) if n2 > 0 else n1
def lcm(self,n1,n2):
"""lowest common multiple function"""
return n1 * n2 // self.gcd(n1, n2)
def getMinCommonMultiple(self,worklist):
for i in range(len(worklist)-1):
n1,n2 = worklist.pop(0),worklist.pop(0)
worklist.insert(0,self.lcm(n1,n2))
return worklist[0]
def fractionAddition(self, expression: str) -> str:
positive,loseList = [],[]
if expression[0] != '+' or expression[0] != '-':
expression = '+'+expression
for i in range(len(expression)):
if (expression[i]=='-'):
ele = ''
for j in range(i+1,len(expression)):
if(expression[j]!='+') and (expression[j]!='-'):
ele += expression[j]
else:
break
loseList.append(ele)
elif (expression[i]=='+'):
ele = ''
for j in range(i+1,len(expression)):
if(expression[j]!='+') and (expression[j]!='-'):
ele += expression[j]
else:
break
positive.append(ele)
positive = self.beautyResultList(positive)
loseList = self.beautyResultList(loseList)
parentList = []
for i in positive:
parentList.append(i[0])
for i in loseList:
parentList.append(i[0])
minCommonNum = self.getMinCommonMultiple(parentList)
positive = self.commonList(positive,minCommonNum)
loseList = self.commonList(loseList,minCommonNum)
posSum = sum(positive)
loseSum = sum(loseList)
son = int(posSum-loseSum)
if son == 0:
return '0'+'/'+str(1)
else:
maxCommonNum = son*minCommonNum/self.getMinCommonMultiple([son,minCommonNum])
return str(int(son/maxCommonNum))+"/"+str(int(minCommonNum/maxCommonNum))
if __name__ == "__main__":
obj = Solution()
print(obj.fractionAddition("-1/2+1/2"))
# 0/1
- 思考
题目关键词:
这道题主要侧重于考察数学知识中的最小公倍数和最大公因数。另外在解析字符串的过程中,也是需要一些处理逻辑的,但是如果引用正则表达式的话,将会大大减少工作量。这等同于是否造轮子的区别。
另外有一些细节上的处理,也非常关键,在测试过程中遇到了好几个问题,都是因为细节处理上的疏忽。
- 来源
力扣(LeetCode)-1033-移动石子直到连续 - 问题描述
- 三枚石子放置在数轴上,位置分别为 a,b,c。
每一回合,我们假设这三枚石子当前分别位于位置 x, y, z 且 x < y < z。从位置 x 或者是位置 z 拿起一枚石子,并将该石子移动到某一整数位置 k 处,其中 x < k < z 且 k != y。
当你无法进行任何移动时,即,这些石子的位置连续时,游戏结束。
要使游戏结束,你可以执行的最小和最大移动次数分别是多少? 以长度为 2 的数组形式返回答案:answer = [minimum_moves, maximum_moves]
- 示例
输入:a = 1, b = 2, c = 5
输出:[1, 2]
解释:将石子从 5 移动到 4 再移动到 3,或者我们可以直接将石子移动到 3。
- …
- 算法分析
- 获取关键
- 输入:一维数轴,3个整数,移动到某个整数处
- 返回:一个数组
- 处理逻辑
- 首先有一个数轴,数轴上有3个数,移动3个数,使得连续。但每一回合只能移动两边的数。
也有几个概念,如果a,b连续,那么就有a+1=b;同理b,c连续则有b+1=c
因为不按顺序地输入,所以需要进行升序排序;根据大小重新定义为a,b,c
可以分为以下几种情况:
- 情况1:3个数是连续的
返回[0,0]即不需要移动- 情况2:左边两个数是连续的
那么移动最少次数为1,即将右边的数c移动1次,移动(飞)到b+1处;
最多次数,产生于将c每次移动1个单位,移动到b+1处,则有c-b-1- 情况3:右边两数是连续的
同上,最少次数为1,最多次数为b-a-1
-情况4:左右两数都不连续 但左右两数相差大于2
最少次数产生于 将a移动1次,移动(飞)到b-1处,将c移动1次,移动(飞)到b+1处,共2次;
最多次数产生于 每次一个单位,将a移动到b-1处,将c移动到b+1处,则有
( c − b − 1 ) + ( b − 1 − a ) = c − a − 2 (c-b-1)+(b-1-a)=c-a-2 (c−b−1)+(b−1−a)=c−a−2
-情况5:左右两数不连续,但左右两数有一个相差等于2,例如以下情况:
区别于情况4的最少移动次数,将离得远的数移动到左右相差2的中间即可。例如上面的将1移动到4的位置;也如将7移动到2的位置;
最多次数还是和情况4一致。
- 细节处理
- 其中情况5没有考虑到,所以算法过不去。看错误示例知道存在这个问题,所以补充了
- 代码实现
class Solution:
def numMovesStones(self, a, b, c):
a,b,c = sorted([a,b,c])
#三连续
if a+1 ==b and b+1 == c:
return [0,0]
#左连续
if a+1 == b and b+1 < c:
return [1,c-b-1]
#右连续
if a+1 <b and b+1 == c:
return [1,b-a-1]
if a+2 == b or b+2 == c:
# return [1,b-a-1+c-b-1]
return [1,c-a-2]
#左右不连续
if a+1 < b and b+1 <c:
# return [2,b-a-1+c-b-1]
return [2,c-a-2]
if __name__ == "__main__":
obj = Solution()
print('[1,2,5]:',obj.numMovesStones(1,2,5))
print('[1,3,5]:',obj.numMovesStones(1,3,5))
print('[1,3,7]:',obj.numMovesStones(1,3,7))
5. 思考
题目关键词:移动
这道算法的最小值和最大值都产生于移动,只不过产生的过程是不同的。
最少次数产生于跨度比较大的移动,更加确切的说是飞。而最多次数产生于一个单位的移动,每次只移动1个单位,不断缩小3个数的距离。
另外,每一种情况都有特殊性,区别在于最少次数的产生。因为情况不同,最少次数也不同。
最后,这道题也是比较简单,也因为这几天比较忙,所以刷几道简单的。
- 来源
力扣LeetCode-1160 - 问题描述
- 给你一份『词汇表』(字符串数组) words 和一张『字母表』(字符串) chars。
假如你可以用 chars 中的『字母』(字符)拼写出 words 中的某个『单词』(字符串),那么我们就认为你掌握了这个单词。
注意:每次拼写(指拼写词汇表中的一个单词)时,chars 中的每个字母都只能用一次。
返回词汇表 words 中你掌握的所有单词的 长度之和。
- 示例
输入:words = [“cat”,“bt”,“hat”,“tree”], chars = “atach”
输出:6
解释:
可以形成字符串 “cat” 和 “hat”,所以答案是 3 + 3 = 6。
- …
- 算法分析
- 获取关键
- 输入:一个列表,包含多个字符串,一个字符串
- 返回:返回列表中满足条件的字符串的总和
- 处理逻辑
- 定义字符串为字典,列表为待验证的字符串的集合
- 分析字典中每一个字符的出现次数,使用dict(),格式{“x”:counts}
- 遍历集合,取出每一个待验证的字符串,并分析字符串的每一个字符的出现次数,同上的处理方式
- 如果待验证字符串字典中的键在字典中,且出现次数小于或等于字典,则表示该键满足条件,记录对应的数值
- 当遍历完成一个待验证字符串字典时,如果数值等于该字符串的长度,则表示这个待验证字符串满足条件,记录对应的长度。
- 遍历完成时,返回最终的长度即可。
- 细节处理
- 对象的位置与区别需要分析好,不然很容易出现混淆。
- 代码实现
class Solution:
def counter(self,chars):
worddict = {}
for i in chars:
if(i in worddict):
worddict[i] = worddict[i]+1
else:
worddict[i] = 1
return worddict
def countCharacters(self, words, chars):
charsdict = self.counter(chars)
result = 0
for i in words:
i_dict = self.counter(i)
flag = 0
for j in i_dict:
if j in charsdict and charsdict[j] >= i_dict[j]:
flag += i_dict[j]
if flag == len(i):
result += len(i)
return result
if __name__ == "__main__":
obj = Solution()
words = ["cat","bt","hat","tree"]
chars = "atach"
# words = ["hello","world","leetcode"]
# chars = "welldonehoneyr"
words = ["dyiclysmffuhibgfvapygkorkqllqlvokosagyelotobicwcmebnpznjbirzrzsrtzjxhsfpiwyfhzyonmuabtlwin","ndqeyhhcquplmznwslewjzuyfgklssvkqxmqjpwhrshycmvrb","ulrrbpspyudncdlbkxkrqpivfftrggemkpyjl","boygirdlggnh","xmqohbyqwagkjzpyawsydmdaattthmuvjbzwpyopyafphx","nulvimegcsiwvhwuiyednoxpugfeimnnyeoczuzxgxbqjvegcxeqnjbwnbvowastqhojepisusvsidhqmszbrnynkyop","hiefuovybkpgzygprmndrkyspoiyapdwkxebgsmodhzpx","juldqdzeskpffaoqcyyxiqqowsalqumddcufhouhrskozhlmobiwzxnhdkidr","lnnvsdcrvzfmrvurucrzlfyigcycffpiuoo","oxgaskztzroxuntiwlfyufddl","tfspedteabxatkaypitjfkhkkigdwdkctqbczcugripkgcyfezpuklfqfcsccboarbfbjfrkxp","qnagrpfzlyrouolqquytwnwnsqnmuzphne","eeilfdaookieawrrbvtnqfzcricvhpiv","sisvsjzyrbdsjcwwygdnxcjhzhsxhpceqz","yhouqhjevqxtecomahbwoptzlkyvjexhzcbccusbjjdgcfzlkoqwiwue","hwxxighzvceaplsycajkhynkhzkwkouszwaiuzqcleyflqrxgjsvlegvupzqijbornbfwpefhxekgpuvgiyeudhncv","cpwcjwgbcquirnsazumgjjcltitmeyfaudbnbqhflvecjsupjmgwfbjo","teyygdmmyadppuopvqdodaczob","qaeowuwqsqffvibrtxnjnzvzuuonrkwpysyxvkijemmpdmtnqxwekbpfzs","qqxpxpmemkldghbmbyxpkwgkaykaerhmwwjonrhcsubchs"]
chars = "usdruypficfbpfbivlrhutcgvyjenlxzeovdyjtgvvfdjzcmikjraspdfp"
# words = ["hello","world","leetcode"]
# chars = "welldonehoneyr"
print(obj.countCharacters(words,chars))
运行截图:
5. 思考
题目关键词:字典
有时需要认真的阅读题目,其实题目中的很多信息,已经告诉我们处理的方式。例如这道题,最开始时,我是准备将每一个字符替换,当完成后,如果字符都被替换完成后,如果替换次数等于待验证字符串的长度,那么就表示符合条件。当然这可以实现,但有许多细节并没有扣好,而且如果字符串的长度过大时,非常消耗内存。
最后,采用了字典记录字符串的字符与出现次数,最终完成,有许多细节是不需要处理的,而且也并无细节。使用合适的数据类型非常关键,这也验证了那句"算法+数据结构=程序"。
- 来源
力扣LeetCode-1037-有效的回旋镖 - 问题描述
- 回旋镖定义为一组三个点,这些点各不相同且不在一条直线上。给出平面上三个点组成的列表,判断这些点是否可以构成回旋镖。
- 示例
输入:[[1,1],[2,3],[3,2]]
输出:true
- …
- 算法分析
- 获取关键
- 输入:一个列表,列表中有3个子列表,子列表中有2个元素,分别代表下x,y坐标,则3个子列表表示3个点
- 返回:判断3个点是否共线。如果共线,则返回Flase,否则True
- 处理逻辑
- 偏向于数学问题。有许多方法可以解决,例如斜率、3点是否构成三角形、点是否在直线上。
- 斜率:3个点中随机选取2个点,构成1条直线,获取2个直线,并求得斜率。如果2条直线的斜率不同,那么表示3点不共线。
- 三点是否构成三角形:之前有一篇“点在三角形内”,可以用其中的一个面积公式,如果面积为0,则表示构成直线。(代码实现2)
- 点是否在直线上,取两个点,构成一条直线方程,判断点是否满足直线方程,如果满足,则表示三点共线
4.向量法:三点两两组合构成两个向量,判断这两个向量是否共线即可。
-这一道题使用两种方法,一种是向量法,另一种就是三点是否构成三角形。
- . 细节处理
如果需要使用除法的话,可以两两交叉乘,避免分母为0的情况发生。
- 代码实现
这里分别使用两个思路三角形面积公式和向量法实现
- 三角形面积公式
class Solution:
def isBoomerang(self, points: List[List[int]]) -> bool:
a = (points[0][0],points[0][1])
b = (points[1][0],points[1][1])
c = (points[2][0],points[2][1])
##S=(1/2)*(x1y2+x2y3+x3y1-x1y3-x2y1-x3y2)
s = a[0]*b[1]+b[0]*c[1]+c[0]*a[1]
y = a[0]*c[1]+b[0]*a[1]+c[0]*b[1]
if s==y:
return False
else:
return True
- 向量法
class Solution:
def diffOfList(self,list1,list2):
return (list1[0]-list2[0],list1[1]-list2[1])
def isBoomerang(self, points) -> bool:
if points[1]!=points[0] and points[1]!=points[2] and points[0]!=points[2]:
vector_a = self.diffOfList(points[0],points[1])
vector_b = self.diffOfList(points[0],points[2])
if vector_a[0] and vector_a[1]:
if vector_b[0]/vector_a[0] != vector_b[1]/vector_a[1]:
return True
else:
return False
elif not vector_a[0] and vector_a[1]:
if not vector_b[0]:
return False
else:
return True
elif not vector_a[1] and vector_a[0]:
if not vector_b[1]:
return False
else:
return True
else:
return False
else:
return False
if __name__ == "__main__":
obj = Solution()
points = [[1,1],[2,3],[3,2]]
points = [[1,1],[2,2],[3,3]]
points = [[0,0],[1,2],[0,1]]
points = [[1,1],[2,3],[3,2]]
print(obj.isBoomerang(points))
- 思考
题目关键词:点,直线
这道题更偏向于数学知识上,其实除了上面的那些解法之外,还有许多种方式。
除此之外,也没啥好说的了。
- 来源
力扣(LeetCode)-1046-最后一块石头的重量 - 问题描述
- 有一堆石头,每块石头的重量都是正整数。
- 每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
- 如果 x == y,那么两块石头都会被完全粉碎;
- 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
- 最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。
- 示例
输入:[2,7,4,1,8,1]
输出:1
解释:
先选出 7 和 8,得到 1,所以数组转换为 [2,4,1,1,1],
再选出 2 和 4,得到 2,所以数组转换为 [2,1,1,1],
接着是 2 和 1,得到 1,所以数组转换为 [1,1,1],
最后选出 1 和 1,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。
- …
- 算法分析
- 获取关键
- 输入:一个列表,列表内元素都是整数
- 返回:一个整数
- 处理逻辑
- 每一回合选取列表中的最重两块石头,进行碰撞,碰撞后并返回剩余重量到列表。
碰撞:大减小
最简单的方法:- 对列表进行排序,弹出两个最大的元素,求差。当差不为0时,插入到列表中
- 当列表长度大于1时重复以上操作
- 最后将会返回一个空列表或者长度为1的列表
当列表为空时,返回0即可
当列表长度为1时,返回列表第一个元素即可
- 细节处理
- 需要考虑到列表为空的条件存在:即列表中最后一回合中,两块石头的重量相等。
- 代码实现
class Solution:
def lastStoneWeight(self, stones: List[int]) -> int:
while len(stones) > 1:
stones.sort(reverse=True)
max = stones.pop(0)
min = stones.pop(0)
if max != min:
stones.append(max-min)
if stones:
return stones[0]
return 0
- 思考
题目关键词:贪心,排序
其实这道题挺简单的,但并没有实现得多么优美,也没有参考其它的办法。知识简单的刷题而已。
另外也实现了一种想法:就是只执行一次排序,然后每次将碰撞后的结果插入列表的某个位置,保证列表的元素数值顺序不变。
这里使用了二分插入排序,但是效率非常不稳定,可以由于插入算法并没有写得完美。代码如下:
class Solution:
def insert(self,ele,ls):
if not ls:
return [ele]
elif len(ls) == 1:
if ls[0]>ele:
ls.insert(0,ele)
else:
ls.append(ele)
return ls
else:
low = 0
hight = len(ls)
while hight > low:
mid = int((hight-low)/2)+low
if ls[mid] > ele:
if ele > ls[mid-1]:
ls.insert(mid,ele)
return ls
hight = mid-1
elif ele > ls[mid]:
if len(ls)-2>mid and ls[mid+1] > ele:
ls.insert(mid+1,ele)
return ls
low = mid+1
else:
ls.insert(mid,ele)
return ls
ls.insert(low,ele)
return ls
def lastStoneWeight(self, stones: List[int]) -> int:
stones.sort()
while len(stones) > 1:
max = stones.pop(-1)
min = stones.pop(-1)
if max != min:
stones = self.insert(max-min,stones)
if stones:
return stones[0]
else:
return 0
- 来源
力扣(LeetCode)-611-有效三角形的个数 - 问题描述
- 给定一个包含非负整数的数组,你的任务是统计其中可以组成三角形三条边的三元组个数。
- 示例
输入: [2,2,3,4]
输出: 3
解释:
有效的组合是:
2,3,4 (使用第一个 2)
2,3,4 (使用第二个 2)
2,2,3
- …
- 算法分析
- 获取关键
- 输入:数组,非负整数,取出3个整数组合,判断是否符合三角形
- 返回:满足条件的三角形个数
- 处理逻辑
- 关键:三条边中,最小的两条边大于第三边,则三条边即可构成三角形
首先对列表进行排序,难解释,直接看图吧
>3. 细节处理- 根据关键,需要知道边的有效范围和符合条件的边的个数
- 代码实现
class Solution:
def triangleNumber(self, nums: List[int]) -> int:
nums.sort()
result,len_nums = 0,len(nums)
for i in range(len_nums-2):
if nums[i] == 0 : continue
current = i+2
for x in range(i+1,len_nums-1):
while len_nums > current and nums[i]+nums[x] > nums[current]:
current += 1
result += current -x-1
return result
- 思考
题目关键词:排序,三角形
其实一开始是准备3个for循环,获取满足条件的边的集合,最后返回集合的长度即可
但是最后发现,效率太低了,最后使用了动态规划。效率虽然不是很高,但也跑过去了。
- 来源
力扣(LeetCode)-1151-高度检查器 - 问题描述
- 学校在拍年度纪念照时,一般要求学生按照 非递减 的高度顺序排列。
请你返回能让所有学生以 非递减 高度排列的最小必要移动人数。
注意,当一组学生被选中时,他们之间可以以任何可能的方式重新排序,而未被选中的学生应该保持不动。
- 示例
输入:heights = [1,1,4,2,1,3]
输出:3
解释:
当前数组:[1,1,4,2,1,3]
目标数组:[1,1,1,2,3,4]
在下标 2 处(从 0 开始计数)出现 4 vs 1 ,所以我们必须移动这名学生。
在下标 4 处(从 0 开始计数)出现 1 vs 3 ,所以我们必须移动这名学生。
在下标 5 处(从 0 开始计数)出现 3 vs 4 ,所以我们必须移动这名学生。
- …
- 算法分析
- 获取关键
- 输入:一个列表
- 返回:一个整数
- 处理逻辑
- 这道题官方的问题描述和示例都是有问题的。初看时,一直get不到出题人的意图。甚至觉得是否要写一个排序算法,通过最少的移动次数,完成排序后并返回移动次数。但通过示例的返回值,知道了这道题的意图——对所给集合进行升序排序。并将完成排序之后的集合与原集合进行比较,看有同一位置上有多少个元素不同。
- 例如原集合[1,1,4,2,1,3]与完成排序的集合[1,1,1,2,3,4],
有位置2(从0开始),4,5的3个位置上的元素不同,所以返回3.- 所以解决的办法就是进行一一比较
- 细节处理
- 代码实现
class Solution:
def heightChecker(self, heights):
copy_heights = heights.copy()
copy_heights.sort()
count = 0
for i in range(len(heights)):
if heights[i]!= copy_heights[i]:
count += 1
return count
- 思考
题目关键词:简单,排序
- 来源
力扣(LeetCode-640-求解方程) - 问题描述
- 求解一个给定的方程,将x以字符串"x=#value"的形式返回。该方程仅包含’+’,’ - '操作,变量 x 和其对应系数。
如果方程没有解,请返回“No solution”。
如果方程有无限解,则返回“Infinite solutions”。
如果方程中只有一个解,要保证返回值 x 是一个整数。
- 输入: “x+5-3+x=6+x-2”
输出: “x=2”
- …
- 算法分析
- 获取关键
- 输入:一个字符串,一个方程
- 返回:返回解,如果特殊情况,则返回对应的值
- 处理逻辑
- 前言: 逻辑并不复杂,只是处理过程有点复杂。
- 解析字符串,获取等号两边的元素
- 根据正负收集元素(最关键的一步):假如以等号左边的正数为正的元素,那么等号右边的负数则为正。其它相反即可
-分别处理正负元素,返回解即可
- 细节处理
- 其中正负收集元素的这一步中,非常关键,可以使用两个集合分别收集正负元素。最后统计集合的元素,,返回解。
- 代码实现
class Solution:
def sum(self,workList):
nums,count = 0,0
for i in workList:
if i:
if 'x' not in i:
nums += int(i)
else:
if len(i) == 1:
count += 1
else:
count += int(i[:len(i)-1])
return nums,count
def getELement(self,workStr,pList,mList):
# '+x+5+x' len = 8 index = 7
current = 0
while len(workStr)-1>current:
if workStr[current]=='+':
plushStr = ''
k = 0
for i in range(current+1,len(workStr)):
k = i
if i == len(workStr)-1:
plushStr += workStr[i]
break
if workStr[i] != '+' and workStr[i] != '-':
plushStr += workStr[i]
else:
break
pList.append(plushStr)
current = k
else:
minusStr = ''
k = 0
for i in range(current+1,len(workStr)):
k = i
if i == len(workStr)-1:
minusStr += workStr[i]
break
if workStr[i] != '+' and workStr[i] != '-':
minusStr += workStr[i]
else:
break
mList.append(minusStr)
current = k
return pList,mList
def solveEquation(self, equation: str) -> str:
workList = equation.split('=')
if workList[0] != '+' and workList[0] != '-':
front = '+'+workList[0]
else:
front = workList[0]
if workList[1] != '+' and workList[1] != '-':
back = '+'+workList[1]
else:
back = workList[1]
# plus or minus
plusList,minusList = [],[]
plusList,minusList = self.getELement(front,plusList,minusList)
plusList,minusList = self.getELement(back,minusList,plusList)
plus = self.sum(plusList)
minus = self.sum(minusList)
if plus[1] == minus[1]:
if plus[0] != minus[0]:
return 'No solution'
else:
return 'Infinite solutions'
else:
if plus[1] > minus[1]:
return 'x={}'.format(int((minus[0]-plus[0])/(plus[1]-minus[1])))
else:
return 'x={}'.format(int((plus[0]-minus[0])/(minus[1]-plus[1])))
- 思考
题目关键词:解析
在现实中简单的一个加减法求解方程,使用代码实现,却如此复杂。说明自己的代码水平还是需要有所提高的。
- 来源
力扣(LeetCode)-1103-分糖果Ⅱ - 问题描述
- 排排坐,分糖果。
我们买了一些糖果 candies,打算把它们分给排好队的 n = num_people 个小朋友。
给第一个小朋友 1 颗糖果,第二个小朋友 2 颗,依此类推,直到给最后一个小朋友 n 颗糖果。
然后,我们再回到队伍的起点,给第一个小朋友 n + 1 颗糖果,第二个小朋友 n + 2 颗,依此类推,直到给最后一个小朋友 2 * n 颗糖果。
重复上述过程(每次都比上一次多给出一颗糖果,当到达队伍终点后再次从队伍起点开始),直到我们分完所有的糖果。注意,就算我们手中的剩下糖果数不够(不比前一次发出的糖果多),这些糖果也会全部发给当前的小朋友。
返回一个长度为 num_people、元素之和为 candies 的数组,以表示糖果的最终分发情况(即 ans[i] 表示第 i 个小朋友分到的糖果数)。
- 输入:candies = 7, num_people = 4
输出:[1,2,3,1]
解释:
第一次,ans[0] += 1,数组变为 [1,0,0,0]。
第二次,ans[1] += 2,数组变为 [1,2,0,0]。
第三次,ans[2] += 3,数组变为 [1,2,3,0]。
第四次,ans[3] += 1(因为此时只剩下 1 颗糖果),最终数组变为 [1,2,3,1]。
- …
- 算法分析
- 获取关键
- 输入:糖果的个数,人的个数
- 返回:以集合的方式返回每个人收到糖果的个数
- 处理逻辑
- 按照题目的逻辑走一遍即可
- 细节处理
- 定位索引这个地方可以使用求余符号%实现。
例如:设定有n个小朋友
第0次分糖果,应该定位到第0位小朋友,即0%n=0
第n+1次分糖果,应该定位到第1位小朋友,即(n+1)%n=1
- 代码实现
class Solution:
def distributeCandies(self, candies: int, num_people: int) -> List[int]:
resultList = [0 for _ in range(num_people)]
index,count = 0,1
while candies:
if candies >= count:
resultList[index%num_people] += count
candies = candies - count
index += 1
count += 1
else:
resultList[index%num_people] += candies
candies = 0
return resultList
- 思考
题目关键词:顺序 分
从看到问题到写完,没有花多少时间,而且直接一遍过。
可能因为这道题非常简单吧。
- 来源
力扣(LeetCode)-452-用最少数量的箭引爆气球 - 问题描述
- 在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。
一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
- 输入:[[10,16], [2,8], [1,6], [7,12]]
输出:2
解释:
对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。
- …
- 算法分析
- 获取关键
- 输入:一个列表
- 返回:需要最少的弓箭数量
- 处理逻辑
- 这道题是属于贪心算法,所以有两个常规操作:排序和贪心选择
问题描述中有一句非常关键:由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。
上面的这句关键说明只有x轴方向,即一维的。另外其实更确切地说,排序的关键在于结束坐标。如下:
那么引爆以上气球需要2个弓箭。一个弓箭在x=3,引爆气球1,2和2;另一个弓箭引爆气球4即可。
- 细节处理
- 其实对上面的样例,不止有以上的办法。还有:
- 弓箭在x=2出,另一个弓箭在x=4处
- 等等
但最终的答案是不会变的。正如贪心算法的一个案例-活动安排。
贪心算法解决活动安排-Python实现(排序+贪心选择)
活动结束时间对应气球的结束坐标,排序还是那个排序;
- 代码实现
class Solution:
def findMinArrowShots(self, points:list) -> int:
points.sort(key=lambda x:(x[1]))
flag = 0
while points:
workList = []
workList.append(points.pop(0))
while points:
if workList[0][1] >= points[0][0]:
workList.append(points.pop(0))
else:
break
flag += 1
return flag
- 思考
题目关键词:排序,贪心
以前解决问题,都是按照c或者c++的思维,手写排序,手写逻辑。固然非常锻炼编程能力,但也是重复造轮子。所以,有些时候,使用一些轮子非常重要,当然这些轮子的使用前,必须知道这个轮子是怎么造的。例如代码中的points.sort(key=lambda x:(x[1]))
即代表根据对象的位置1进行排序。
另外,贪心算法并不是一种“完美“的解决方案:
- 当存在多个最优方案时,无法列举所有的方案
- 最终的方案是基于每一次的最优选择,而许多问题并不全是走好每一步就代表可以得到最优方案。例如动态规划问题等等。
- 来源
力扣(LeetCode)-1268-搜索推荐系统 - 问题描述
- 给你一个产品数组 products 和一个字符串 searchWord ,products 数组中每个产品都是一个字符串。
请你设计一个推荐系统,在依次输入单词 searchWord 的每一个字母后,推荐 products 数组中前缀与 searchWord 相同的最多三个产品。如果前缀相同的可推荐产品超过三个,请按字典序返回最小的三个。
请你以二维列表的形式,返回在输入 searchWord 每个字母后相应的推荐产品的列表。
- 输入:products = [“mobile”,“mouse”,“moneypot”,“monitor”,“mousepad”], searchWord = “mouse”
输出:[
[“mobile”,“moneypot”,“monitor”],
[“mobile”,“moneypot”,“monitor”],
[“mouse”,“mousepad”],
[“mouse”,“mousepad”],
[“mouse”,“mousepad”]
]
解释:按字典序排序后的产品列表是 [“mobile”,“moneypot”,“monitor”,“mouse”,“mousepad”]
输入 m 和 mo,由于所有产品的前缀都相同,所以系统返回字典序最小的三个产品 [“mobile”,“moneypot”,“monitor”]
输入 mou, mous 和 mouse 后系统都返回 [“mouse”,“mousepad”]
- …
- 算法分析
- 获取关键
- 输入:xxx
- 返回:xxx
- 处理逻辑
- 通过问题描述,觉得很高大上,其实通过暴力法很简单
- 对产品组进行排序,由于产品组都是字符串,所以排序是根据字典排序。
- 通过两个for即可不断遍历产品组,获得符合条件产品即可。
- 细节处理
- 无
- 代码实现
class Solution:
def suggestedProducts(self, products, searchWord):
products.sort()
result = [[] for _ in range(len(searchWord))]
workStr = ''
lenStr = 0
for i in searchWord:
workStr += i
lenStr += 1
for j in products:
if j[:lenStr] == workStr and len(result[lenStr-1]) < 3:
result[lenStr-1].append(j)
return result
- 思考
题目关键词:暴力 遍历
现在刷题都是暴力法解决的,往往遇到最多的不是出现错误,而是超时。有时总在技巧和暴力之间犹豫不决。两个方向各有好处,技巧锻炼思维,暴力锻炼逻辑。虽然更偏向于技巧,但有时因为时间和思维有限,所以走向暴力法。