作为一个计算机院的大学生,总觉得仅仅在学校粗略的学习计算机专业课是不够的,尤其是假期大量的空档期,作为一个小白,实习也莫得路子,又不想白白耗费时间。于是选择了Leetcode这个平台来刷题库。编程我只学过基础的C语言,现在在自学Python,所以用Python3.8刷题库。现在我Python掌握的还不是很熟练,算法什么的也还没学,就先不考虑算法上的优化了,单纯以解题为目的,复杂程度什么的以后有时间再优化。计划顺序五个题写一篇日志,希望其他初学编程的人起到一些帮助,写算是对自己学习历程的一个见证了吧。
有一起刷LeetCode的可以关注我一下,我会一直发LeetCode题库Python3解法的,也可以一起探讨。
觉得有用的话可以点赞关注下哦,谢谢大家!
········································································································································································
题解框架:
1.题目,难度
2.题干,题目描述
3.题解代码(Python3(不是Python,是Python3))
4.或许有用的知识点(不一定有)
5.解题思路
6.优解代码及分析(当我发现有比我写的好很多的代码和思路我就会写在这里)
········································································································································································
题解代码(Python3.8)
class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
def swap(index1,index2):
nums[index1],nums[index2] = nums[index2],nums[index1]
l=len(nums)
for i in range(l):
while 1 <= nums[i] <= l and nums[i] != nums[ nums[i]-1 ]:
swap(i,nums[i]-1)
for i in range(l):
if i+1 != nums[i]:
return i+1
return l+1
或许有用的知识点:
这道题会用到‘桶的思想’和‘抽屉原理’。
在学习“排序算法”的时候,可能会忽略“桶排序”的作用,但它的思想的确可以解决一些特定问题。所谓桶排序,即每个桶存储一定范围的元素,通过映射函数,将待排序数组中的元素映射到各个桶中,对每个桶中元素进行排序,最后将非空桶中的元素逐个放入原序列。
这道题还使用了“抽屉原理”,有些地方也叫“鸽巢原理”,说白了就是“一个萝卜一个坑”。抽屉原理的一般含义为:“如果每个抽屉代表一个集合,每一个苹果就可以代表一个元素,假如有 n + 1 个元素放到 n 个集合中去,其中必定有一个集合里至少有两个元素。” 抽屉原理有时也被称为鸽巢原理。它是组合数学中一个重要的原理。
这道题中用到了len个容纳范围为1的桶,len个桶容纳的总范围为[1,len] 但是有一些元素是超出范围[1,len]的,所以这个时候用到了抽屉原理,即有超出[1,len]范围的元素存在时,至少有一个抽屉中放的是超出范围的元素,对这些超出范围的元素,程序中选择的处理方式是忽略。
解题思路:
我们可以把数组进行一次“排序”,“排序”的规则是:如果这个数字 i 落在“区间范围里”,i 就应该放在索引为 i - 1 的位置上,下面具体解释。
1、数字 i 落在“区间范围里”;
例如:[3, 4, -1, 1],一共 4 个数字,那么如果这个数组中出现 “1”、“2”、“3”、“4”,就是我们重点要关注的数字了;又例如:[7, 8, 9, 11, 12] 一共 5 个数字,每一个都不是 “1”、“2”、“3”、“4”、“5” 中的一个,因此我们无须关注它们;
2、i 就应该放在索引为i - 1 的位置上;
这句话也可以这么说 “索引为 i 的位置上应该存放的数字是 i + 1”。例如,数字1应该放在索引为0的位置上,数字3应该放在索引为2的位置上,数字4应该放在索引为3的位置上。一个数字放在它应该放的位置上,我们就认为这个位置是“和谐”的,看起来“顺眼”的。
按照以上规则排好序以后,缺失的第1个正数一下子就看出来了,那么“最不和谐”的数字的索引+1,就为所求。那如果所有的数字都“和谐”,数组的长度+1就为所求。
其实,这道题中用到了len个容纳范围为1的桶,len个桶容纳的总范围为[1,len] 但是有一些元素是超出范围[1,len]的,所以这个时候用到了抽屉原理,即有超出[1,len]范围的元素存在时,至少有一个抽屉中放的是超出范围的元素,对这些超出范围的元素,程序中选择的处理方式是忽略。
题解代码(Python3.8)
class Solution:
def trap(self, height: List[int]) -> int:
#思路:雨水面积=组合成的凸边形面积-柱子面积
area=0
i=0
j=len(height)-1
left_highest=0
right_highest=0
pre=0
if i==j:
return 0
while i<j:
if left_highest<=right_highest:
while i<j and height[i]<=left_highest:
i+=1
if right_highest<=left_highest:
while i<j and height[j]<=right_highest:
j-=1
area += (min(height[i],height[j])-pre)*(j-i+1)
pre = min(height[i],height[j])
left_highest=height[i]
right_highest=height[j]
area_black=0
for h in height:
area_black+=h
area_rain = area - area_black
return area_rain
或许有用的知识点:
这道题要用到双指针法。
解题思路:
总思路:能够接到的雨水面积 = 输入数组所构成的凸多边形面积(S1) - 输入数组面积(S2)。
分解步骤:
1.若当前左最高值高于右最高值,则左指针保持不动;右指针同理;
2.用一个变量pre记录前一次增加的斜线标识部分面积的高度,下一次增加的面积应是(min(height[i], height[j]) - pre) * (j - i + 1)
3.更新左右最大高度
4.(最后返回时要注意无法接雨水的情况)
题解代码(Python3.8)
class Solution:
def multiply(self, num1: str, num2: str) -> str:
def Multiply(string,n):
s=string [::-1]
res=[]
for i,digit in enumerate(s):
num=int(digit)
res.append(num*n)
res=Carry(res)
res=res[::-1]
return ''.join(str(x) for x in res)
def Add(s1,s2):
l1,l2=len(s1),len(s2)
if l1<l2: #保证被加数s1比加数s长
s1,s2=s2,s1
l1,l2=l2,l1
s1=[int(x) for x in s1]
s2=[int(x) for x in s2]
s1,s2=s1[::-1],s2[::-1]
for i,digit in enumerate(s2):
s1[i]+=s2[i]
s1=Carry(s1)
s1=s1[::-1]
return ''.join(str(x) for x in s1)
def Carry(nums):
l=len(nums)
i=0
while i<l:
if nums[i]>=10:
carry=nums[i]//10
if i==l-1:
nums.append(carry)
else:
nums[i+1]+=carry
nums[i]=nums[i]%10
i+=1
return nums
if num1=='0' or num2=='0': #特判
return '0'
l1,l2=len(num1),len(num2)
if l1<l2: #保证被乘数num1比乘数num2长
num1,num2=num2,num1
l1,l2=l2,l1
num2=num2[::-1]
res='0'
for i,digit in enumerate(num2):
tmp=Multiply(num1,int(digit))+'0'*i #计算num1与num2当前位的乘积
res=Add(res,tmp) #计算res与tmp的和
return res
或许有用的知识点:
这道题可以用到python的enumerat()函数,enumerate()函数的部分介绍如下:
解题思路:
这道题按照题意应该不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。所以我们希望可以把最多1位字符串转换成整数(避免超长)。我们的代码可能跑出来排名比较落后,可能是有些人直接用了库函数或者直接转成整形,我们了解正确的方法就好。
我们的思路是像竖式乘法一样一位一位的算,我们先定义三个函数:乘法(一串数与一位数的乘积)、加法(两串数的加法)、进位计算。
题解代码(Python3.8)
class Solution:
def isMatch(self, s: str, p: str) -> bool:
ls=len(s)
lp=len(p)
dp=[[False]*(lp+1) for _ in range(ls+1)] #初始化
dp[0][0]=True
for j in range(1,lp+1):
if p[j-1]=='*':
dp[0][j]=dp[0][j-1]
for i in range(1,ls+1):
for j in range(1,lp+1):
if s[i-1]==p[j-1] or p[j-1] == '?':
dp[i][j]=dp[i-1][j-1]
elif p[j-1]=='*':
dp[i][j] = dp[i-1][j] or dp[i][j-1]
return dp[-1][-1]
或许有用的知识点:
本题可以使用动态规划,也可以使用回溯算法。
解题思路:
dp[i][j]表示s到i位置,p到j位置是否匹配!
初始化:
dp[0][0]:什么都没有,所以为true
第一行dp[0][j],换句话说,s为空,与p匹配,所以只要p开始为*才为true
第一列dp[i][0],当然全部为False
动态方程:
如果(s[i] == p[j] || p[j] == “?”) && dp[i-1][j-1] ,有dp[i][j] = true
如果p[j] == “*” && (dp[i-1][j] = true || dp[i][j-1] = true) 有dp[i][j] = true
note:
dp[i][j-1],表示*代表是空字符,例如ab,ab*
dp[i-1][j],表示*代表非空任何字符,例如abcd,ab*
优解代码及分析:
优解代码(Python3.8)
class Solution:
def isMatch(self, s: str, p: str) -> bool: # 回溯函数
i = j = 0 # 用i索引s字符串,用j索引p字符串
back_j = -1 # 记录p字符串中星号出现的位置
match_i = 0 # 记录s字符串中从0到match_i位置都能匹配
ls = len(s)
lp = len(p)
while i < ls:
if j < lp and (s[i] == p[j] or p[j] == '?'): # 若匹配,i和j往后移
i += 1
j += 1
elif j < lp and p[j] == '*': # *号出现,i不变,j记录
back_j = j # 记录星号的位置,方便回溯
match_i = i # 因为j处的'*'可以匹配s[i],所以match_i = i
j += 1 # i不变,j加1,看i能否和星号后面字符的匹配,若匹配则i和j后移,若不匹配则回溯
elif back_j != -1: # 回溯,j回到星号位置的下一个位置,i回到match_i的下一个位置
j = back_j + 1
match_i += 1 # match_i一直加1就是为了找到能和back_j的下一个位置匹配的,若匹配i和j各自加1继续往后移,若不匹配j继续回到 # back_j的下一个位置,match_i继续加1,直到match_i和back_j匹配,或者i溢出
i = match_i
else: # 还没出现*,但s[i]和p[j]已经不匹配了,所以False
return False
return list(p[j:]).count('*') == len(p[j:]) # 此时s[:i]和p[:j]已经匹配,而且i==len(s),若j也刚好溢出即p[j:]=[],或者p[j:]的 # 内容全是'*',则s和p匹配
分析:
这是回溯算法的方法,详细的解释在代码中逐条给出了。
题解代码(Python3.8)
class Solution:
def jump(self, nums: List[int]) -> int:
step=0
end=0
max_jump=0
for i in range(len(nums)-1):
max_jump=max( max_jump , nums[i]+i )
if (i==end):
step+=1
end=max_jump
return step
或许有用的知识点:
这道题可以用到贪心算法。
解题思路:
总思路:使用最少的步数到达最后一个位置,则第i步位置为第i−1步前的点中所能达到的最远位置。
贪心算法:
定义步数step=0,能达到的最远位置max_jump=0,和上一步到达的边界end=0。
遍历数组,遍历范围[0,n-1):
所能达到的最远位置max_jump=max(max_jump,nums[i]+i),表示上一最远位置和当前索引i和索引i上的步数之和中的较大者。
如果索引i到达了上一步的边界end,即i==end,则:
更新边界end,令end等于新的最远边界max_jump,即end=max_jump
令步数step加一
返回step
注意:数组遍历范围为[0,n-1),因为当i==0时,step已经加一,所以若最后一个元素也遍历的话,当end恰好为n−1,步数会多1。
复杂度分析
时间复杂度:O(n)
空间复杂度:O(1)