此篇博客用于记录我刷Leetcode题目的相关方法(草稿写法,若要具体了解参考其他博客)
hot100
1.两数之和:采用字典法解法。
暴力破解,时间复杂度较高
O(n)算法, a = target - nums[x],求出a的索引即可,返回a,x索引,注意a=x索引时需continue
字典法:字典用于提升查找速度
1。首先遍历nums列表,目标值-遍历值不在字典中,记录该值和值的索引
2。发现目标值-遍历值在字典中,说明前面已经出现过,返回前一个值的索引和当前值的索引
2.两数相加
基本的链表操作
3.无重复的最大子串:采用滑动窗口解法。
滑动窗口的应用场景有几个特点:
- 需要输出或比较的结果在原数据结构中是连续排列的;
- 每次窗口滑动时,只需观察窗口两端元素的变化,无论窗口多长,每次只操作两个头尾元素,当用到的窗口比较长时,可以显著减少操作次数;
- 窗口内元素的整体性比较强,窗口滑动可以只通过操作头尾两个位置的变化实现,但对比结果时往往要用到窗口中所有元素。
4.寻找两个正序数组的中位数:采用最小K值法。
此题的最小k意味着第k个最小的数,对于一个数组,可能长度是奇数也有可能是偶数,那么 k = l e n g t h / / 2 k = length // 2 k=length//2或者 k = l e n g t h / / 2 + 1 k = length //2 +1 k=length//2+1,对于这题,假设AB两个数组,我们需要比较第 k / / 2 − 1 k//2 -1 k//2−1数,谁小就把到这为止的元素剔除,如何重新计算k值,假设剔除了两个数(该数一定比k小),那么k应该等于k-2,直到k=1时,输出两者min值。
5.最长回文子串:采用动态规划法和中心扩散法
什么样的问题可以考虑使用动态规划解决呢?
如果一个问题,可以把所有可能的答案穷举出来,并且穷举出来后,发现存在重叠子问题,就可以考虑使用动态规划。比如一些求最值的场景,如最长递增子序列、最小编辑距离、背包问题、凑零钱问题等等,都是动态规划的经典应用场景。
中心扩散法:对于一个字符串,中心可能是奇的(比如aba,中心为a),也有可能是偶的(比如abba,中心为bb)。我们可以遍历这个字符串,以奇为中心和以偶为中心分别扩展。
left1, right1 = self.expandAroundCenter(s, i, i) # 中心是奇数 比如aba b是中心
left2, right2 = self.expandAroundCenter(s, i, i + 1) # 中心是偶数 比如abba bb是中心
求出以该中心扩散的最大回文串索引即可。
10.正则表达式匹配:采用动态规划法。【困难题】
此题有必要记录下思路和算法过程:
给你一个字符串s和一个字符规律p,请你来实现一个支持 '.'和 ‘✳’ 的正则表达式匹配。
‘.’ 匹配任意单个字符
’ ✳‘匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖整个字符串s的,而不是部分字符串。
解题思路:
AC代码[写法较拙劣,上述思路实现]:
class Solution:
def isMatch(self, s: str, p: str) -> bool:
m, n = len(s), len(p)
dp = [[False] * (n + 1) for _ in range(m + 1)] # 初始化dp数组,因为需要考虑两边空串情况,所以行数和列数分别加1
# 含空字串处理
dp[0][0] = True # 空字串为True
for i in range(1, n + 1): # dp行与列
if p[i-1] == '*': # dp第一行,空串遇见'*',返回j-2状态
dp[0][i] = dp[0][i-2]
else: # 其余置false
dp[0][i] = False
for i in range(1, m + 1): # 列置false
dp[i][0] = False
for i in range(1, m + 1): # 遍历,dp变化参照上述规则
for j in range(1, n + 1):
if s[i - 1] == p[j - 1] or p[j - 1] == '.':
dp[i][j] = dp[i - 1][j - 1]
elif p[j - 1] == '*':
if s[i - 1] == p[j - 2] or p[j - 2] == '.': # 注意先判断相等的情况(细节)
dp[i][j] = (dp[i][j - 2] or dp[i][j - 1] or dp[i - 1][j])
else:
dp[i][j] = dp[i][j - 2]
return dp[m][n]
11.盛最多水的容器:采用双指针。
O(n)算法,控制左右两个指针,指针往容器高度较低的移动
15.三数之和:采用循环+双指针。
先将数组排好序,开始遍历数组,设两个指针,left = i + 1,right = len(nums) - 1, 当left < right时候,进行三元组判断,三种情况,大于0,小于0,等于0.
大于0: right左移 。 小于0,left右移, 当等于0时,需要考虑去重。
三元组等于0 时候去重的核心代码:
while left < right and nums[left + 1] == nums[left]: # 防止重复
left = left + 1
while left < right and nums[right - 1] == nums[right]: # 防止重复
right = right - 1
17.电话号码的字符组合:采用回溯法。
此题考查组合,暴力求解不仅麻烦而且时间复杂度较高,采用回溯法(递归求解)
首先构造数据字典,回溯核心代码模板:
def backtrack(temp):
if len(path) == len(digits):
res.append("".join(path[:]))
return
for i in KEY[temp[0]]:
path.append(i)
backtrack(temp[1:])
path.pop()
Python中也可以考虑列表推导式进行求解:
for num in digits:
ans = [pre + suf for pre in ans for suf in KEY[num]]
19.删除链表的倒数第N个节点:采用快慢指针。
链表基本操作,先讲常用方法:可以先求出链表长度,然后求正序目标节点,工作指针定位即可。
此题用快慢指针方法更好,因为求倒数第N个节点,定义两个指针,fast、slow。两个指针都先指向头节点,fast先移动N步,然后fast和slow再一起移动,当fast移动到空节点(也就是末尾后一个时),此时slow所处的位置就是目标节点的位置。
20.有效的括号:采用栈。
括号匹配题:采用栈的思想即可。遇见左括号直接入栈,右括号需要与出栈字符比较。
21.合并两个有序链表:采用递归/迭代法。
对一个问题,用递归求解的时候,要想清楚几点问题:
1、这个问题的子问题是什么?(在两个链表中找到最小值)
2、当前层要干什么事情?(找最小值)
3、递归出口。(其中一个链表为空)
此题采用迭代的话,直接工作指针遍历即可。
22.括号生成:采用DFS。
1.DFS + 剪枝 大大缩短时间。DFS三个参数
def dfs(str,left,right)
"""
:param str: 当前字符串
:param left: 左边剩余可用括号数
:param right 右边剩余可用括号数
:return:
"""
递归出口:left == 0 and right ==0添加到res中,当left > right时,进行剪枝操作
23.合并K个升序链表:采用优先级队列/分治法。
这道题,首先顺序合并也是可以的,遍历,一个一个合并即可[21题思路]。不过耗时会很长。
介绍下优先级队列解法,创建最小堆,因为用的Python,调用Python的heapq库快速遍历创建,然后创建完毕后,依次取顶部加入到新链表中即可。
再介绍下分治法:分而治之,链表两两合并。计算数组长度
l1 = merge(lists,left,mid)
l2 = merge(lists,mid+1,right)
mergeTwolists(l1,l2) #此函数就是两个链表的合并算法
31.下一个排列:此题需要一定思想,双排列法
本题题意:返回恰好比nums排列后大的数,如果nums排列后是最大的,就返回最小的数
example:[1, 5, 8, 7, 6, 2, 1] -> [1, 6, 1, 2, 5, 7, 8]
那么这题需要的一个思想就是:找数、交换和翻转
找数:从尾部开始找,找到第一个比前面的数比后面小的数,[1, 5, 8, 7, 6, 2, 1]
找到这个数后,然后再从尾部找到第一个比此数大的数,[1, 5, 8, 7, 6, 2, 1]
交换:[1, 6, 8, 7, 5, 2, 1]
翻转:[1, 6, 8, 7, 5, 2, 1] —>[1, 6, 1, 2, 5, 7, 8]
33.搜索旋转排序素组:采用二分法
评论中有一句话方便理解这种做法:将数组一分为二,其中一定有一个是有序的,另一个可能是有序,也能是部分有序。此时有序部分用二分法查找。无序部分再一分为二,其中一个一定有序,另一个可能有序,可能无序。就这样循环.
官方代码中,指出如果num[0] < =num[mid] 说明右边有序,否则左边有序,注意:nums无重复。
官方代码:
class Solution:
def search(self, nums: List[int], target: int) -> int:
if not nums:
return -1
l, r = 0, len(nums) - 1
while l <= r:
mid = (l + r) // 2
if nums[mid] == target:
return mid
if nums[0] <= nums[mid]: # 说明左边有序
if nums[0] <= target < nums[mid]: # target在里面,然后二分求解
r = mid - 1
else:
l = mid + 1 # 目标值不在,l=mid+1,跳到右边
else: # 说明右边有序
if nums[mid] < target <= nums[len(nums) - 1]: # target在里面,然后二分求解
l = mid + 1
else:
r = mid - 1 # 目标值不在,r=mid-1,跳到左边
return -1
34.在排序数组中查找元素的第一个和最后一个位置:采用二分法
此题限制时间复杂读O(logn),对于这种非递减数组,首先就应该采用二分查找法。
题意:给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
对于开始位置,我们可以用二分去查找,通过条件nums[mid]>=target,可以找到第一个大于等于 target的索引值,对于结束位置,我们通过条件nums[mid]>target,可以找到第一个大于target的索引值,该索引值-1可能就是我们需要的结束位置了。
所以得到这两个值后,我们需要判断
if leftIndex == len(nums): # 说明target值大于数组所有值
return [-1, -1]
if rightIndex < leftIndex: # 说明数组内无target值
return [-1, -1]
return [leftIndex,rightIndex]
39.组合总和:采用回溯法
对于这类寻找所有可行解的题,我们都可以尝试用「搜索回溯」的方法来解决。
以candidates = [2,3,6,7],target = 7 为例。
考虑 candidates[0] = 2 我们只需要找到组合总和为 7- 2 =5的所有总和
考虑 candidates[1] = 3 我们只需要找到组合总和为 7- 3 =4的所有总和
…
这样我们可以画出一颗递归树
图片来源于:leetcode39.解析
可以看到红色叶子节点所在路径是我们需要的组合,但是由于[2,2,3]会和[3,2,2]重复,所以需要进行简单处理,只需遍历的时候设置下一轮搜索的七点即可。
当然此题也可以做剪枝处理,我们可以先对candidates数组进行排序处理,然后递归时候判断 target - candidates[i] < 0,则剪枝。
46.全排列:采用回溯法
寻找所有可行解,一般都用回溯法求解
回溯模板:
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
图片来源leetcode46.解析
表示从左往右填到第first个位置,当前排列为output