力扣100刷题笔记[python]
letcode100
题库地址:
https://leetcode.cn/studyplan/top-100-liked/
哈希
两数之和
思路:
0、用 hash_table ={1: 0, 2:1} 保存值与下标
1、遍历所nums,如果 target - val 不在hash_table中,将当前val和index 增加或刷新到hash_table中;如果 target - val 在hash_table中,就直接返回
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
hash_table = defaultdict(int)
for index, val in enumerate(nums):
if target - val in hash_table:
return [hash_table[target - val], index] # 如果在hash_table中就直接返回
if val not in hash_table:
hash_table[val] = index
49 字母异位词分组
思路:
1 使用 hash_dict = defaultdict(list) 保存hash相同的list
2 使用hash_val += hash(item)hash(item)hash(item) 的方式来计算hash
from collections import defaultdict
class Solution(object):
def groupAnagrams(self, strs):
"""
:type strs: List[str]
:rtype: List[List[str]]
"""
re = []
def calHash(stri):
hash_val = 0
for item in stri:
hash_val += hash(item)*hash(item)*hash(item)
return hash_val
hash_dict = defaultdict(list)
for item in strs:
hash_val = calHash(item)
hash_dict[hash_val].append(item)
for key, val_list in hash_dict.items():
re.append(val_list)
return re
128. 最长连续序列
思路:
1、遍历所有元素。以每一个元素为起点连续搜索,直到没有。搜索的时候用 key in的方式去做。可以使用hash表也可以直接使用一个set
2、去重的好处是防止重复遍历
3、此外这里使用 if num - 1 not in num_set: 来进行剪枝,这段代码的意思是判断是否为起始节点
class Solution(object):
def longestConsecutive(self, nums):
longest_streak = 0 # 保存当前最大的连续序列
num_set = set(nums)
for num in num_set:
if num - 1 not in num_set:
current_num = num
current_streak = 1
while current_num + 1 in num_set:
current_num += 1
current_streak += 1 # 保存当前的最大连续序列长度
longest_streak = max(longest_streak, current_streak)
return longest_streak
双指针
移动零(简单题)
思路:
1、此题有点像插入排序。左指针维护一个非零的队列,右指针维护一个0的队列
2、
class Solution:
def moveZeroes(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
left = 0 # 第一个指针,left
for right in range(len(nums)): # 第二个指针,right,在for循环中实现移动
# 核心的交换步骤:如果当前right不为0,则交换到左侧,把非0数往左侧移动就对了
if nums[right]:
nums[left], nums[right] = nums[right], nums[left]
left += 1 # 交换后也移动left
作者:庸才程序员
链接:https://leetcode.cn/problems/move-zeroes/solutions/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
乘最多水的容器
- 盛最多水的容器
我的思路:(但我觉得还是不严谨)
1 如果将两个指针放在两端,那么 width 只能减少,但问题是左右指针怎么移动
2 左右指针的移动目标就是朝着 height 增大的方向去移动,所有选择移动其中小的指针
具体算法
构建一个搜索路径,保证搜索的单向性,使用排除法
收尾两个指针,向中间移动,永远移动小指针,直到收尾指针重合
这篇题解和我的思路一模一样:
https://leetcode.cn/problems/container-with-most-water/solutions/11491/container-with-most-water-shuang-zhi-zhen-fa-yi-do
class Solution(object):
def maxArea(self, height):
"""
:type height: List[int]
:rtype: int
"""
left = 0
right = len(height) -1
global_area = 0
while left < right and right < len(height):
currrnt_area = min(height[left], height[right]) * (right - left)
global_area = max(global_area, currrnt_area)
if height[left] < height[right]:
left = left+1
else:
right = right - 1
return global_area
if __name__ == '__main__':
slution = Solution()
re = slution.maxArea([1,1])
print(re)
三数之和
使用双指针
1 首先排序
2 不重复第一个元素(第一个指针) 保证第一个元素不重复
3 不重复的遍历第二个元素(第二个指针,从第一个指针往后), 找到第三个元素
注:其实我觉得 if cur + nums[l] + nums[r] > 0 之后 r可以连续走的
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
if len(nums) < 3: return []
nums, res = sorted(nums), []
for i in range(len(nums) - 2):
cur, l, r = nums[i], i + 1, len(nums) - 1
if res != [] and res[-1][0] == cur: continue # Drop duplicates for the first time.
while l < r:
if cur + nums[l] + nums[r] == 0:
res.append([cur, nums[l], nums[r]])
while l < r - 1 and nums[l] == nums[l + 1]:
l += 1
while r > l + 1 and nums[r] == nums[r - 1]:
r -= 1
if cur + nums[l] + nums[r] > 0: # 注:这里为什么不连续接着走呢,其实我觉得是可以的
r -= 1
else:
l += 1
return res
作者:代码随想录
链接:https://leetcode.cn/problems/3sum/solutions/1670261/dai-ma-sui-xiang-lu-dai-ni-gao-ding-ha-x-jzkx/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
滑动窗口
无重复最长子串
题解:
用一个滑窗和hash
具体算法
1、while循环(左窗口<=窗口 and 右窗口小于字符串长度),判断右窗口指向的元素是否重复,如果不在更新最大值,右窗口++。否则左窗口++,窗口内的元素--
注:
先判断再加进滑窗,不要每次循环都加
先想好伪代码,再开始写代码
from collections import defaultdict
class Solution(object):
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
left, right = 0, 0
hashmap = defaultdict(int)
res = 0
if len(s) == 0:
return 0
while left <= right and right < len(s):
if hashmap[s[right]] > 0:
hashmap[s[left]] -=1
left += 1
else:
hashmap[s[right]] += 1 # 注:先判断再加,不要先加再判断
res = max(res, right - left +1)
right += 1
return res
438. 找到字符串中所有字母异位词(有技巧性)
题解:
用一个滑窗和hash(hash使用26个字母的编号)
具体算法
1、滑窗长度固定,慢慢平移。每滑动一格重新计算滑窗的编码判断是否相等
注:本题最关键的点在于如何让设计hash,注26个编码的实现方法,使用一个26个字母的list,
或者使用dict但是需要提前把所有的值都赋值好,不能动态增加
class Solution(object):
def findAnagrams(self, s, p):
"""
:type s: str
:type p: str
:rtype: List[int]
"""
def get_hash(strp):
'''
返回26个字母的编码
:param s:
:return:
'''
re_hash = [0]*26
for i in range(0, len(strp)):
re_hash[ord(strp[i])-ord('a')] += 1
return re_hash
re = []
p_hash = get_hash(p)
left = 0
right = len(p)-1
s_hash = get_hash(s[left:right+1])
while right < len(s):
if left > 0:
s_hash[ord(s[right])-ord('a')] += 1
if s_hash == p_hash:
re.append(left)
s_hash[ord(s[left])-ord('a')] -= 1
right += 1
left += 1
return re
子串
和为k的子数组 ( 前缀和)
- 和为 K 的子数组
题解:
1、先求解前缀和 : pre_sum_list[i] = pre_sum_list[i-1] + nums[i-1]
2、最后就变成了在前缀和序列中求解两数之差为k的数量 ps:区间和:pre_sum_list[j]-pre_sum_list[i] = [i, j)
注:前缀和
pre_sum_list[0] 为 0
pre_sum_list[1] 为 num[0]
pre_sum_list[2] 为 num[0] + num[1]
pre_sum_list[5] 为 num[0] num[1] num[2] num[3] num[4]
区间和:pre_sum_list[j]-pre_sum_list[i] = [i, j)
class Solution(object):
def subarraySum(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
re_sum = 0
# 获取前缀和 pre_sum_list[1] = pre_sum_list[0] + nums[0]
pre_sum_list = [0]*(len(nums)+1)
for i in range(1, len(pre_sum_list)):
pre_sum_list[i] = pre_sum_list[i-1] + nums[i-1]
# 获取两数之和 pre_sum_list[j]-pre_sum_list[i] = [i, j)
pre_sum_map = defaultdict(int) # 使用dict而不是set
for i in range(0, len(pre_sum_list)):
target = pre_sum_list[i] - k
re_sum += pre_sum_map[target]
pre_sum_map[pre_sum_list[i]] += 1
return re_sum
滑动窗口最大值(困难 单调队列 双端队列)
使用:单调队列
参考资料:https://www.bilibili.com/video/BV1bM411X72E/?vd_source=d8ef884ad74a2afc063f9ca90338ca3c
题解:
这里的单调队列有一个特点,只有后来的且比他小的才能加进去。因为前面这些值一定不是解
1、遍历nums,每遍历一个元素,将队列中比它小的全从尾部弹出来,将该元素入队
2、将超出k的左边部分出队,小于k时不需要出队
3、队首就是解。将队首加入结果数组
from collections import deque
class Solution(object):
def maxSlidingWindow(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
ans = []
q = deque()
for i, x in enumerate(nums):
#右入 将比当前小的元素都弹出来,右边弹出
while q and nums[q[-1]] <= x:
q.pop()
q.append(i)
# 左出
if i-q[0]>=k: # 将超出滑窗范围的元素弹出,这种做法是因为小于k时不需要出队
q.popleft() # 左边弹出
if i >= k-1: # 刚开始滑窗大小未到k
ans.append(nums[q[0]]) # 将当前
return ans
最小覆盖子串(困难 )
题解:
1、不断增加j使滑动窗口增大,直到窗口包含了T的所有元素
2、不断增加i使滑动窗口缩小,因为是要求最小字串,所以将不必要的元素排除在外,使长度减小,直到碰到一个必须包含的元素,这个时候不能再扔了,再扔就不满足条件了,记录此时滑动窗口的长度,并保存最小值
3 让i再增加一个位置,这个时候滑动窗口肯定不满足条件了,那么继续从步骤一开始执行,寻找新的满足条件的滑动窗口,如此反复,直到j超出了字符串S范围。
from collections import Counter
class Solution:
def minWindow(self, s: str, t: str) -> str:
ans_left, ans_right = -1, len(s)
left = 0
cnt_s = Counter() # s 子串字母的出现次数,初始化方法
cnt_t = Counter(t) # t 中字母的出现次数
for right, c in enumerate(s): # 移动子串右端点
cnt_s[c] += 1 # 右端点字母移入子串
while cnt_s >= cnt_t: # 涵盖
if right - left < ans_right - ans_left: # 找到更短的子串
ans_left, ans_right = left, right # 记录此时的左右端点
cnt_s[s[left]] -= 1 # 左端点字母移出子串
left += 1 # 移动子串左端点
return "" if ans_left < 0 else s[ans_left: ans_right + 1]
普通数组
53. 最大子数组和
动态规划
核心公式:dp[i] = max(nums[i], dp[i-1]+nums[i])
class Solution(object):
def maxSubArray(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
dp = [0] * len(nums)
dp[0] = nums[0]
max_sum = nums[0]
for i in range(1, len(nums)):
dp[i] = max(nums[i], dp[i-1]+nums[i])
max_sum = max(max_sum, dp[i])
return max_sum
作者:YingL
链接:https://leetcode.cn/problems/maximum-subarray/solutions/2676294/53-zui-da-zi-shu-zu-he-by-user5776-6kds/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
合并区间
题解:
1、排序
2、依次加入数组,加入之前和最后一个元素进行对比。如果有交叉就合并然后加入数组
class Solution(object):
def merge(self, intervals):
if len(intervals) == 0:
return []
intervals.sort()
re = []
re.append(intervals[0])
for i, item in enumerate(intervals):
if item[0] <= re[-1][1]:
last = re.pop()
re.append([last[0], max(last[1], item[1])]) # 合并然后加入数组
else:
re.append(item)
return re
轮转数组
题解:
1、保存前面的的n-k个元素
2、删除前面的n-k个元素, 只剩下k个元素了
注意:不能使用 nums = last_k 这样无法修改nums中的内容
import copy
from collections import Counter
class Solution:
def rotate(self, nums, k):
k = k % len(nums)
retote = nums[:len(nums)-k] # 保存前面的的n-k个元素
last_k = nums[len(nums)-k:] # 保存前面的的n-k个元素
last_k.extend(retote)
nums[:] = last_k # 注意:不能使用 nums = last_k
238. 除自身以外数组的乘积
使用前缀乘积和后缀乘积。
题解:
1、 提前将从左到右的所有乘积和从右到左的所有乘积保存下来,然后进行相乘。
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
# 前缀乘积
pre, sum = [1], 1
for i in nums:
sum = sum * i
pre.append(sum)
# 后缀乘积
suf, sum = [1], 1
for i in range(len(nums)):
sum = sum * nums[len(nums) - i - 1]
suf.append(sum)
# 求解
ans = []
for i in range(len(nums)):
total = pre[i] * suf[len(nums) - i - 1]
ans.append(total)
return ans
作者:Nebula
链接:https://leetcode.cn/problems/product-of-array-except-self/solutions/1820552/238-by-nebula-a0-c06n/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
作者:YingL
链接:https://leetcode.cn/problems/product-of-array-except-self/solutions/2676286/238-chu-zi-shen-yi-wai-shu-zu-de-cheng-j-1pnp/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
缺失的第一个正整数(困难-我感觉很简单)
数组长度定了,正整数就定了,所以就可以从小到大遍历正整数,看看哪个最先不在原数组中。具体而言,假设我的数组长度为5,那么就应该出现12345,遍历12345,看哪个不在数组中,如果都在就是满足条件,返回6
题解;
1、遍历一遍数组,使用hash_table 保存所有出现过值,用于判断该值是否存在
2、从1数到len(num) 看哪个值不存在
class Solution(object):
def firstMissingPositive(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
s = defaultdict(int)
for item in nums:
s[item] += 1
for i in range(1,len(nums)+1):
if s[i] == 0:
return i
return len(nums)+1
作者:YingL
链接:https://leetcode.cn/problems/first-missing-positive/solutions/2678840/que-shi-de-di-yi-ge-zheng-shu-by-user577-vmna/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
矩阵
矩阵置零
题解:
1、遍历一遍矩阵,记录有0的行与列
2、将相关行与列全部置为0
class Solution:
def setZeroes(self, matrix):
m, n = len(matrix), len(matrix[0])
row, col = [False] * m, [False] * n
for i in range(m):
for j in range(n):
if matrix[i][j] == 0:
row[i] = col[j] = True
# 一次遍历即可
for i in range(m):
for j in range(n):
if row[i] or col[j]: # 如果在目标行或者目标列,就置为0
matrix[i][j] = 0
作者:YingL
链接:https://leetcode.cn/problems/set-matrix-zeroes/solutions/2678862/ju-zhen-zhi-ling-by-user5776-u6gd/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
54.螺旋矩阵(这个题代码量较大)
讲述看到这一题的思路
两个方向,四个边界
模拟寻路
1、右边界,上边界下移,j不变,i向下
2、下边界,右边界左移,i不变,j向左
3、左边界,下边界上移,j不变,i向上
4、上边界,左边界右移,i不变,j向右
作者:YingL
链接:https://leetcode.cn/problems/spiral-matrix/solutions/2679067/54-luo-xuan-ju-zhen-by-user5776-e2em/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution(object):
def spiralOrder(self, matrix):
"""
:type matrix: List[List[int]]
:rtype: List[int]
"""
i_index, j_index = 0, 0
re = []
letf_bound, righ_bound = 0, len(matrix[0])-1
up_bound, down_bound = 0, len(matrix)-1
drj , dri = 1,0 # drj左右方向 dri上下方向
while letf_bound <= righ_bound and up_bound <= down_bound:
re.append(matrix[i_index][j_index])
# 右边界,上边界下移,j不变,i向下
if drj ==1 and j_index == righ_bound:
drj = 0
dri = 1
up_bound += 1
# 下边界,右边界左移,i不变,j向左
if dri == 1 and i_index == down_bound:
drj = -1
dri = 0
righ_bound -= 1
# 左边界,下边界上移,j不变,i向上
if drj ==-1 and j_index == letf_bound:
drj = 0
dri = -1
down_bound -= 1
# 上边界,左边界右移,i不变,j向右
if dri == -1 and i_index == up_bound:
drj = 1
dri = 0
letf_bound += 1
i_index = i_index + dri
j_index = j_index + drj
return re
if __name__ == '__main__':
re=Solution().spiralOrder([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
print(re)
作者:YingL
链接:https://leetcode.cn/problems/spiral-matrix/solutions/2679067/54-luo-xuan-ju-zhen-by-user5776-e2em/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
48.旋转图像(找规律)
题解:
1、找规律 第一行变成倒数第一列
matrix_new[j][n - i - 1] = matrix[i][j]
注:最后返回的时候要用切片法 matrix[:] = matrix_new
class Solution:
def rotate(self, matrix):
n = len(matrix)
# Python 这里不能 matrix_new = matrix 或 matrix_new = matrix[:] 因为是引用拷贝
matrix_new = [[0] * n for _ in range(n)]
for i in range(n):
for j in range(n):
matrix_new[j][n - i - 1] = matrix[i][j]
# 不能写成 matrix = matrix_new
matrix[:] = matrix_new
作者:YingL
链接:https://leetcode.cn/problems/rotate-image/solutions/2679100/48-xuan-zhuan-tu-xiang-by-user5776-tx9x/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
搜索二维矩阵(从右上角的点)
讲述看到这一题的思路
1、从右上角的点开始找,如果大于target就向左,小于target就向下
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
m, n = len(matrix), len(matrix[0])
x, y = 0, n - 1
while x < m and y >= 0:
if matrix[x][y] == target:
return True
if matrix[x][y] > target:
y -= 1
else:
x += 1
return False
作者:YingL
链接:https://leetcode.cn/problems/search-a-2d-matrix-ii/solutions/2679113/240-sou-suo-er-wei-ju-zhen-by-user5776-8aka/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
链表
160.相交链表(使用hash方法)
双指针法不太好懂。这里推荐使用hash方法
题解:
1、遍历A,将所有的节点使用set存起来,
2、顺序遍历B 判断是第一个在set中的节点返回
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
# 创建一个散列表,用以记录遍历经过的节点
visited = set()
# 遍历第一个list,并将所有node放入set
temp = headA
while temp:
visited.add(temp)
temp = temp.next
# 遍历第二个list,并检查是否有任何node在set中
temp = headB
while temp:
if temp in visited:
# 发现交叉,返回交叉
return temp
temp = temp.next
# 没有发现交叉
return None
作者:YingL
链接:https://leetcode.cn/problems/intersection-of-two-linked-lists/solutions/2679230/160-xiang-jiao-lian-biao-by-user5776-gnj6/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
206.反转链表(平行赋值法)
题解:
1、使用python的语法糖 cur.next, pre, cur = pre, cur, cur.next #平行赋值法
2、一次遍历i即可
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
cur, pre = head, None
while cur:
cur.next, pre, cur = pre, cur, cur.next #平行赋值法
return pre
作者:YingL
链接:https://leetcode.cn/problems/reverse-linked-list/solutions/2679241/206-fan-zhuan-lian-biao-by-user5776-46zl/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
234.回文链表(反转数组进行对比)
先将数值保存到数组中,反转数组进行对比,构造一个列表的反向副本
题解:
1、 遍历链表将数值保存到数组中
2、反转数组看是否相等
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
vals = []
current_node = head
while current_node is not None:
vals.append(current_node.val)
current_node = current_node.next
return vals == vals[::-1] # 反转数组进行对比,构造一个列表的反向副本
作者:YingL
链接:https://leetcode.cn/problems/palindrome-linked-list/solutions/2679254/234-hui-wen-lian-biao-by-user5776-4lhf/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
141.环形链表(快慢指针)
思路:
快慢指针,快指针两倍速。如果有环,一定会相遇,如果没有相遇就无环。
(2vt-vt) = vt(当vt等于k时一定会相遇,而vt也一定会有等于k的时候)
注:其实我觉得这题也可以用hash表去做,使用一个set
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
slow = fast = head
while fast:
if fast.next:
fast = fast.next.next
else:
break
slow = slow.next
if fast == slow: # 如果有环,一定会相遇,如果没有相遇就无环。
return True
return False
142.环形链表 II(二次相遇)
题解
1、第一次相遇时,快指针从头再次出发并降为慢指针,
2、第二次相遇时即为环的起始点
class Solution(object):
def detectCycle(self, head):
fast, slow = head, head
while True:
if not (fast and fast.next): return # 无环返回none
fast, slow = fast.next.next, slow.next
if fast == slow: break # 第一次相遇
fast = head
while fast != slow: # 第二次相遇
fast, slow = fast.next, slow.next
return fast
作者:YingL
链接:https://leetcode.cn/problems/linked-list-cycle-ii/solutions/2679389/142-huan-xing-lian-biao-ii-by-user5776-zup7/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
合并有序链表
题解:
1、使用一个额外的链表 cur = ListNode(-1)
class Solution(object):
def mergeTwoLists(self, list1, list2):
"""
:type list1: Optional[ListNode]
:type list2: Optional[ListNode]
:rtype: Optional[ListNode]
"""
# 若其中任一个链表为空,返回另一个
if not list1:
return list2
if not list2:
return list1
res = cur = ListNode(-1)
while list1 and list2:
if list1.val>list2.val:
cur.next = list2
list2 = list2.next
else:
cur.next = list1
list1 = list1.next
cur = cur.next
cur.next = list1 if list1 else list2
return res.next
两数相加
题解:
其实核心就是 v_new = n1.val + n2.val + target
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
cur = 0
n1, n2 = l1, l2
head = ListNode()
node = head
while n1 or n2 or cur:
if n1:
cur += n1.val
n1 = n1.next
if n2:
cur += n2.val
n2 = n2.next
node.next = ListNode(cur % 10)
node = node.next
cur //= 10
return head.next
删除倒数第N个节点
题解:
1 快慢指针,让一个指针提前走N步
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
dummy = ListNode(0, head)
first = head
second = dummy
for i in range(n): # 将快指针往前多走n步
first = first.next
while first: # 两个指针齐头并进
first = first.next
second = second.next
second.next = second.next.next # 最后删除特定节点
return dummy.next
作者:YingL
链接:https://leetcode.cn/problems/remove-nth-node-from-end-of-list/solutions/2680356/shan-chu-lian-biao-de-dao-shu-di-n-ge-ji-qujq/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
两两交换链表中的节点
题解:
1、使用递归。每次处理两个
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head is None or head.next is None: # 递归边界
return head # 不足两个节点,无需交换
node1 = head # 需要交换的左边节点
node2 = head.next #需要交换的右边节点
node3 = node2.next # 需要递归处理的后面节点
# 交换节点
node1.next = self.swapPairs(node3)
node2.next = node1
return node2 # 返回交换后的链表头节点
作者:YingL
链接:https://leetcode.cn/problems/swap-nodes-in-pairs/solutions/2680374/liang-liang-jiao-huan-lian-biao-zhong-de-p9ej/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
K个一组翻转链表
题解:
1、使用栈来进行翻转。(易错点:注意将值放到stack里面,二不是把整个node放进去)
2、使用递归来连续翻转。
from typing import Optional
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
stack = []
h = ListNode(0, head) # 这是一个辅助节点,方便写循环
node1, node2 = h, h # node1是头部节点,node2是尾部节点
# 向后移动,将
for _ in range(k):
if not node2.next or not node2:
print_linked_list(head)
return head
node2 = node2.next
stack.append(node2.val) # 注意指针的引用关系非常容易出错,所以这里保存值就行,不要保存指针
node2.next = self.reverseKGroup(node2.next, k) # 递归调整
for _ in range(k):
node1.next.val = stack.pop()
node1 = node1.next
print_linked_list(head)
return head
随机链表复制
排序链表
合并K个升序链表
LRU缓存
二叉树
二叉树的最大深度
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if root is None:
return 0
left_depth = self.maxDepth(root.left)
right_depth = self.maxDepth(root.right)
depth = max(left_depth, right_depth) + 1
return depth
作者:YingL
链接:https://leetcode.cn/problems/maximum-depth-of-binary-tree/solutions/2709345/er-cha-shu-de-zui-da-shen-du-by-user5776-jee9/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
翻转二叉树
题解:
1、翻转左边
2、翻转右边
3、将左右进行交换
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if root is None:
return root
self.invertTree(root.left)
self.invertTree(root.right)
root.left, root.right = root.right, root.left
return root
对称二叉数(有点难)
这题其实是如何判断两个树是否对称,dfs齐头并进。中轴对称的意思是每一层是要对称的。而不是节点内部需要对称
判断两个树是否对称,使用dfs。这题容易被误解成判断两个树是相等的了
class Solution:
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
if root is None:
return True
return self.dfs(root.left, root.right)
# 判断两课树是否对称,相当于搞了两个指针,走到对应的节点时,判断左右是否对称
def dfs(self, root1: Optional[TreeNode], root2: Optional[TreeNode])-> bool:
if root1 is None and root2 is None:
return True
if root1 is None and root2 is not None:
return False
if root2 is None and root1 is not None:
return False
if root1.val != root2.val:
return False
return self.dfs(root1.left, root2.right) and self.dfs(root1.right, root2.left)
作者:YingL
链接:https://leetcode.cn/problems/symmetric-tree/solutions/2709422/101-dui-cheng-er-cha-shu-by-user5776-78u5/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
二叉树的直径(有技巧性)
题解
1、使用一个全局的 max_depth 保存全局的最大路径
2、但每次返回的当前节点的深度(从底向上的深度)
class Solution:
def __init__(self):
self.max_depth = 0
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
if root is None:
return 0
left_depth = self.dfs(root.left)
right_depth = self.dfs(root.right)
self.max_depth = max(self.max_depth, left_depth + right_depth)
return self.max_depth
def dfs(self, root: Optional[TreeNode]) -> int:
'''
获取当前节点的最大深度
:param root:
:return:
'''
if root is None:
return 0
left_dept = self.dfs(root.left)
right_dept = self.dfs(root.right)
self.max_depth = max(self.max_depth, left_dept+right_dept) # 使用一个全局的 max_depth 保存全局的最大路径
return max(left_dept, right_dept) + 1
作者:YingL
链接:https://leetcode.cn/problems/diameter-of-binary-tree/solutions/2709660/gooder-cha-shu-de-zhi-jing-by-user5776-um96/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
二叉树层次遍历
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
本题的难度主要在于如何识别每一层
题解:
1、 外层循环判断队列是否结束
2、里层循环只遍历队列长度
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if not root: return []
res, queue = [], collections.deque()
queue.append(root)
while queue: # 队列不为空
tmp = []
cur_len = len(queue)
for _ in range(cur_len): # 虽然queue在一直增加,但是 len(queue) 首次计算之后就不会再变了
node = queue.popleft()
tmp.append(node.val)
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
res.append(tmp)
return res
作者:YingL
链接:https://leetcode.cn/problems/binary-tree-level-order-traversal/solutions/2712963/102-er-cha-shu-de-ceng-xu-bian-li-by-use-j773/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
108. 将有序数组转换为二叉搜索树(构思巧妙)
平衡二叉树的特征是左右深度差不能超过1
二叉搜索树的特点是左小右大,二叉搜索树的中序遍历是升序序列
平衡二叉搜索树的特点是 ,它的中序遍历为升序序列,且根节点在升序序列的中间。基于这个特点,每次取中间的节点做根节点(保证左边的左右深度差不超过1),然后递归构建一棵树即可。
这里构建的方法有三种,最后构建出的都是平衡二叉搜索树:
方法一:中序遍历,总是选择中间位置左边的数字作为根节点
方法二:中序遍历,总是选择中间位置右边的数字作为根节点
方法二:中序遍历,随机选择中间位置左边或者右边数字作为根节点
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
def dfs(left, right):
if left > right:
return None
# 总是选择中间位置左边的数字作为根节点
mid = (left + right) // 2
root = TreeNode(nums[mid])
root.left = dfs(left, mid - 1)
root.right = dfs(mid + 1, right)
return root
return helper(0, len(nums) - 1)
作者:力扣官方题解
链接:https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/solutions/312607/jiang-you-xu-shu-zu-zhuan-huan-wei-er-cha-sou-s-33/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
作者:YingL
链接:https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/solutions/2712989/108-jiang-you-xu-shu-zu-zhuan-huan-wei-e-k2ef/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
验证二叉搜索树
使用递归,遍历每一个节点时,要求是左节点要小于当前节点,右节点要大于当前节点
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
def helper(node, lower = float('-inf'), upper = float('inf')) -> bool:
if not node:
return True
val = node.val
if val <= lower or val >= upper:
return False
if not helper(node.right, val, upper):
return False
if not helper(node.left, lower, val):
return False
return True
return helper(root)
230. 二叉搜索树中第K小的元素
思路:
在二叉搜索树中,任意子节点都满足“左子节点 <<< 根节点 <<< 右子节点”的规则。因此二叉搜索树具有一个重要性质:二叉搜索树的中序遍历为递增序列。
所以本题就是中序遍历第k个元素
代码
class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
def dfs(root):
if not root: return
dfs(root.left)
if self.k == 0: return
self.k -= 1
if self.k == 0: self.res = root.val
dfs(root.right)
self.k = k
dfs(root)
return self.res
作者:Krahets
链接:https://leetcode.cn/problems/kth-smallest-element-in-a-bst/solutions/2361685/230-er-cha-sou-suo-shu-zhong-di-k-xiao-d-n3he/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
199. 二叉树的右视图
题解:
其实就是层次遍历,使用层次遍历获取层次遍历的最后一个点
1 队列中保存值和值的深度
queue = deque([(root, 0)])
2、rightmost_value_at_depth[depth] = node.val 字典中保存每个depth的最后节点值
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rightSideView(self, root: TreeNode) -> List[int]:
rightmost_value_at_depth = dict() # 深度为索引,存放节点的值
max_depth = -1
queue = deque([(root, 0)])
while queue:
node, depth = queue.popleft()
if node is not None:
# 维护二叉树的最大深度
max_depth = max(max_depth, depth)
# 由于每一层最后一个访问到的节点才是我们要的答案,因此不断覆盖 rightmost_value_at_depth[depth] = val
rightmost_value_at_depth[depth] = node.val
queue.append((node.left, depth + 1))
queue.append((node.right, depth + 1))
return [rightmost_value_at_depth[depth] for depth in range(max_depth + 1)]
将二叉树转化为链表
这种做法性能超过68%的人
题解:
1、使用数组获取先序遍历
2、将先序遍历数组拼成链表
class Solution:
def flatten(self, root: TreeNode) -> None:
preorderList = list()
def preorderTraversal(root: TreeNode):
if root:
preorderList.append(root)
preorderTraversal(root.left)
preorderTraversal(root.right)
preorderTraversal(root)
size = len(preorderList)
for i in range(1, size):
prev, curr = preorderList[i - 1], preorderList[i]
prev.left = None
prev.right = curr
105. 从前序与中序遍历序列构造二叉树
题解
1、先序遍历的第一个节点就是根节点
2、定位根节点的index,以该节点位置为界划分左右子树。
注:这里先遍历一遍中序遍历的数列,提前缓存所有节点的index值
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
def recur(root, left, right):
if left > right: return # 递归终止
node = TreeNode(preorder[root]) # 建立根节点
i = dic[preorder[root]] # 划分根节点、左子树、右子树
node.left = recur(root + 1, left, i - 1) # 开启左子树递归
node.right = recur(i - left + root + 1, i + 1, right) # 开启右子树递归
return node # 回溯返回根节点
dic, preorder = {}, preorder
for i in range(len(inorder)):
dic[inorder[i]] = i
return recur(0, 0, len(inorder) - 1)
路径总和(困难 确实细节很多)
前缀和+回溯+hashtabe+两数之和
题解:
1、使用前序遍历。每遍历到一个节点时,计算当前路径的路径和。并把当前和加到一个hashtable种,使用两数之和的方法求解是否存在一个target
2、基于当前路径和 currSum 与 prefixSum 开始回溯左分支和右分支
3、完成当前节点的处理后,进行回溯,即将当前前缀和的计数减1。要将自己的痕迹抹除
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
# 初始化路径计数器
self.count = 0
# 初始化前缀和字典,用于记录各个前缀和出现的次数
prefixSum = {0: 1} # 前缀和为0的路径默认有一个,即不选择任何节点的情况
def dfs(node, currSum):
# 如果当前节点为空,直接返回,不做任何处理
if not node:
return
# 更新当前路径的累计和
currSum += node.val
# 计算从当前节点开始,是否存在一条路径的和为targetSum
oldSum = currSum - targetSum
# 如果oldSum在前缀和字典中,说明存在一条有效路径,将其出现次数加到总计数器中
self.count += prefixSum.get(oldSum, 0)
# 将当前前缀和加入到字典中,或更新其出现次数
prefixSum[currSum] = prefixSum.get(currSum, 0) + 1
# 递归访问当前节点的左右子节点
dfs(node.left, currSum)
dfs(node.right, currSum)
prefixSum[currSum] -= 1
# 从根节点开始进行深度优先搜索
dfs(root, 0)
return self.count
二叉树的最近公共祖先
题解:
1、dfs(root, p, q) 递归判断 p,q是不是root的子孙
class Solution:
def __init__(self):
self.parent = None
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
def dfs(root: 'TreeNode', p: 'TreeNode', q: 'TreeNode'):
# 判断当前root是否为p或q的祖宗节点
if not root:
return False
if root.val == p.val or root.val == q.val:
return True
is_left = dfs(root.left, p, q)
is_right = dfs(root.right, p, q)
if is_left and is_right: # 如果正好是公共节点就把结果保存下来,这个结果是唯一的
self.parent = root
return is_left or is_right # 左右节点只要有一个包含就说明是他的祖宗
# 如果p是q的子孙节点,那么q是公共节点
if dfs(q, p, p): self.parent = q
if dfs(p, q, q): self.parent = p
dfs(root, p, q)
return self.parent
贪心算法
买卖股票的最佳时机(1次遍历)
思路:
没用贪心,一次遍历。每次都保存迄今为止的最大值和最小值
class Solution:
def maxProfit(self, prices: List[int]) -> int:
min_val = 10000
max_val = 0
for item in prices:
max_val = max(max_val, item-min_val)
min_val = min(min_val, item)
return max_val
作者:YingL
链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/solutions/2745769/mai-mai-gu-piao-de-zui-jia-shi-ji-1ci-bi-zqqb/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
跳跃游戏
思路
动态规划,没用贪心
判断之前的每一个点是否有跳跃能
class Solution:
def canJump(self, nums: List[int]) -> bool:
n = len(nums)
dp = [False] * len(nums)
dp[0] = True # 注意初始化
for j in range(1, n):
for i in range(1, j+1):
# print(j-i, nums[j-i], dp[j-i]) # 这里适合打断点调试
if nums[j-i] >= i and dp[j-i] == True:
dp[j] = True
break
return dp[-1]
作者:YingL
链接:https://leetcode.cn/problems/jump-game/solutions/2745890/55-tiao-yue-you-xi-dong-tai-gui-hua-by-u-t86t/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
跳跃游戏 II
思路:
1、每次在上次能跳到的范围(end)内选择一个能跳的最远的位置(其实就是遍历到end之前)
2、当 i 到达 end的时候,下次起跳的最大位置也确定下来了。你不用管是从哪个点跳过去的,反正是只跳一步就行。此时 step++,代表我往后跳了。注意这里并不代表是从end跳的,而是从上一个范围中跳的
3、继续往后遍历。如果i走完了,end还没达到,那就说明上一次的跳跃就已经可以跳到最后了
我的写法
class Solution:
def jump(self, nums: List[int]) -> int:
if len(nums) == 1:
return 0
step , end_index = 0, 0
max_index = 0
for i, val in enumerate(nums):
max_index = max(nums[i]+i, max_index)
if i == end_index:
end_index = max_index
step += 1 # 表示先跳一步
if end_index >= len(nums)-1:
# 如果上次跳跃可以到达数据结尾,那就无需再遍历了
return step
return step
作者:YingL
链接:https://leetcode.cn/problems/jump-game-ii/solutions/2750768/tiao-yue-you-xi-ii-by-user5776-i4bl/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
763. 划分字母区间
思路
在一个分组的字母有这样一个规律,最后一个位置一定是分组中的某个字母的最后一次出现位置。
本题只需要一次遍历即可
算法步骤:
1 遍历所有元素,每次遍历都求取当前字母的最后一个位置,一旦index = last就说明,当前的分组刚好结束。
2、当index = last时 初始化所有条件继续遍历
注意求组字母的最后一个位置时,从后往前寻找
class Solution:
def partitionLabels(self, s: str) -> List[int]:
def find_last(s, target):
for i in range(len(s)-1, -1,-1):
if s[i] == target:
return i
return -1
index = 0
re = []
result = []
last = find_last(s, s[index])
while index < len(s):
tmp = find_last(s, s[index])
last = max(last, tmp)
result.append(s[index])
if index == last:
re.append("".join(result))
result = []
index += 1
return [len(item) for item in re]
作者:YingL
链接:https://leetcode.cn/problems/partition-labels/solutions/2758473/763-hua-fen-zi-mu-qu-jian-by-user5776-e3gh/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
动态规划
70. 爬楼梯
思路:
1、 dp[n]表示第n级台阶有多少种方法。要么从n-1跳,要么从n-2跳,所以有
dp[i] = dp[i-1] + dp[i - 2]
2、 引入一个dp[0] = 1
class Solution:
def climbStairs(self, n: int) -> int:
dp = [1] * (n+1)
for i in range(2, n+1):
dp[i] = dp[i-1] + dp[i - 2]
return dp[n]
118. 杨辉三角
思路
感觉不像是一个正儿八经的动态规划
1 每一层的数量其实是确定的,如第三层有3个,第四层有4个 . result = [0] * (i+1) #每一层的数量其实是确定的
2 每次都将首尾两个值赋为1 result[0] = 1 # 将首尾两个值赋为1
3 然后进行迭代即可 result[j] = re[i-1][j-1] + re[i-1][j]
class Solution:
def generate(self, numRows: int) -> List[List[int]]:
re = [[1], [1,1]]
if numRows <= 2:
return re[:numRows]
for i in range(2, numRows):
result = [0] * (i+1) #每一层的数量其实是确定的
result[0] = 1 # 将首尾两个值赋为1
result[-1] = 1
for j in range(1,i):
result[j] = re[i-1][j-1] + re[i-1][j]
re.append(copy.deepcopy(result))
return re
作者:YingL
链接:https://leetcode.cn/problems/pascals-triangle/solutions/2755007/118-yang-hui-san-jiao-by-user5776-vxfq/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
198. 打家劫舍
思路
1 dp[i]表示打劫到i间房子的时候最大值为多少,则状态转移如下
dp[i] = max(dp[j]+nums[i], dp[i]),这里的j从[0, i-2],即非相邻的房子跳过来
class Solution:
def rob(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
dp = copy.deepcopy(nums)
max_val = dp[0]
for i in range(1, len(nums)):
for j in range(0, i-1):
dp[i] = max(dp[j]+nums[i], dp[i])
max_val = max(dp[i], max_val)
return max_val
作者:YingL
链接:https://leetcode.cn/problems/house-robber/solutions/2755057/198-da-jia-jie-she-by-user5776-yxnw/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
完全平方数
思路
本题和零钱组合很像,但也有个显著差别。零钱是累加,本题不是累加
1、dp初始值为0 dp = [0] * (n+1)
2、遍历n以内的所有平方数jj,min_val = min(dp[i-jj] + 1, min_val)
class Solution:
def numSquares(self, n: int) -> int:
dp = [0] * (n+1) # 初始值为0
dp[1] = 1
for i in range(1,n+1):
min_val = inf # 注意这里需要获取的是 min([np[i-j**2]+1]),所以要使用一个中间变量
for j in range(1, int((n)**(0.5))+1): # 根号n
if (i-j*j) < 0:
continue
min_val = min(dp[i-j*j] + 1, min_val)
dp[i] = min_val
return dp[n]
作者:YingL
链接:https://leetcode.cn/problems/perfect-squares/solutions/2755208/279-wan-quan-ping-fang-shu-by-user5776-kvpt/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
零钱兑换
思路:
我们可以使用状态 dp[i] 表示凑成金额 i 所需的最少硬币个数。
如果我们枚举不同面额的硬币 coins[j],那么 dp[i] = 1 + dp[i - coins[j]]。由于 j 有多种取值,
因此 dp[i] = min(1 + dp[i - coins[j]])。
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
dp = [amount + 1] * (amount + 1) # dp[i]表示凑成金额i所需的最少硬币个数,初始都为-1表示不可凑
dp[0] = 0 # 金额0需要0枚硬币
# 枚举每一个金额
for a in range(1, amount + 1):
# 枚举每一种硬币
for c in coins:
# 假设使用了硬币c,那么最少硬币数就由a-c转移来
if a - c < 0: continue # 说明当前面值已经超过了amount
dp[a] = min(dp[a], dp[a - c] + 1)
return dp[amount] if dp[amount] != amount+1 else -1 # 修正结果
作者:YingL
链接:https://leetcode.cn/problems/coin-change/solutions/2755139/322-ling-qian-dui-huan-by-user5776-lpeb/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
最长递增子序列
题解:dp
1、dp[i]表示以i结尾的最长子序列
2、遍历前面的dp[0-i],取他们当中最大的值+1即可
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums: return 0
dp = [1] * len(nums)
for i in range(len(nums)):
for j in range(i):
if nums[j] < nums[i]: # 如果要求非严格递增,将此行 '<' 改为 '<=' 即可。
dp[i] = max(dp[i], dp[j] + 1) # 注意这里必须要用max 因为可能会有多个递增序列
return max(dp)
乘积最大子数组

题解:
这里都是整数数组 ,但含有0
1、如果都是正数,则一定有 dp[i] = max(dp[i-1] * nums[i], num[i]) ,为什么呢?如果 12034.从4的角度来看,4要么乘以3 要么不乘,乘3的时候也有两种情况,就是3所能积累的最大值,所以 dp[4] = max(dp[3]x3 ,3)
2 因为这里有正反,所有就有下面这种动态转移方程
3 另外这里省去了dp[i-1]因为他们只需要用一次就行了
class Solution:
def maxProduct(self, nums: List[int]) -> int:
maxF = nums[0]
minF = nums[0]
ans = nums[0]
for i in range(1, len(nums)):
mx = maxF
mn = minF
maxF = max(mx * nums[i], max(nums[i], mn * nums[i]))
minF = min(mn * nums[i], min(nums[i], mx * nums[i]))
ans = max(maxF, ans)
return ans
二分法
搜索插入位置
有三种写法 :https://leetcode.cn/problems/search-insert-position/solutions/2023391/er-fen-cha-zhao-zong-shi-xie-bu-dui-yi-g-nq23
题解: 闭区间写法
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums)-1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
if nums[mid] < target:
left = mid +1
if nums[mid] > target:
right = mid -1
if nums[mid] > target:
return mid
else:
return mid + 1
二维矩阵搜索
题解:
从右上角开始搜索,往一边会变小,往另一边会变大,找能不能遇到target
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
m, n = len(matrix), len(matrix[0])
i, j = 0, n - 1
while i < m and j >= 0:
if matrix[i][j] == target:
return True
elif matrix[i][j] > target:
j -= 1
else:
i += 1
return False
在排序数组中查找第一个和最后一个
本题有两种思路,1、二分法+while 2、两次二分法
题解:两次二分法
1、结束条件发生了变化
2、right/left的变更条件也发生了变化
from typing import List
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def searchLeft(nums, target):
left, right = 0, len(nums)-1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target and (mid == 0 or nums[mid - 1] < target):
return mid
if nums[mid] < target:
left = mid +1
elif nums[mid] >= target:
right = mid -1
return -1
def searchRight(nums, target):
left, right = 0, len(nums)-1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target and (mid == len(nums)-1 or nums[mid + 1] > target):
return mid
if nums[mid] <= target:
left = mid +1
elif nums[mid] > target:
right = mid -1
return -1
return [searchLeft(nums, target), searchRight(nums, target)]
搜索旋转排序数组
题解:双重二分法
1、左右区间端点不一样,先判断在左区间还是右区间,左右区间端点不一样
2、在左区间,永远和 nums[0] 比, 但是 mid = (l + r) // 2,其他和二分法一样
3、右区间永远和 nums[len(nums) - 1] 比 但是 mid = (l + r) // 2, 其他和二分法一样
from typing import List
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]: # 在左区间,永远和 nums[0] 比
if nums[0] <= target < nums[mid]:
r = mid - 1
else:
l = mid + 1
# 在右区间
else:
if nums[mid] < target <= nums[len(nums) - 1]:
l = mid + 1
else:
r = mid - 1
return -1
搜索旋转数组最小值
题解:
变形的二分查找法
1、根据左右区间适当变更 左右指针
2、当mid=low=high的时候就说明在最低点重合了
class Solution:
def findMin(self, nums: List[int]) -> int:
low, high = 0, len(nums) - 1
while low <= high:
mid = low + (high - low) // 2
if nums[mid] > nums[high]:
# 在左区间 此时 low=mid+1因为mid绝对不可能是最小值
low = mid + 1
elif nums[low] == nums[high]:
# 相等的情况下一定是在最低点相遇
return nums[mid]
else:
# 在右区间此时 hign = mid因为high有可能是最小值
high = mid
return nums[low]
回朔
全排列
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
result = []
re = []
visited = [False] * len(nums)
def dfs(result):
if len(result) == len(nums):
re.append(copy.deepcopy(result))
return
for i in range(len(nums)):
if visited[i]:
continue
result.append(nums[i])
visited[i] = True
dfs(result)
result.pop()
visited[i] = False
dfs(result)
return re
子集
题解:
1、使用strat将起始点往后移动
2、
1 12 123
2 23
3
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
result = []
visited = [False]*len(nums)
re = [[]]
def dfs(start):
for i in range(start, len(nums)):
if not visited[i]:
result.append(nums[i])
re.append(copy.deepcopy(result))
visited[i] = True
dfs(i+1)
result.pop()
visited[i] = False
dfs(0)
return re
电话号码组合
题解:
1 这题和全排列很像,但是没有visited
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
letter_map = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz'
}
reslut = []
re = []
def backtracking(i):
if len(reslut) == len(digits):
re.append("".join(reslut))
return
sss = letter_map[digits[i]]
for j in range(len(sss)):
reslut.append(sss[j])
backtracking(i+1)
reslut.pop()
if len(digits) > 0:
backtracking(0)
return re
组合总数
题解:
1、这题和全排列很像,但是没有visited
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
result = []
re = []
def get_sum(result):
sum_num = 0
for item in result:
sum_num+=item
return sum_num
def backtrack():
if get_sum(result) == target:
re.append(copy.deepcopy(result))
elif get_sum(result) > target:
return
for i in range(len(candidates)):
result.append(candidates[i])
backtrack()
result.pop()
backtrack()
ddd = list(map(lambda item: tuple(sorted(item)), re))
ss = list(set(ddd))
ss2 = list(map(lambda item: list(item), ss))
return ss2
单词搜索
题解:
1 记录是否访问,遍历上下左右四个方向,然后回退自己的修改
2、每次回溯都从底向上判断是否满足。对于每条搜索链必须每个回溯的点都是true
3、最后的结果数组中必须至少存在一条是满足的
import copy
from typing import List
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
visited = [[False] * len(board[0]) for _ in range(len(board))]
def dfs(i, j, k):
if not 0 <= i < len(board) or not 0 <= j < len(board[0]) or board[i][j] != word[k] or visited[i][j]:
return False
if k == len(word) - 1:
return True
#从底向上 遍历四个方向必须有一个为true才行
visited[i][j] = True
res = dfs(i + 1, j, k + 1) or dfs(i - 1, j, k + 1) or dfs(i, j + 1, k + 1) or dfs(i, j - 1, k + 1)
visited[i][j] = False
return res
for i in range(len(board)):
for j in range(len(board[0])):
if dfs(i, j, 0): return True
return False
22. 括号生成
题解:回溯+剪枝
1、这里的减枝就是,记录当前的左括号的数量和右括号的数量,然后判断 if right > left: return
from typing import List
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
res = []
cur_str = ''
def dfs(cur_str, left, right):
if left == n and right == n:
res.append(cur_str)
return
if right > left: # 剪枝
return
if left < n:
dfs(cur_str + '(', left + 1, right)
if right < n:
dfs(cur_str + ')', left, right + 1)
dfs(cur_str, 0, 0)
return res
分割回文串(感觉很难)
题解:枚举子串结束位置
1、回溯切割 如 abc将有如下切割方法
[a b c], [a bc]
[ab c]
[abc ]
2、if t == t[::-1]: # 判断是否回文
from typing import List
class Solution:
def partition(self, s: str) -> List[List[str]]:
ans = []
path = []
n = len(s)
def dfs(i: int) -> None:
if i == n: # 这个结束判断很关键
ans.append(path.copy()) # 复制 path
return
for j in range(i, n): # 枚举子串的结束位置
t = s[i: j + 1]
if t == t[::-1]: # 判断是否回文
path.append(t)
dfs(j + 1)
path.pop() # 恢复现场
dfs(0)
return ans
图论
岛屿数量
dfs
1 遍历数组,遇见1就进行深度搜索
2、每一次深度搜索都把相连的1都修改为‘#’
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
sum = 0
def dfs(i, j):
if grid[i][j] != '1':
return
grid[i][j] = '#'
directions = [[1,0],[0,1],[-1,0],[0,-1]]
for ii,jj in directions:
i_new, j_new = i + ii,j+jj
if i_new in range(n) and j_new in range(m):
dfs(i_new, j_new)
n,m = len(grid), len(grid[0])
for i in range(n):
for j in range(m):
if grid[i][j] == '1':
dfs(i, j)
sum +=1
return sum
腐烂的橘子
Problem: 994. 腐烂的橘子
思路
使用层次遍历的方式去做
1、先将所有腐烂的橘子入队列
2、计算队列的长度len,出队len次,将相关的新鲜橘子赋值为2然后入队列
3、再次计算队列的长度len,出队len次,再次将相关的新鲜橘子赋值为2然后入队列
4、重复3直到队列为空。判断是否全部腐烂
Code
from collections import deque
import heapq
from typing import List
class Solution:
def orangesRotting(self, grid: List[List[int]]) -> int:
que= deque()
sum = 0 # 最终腐烂的橘子数量
orange_num = 0 #所有的橘子数量
mintues = 0 # 分钟数
direction = [[1,0], [0, 1], [-1,0],[0, -1]]
n, m = len(grid), len(grid[0])
visited = [[False for _ in range(m)] for _ in range(n)]
for i in range(0, n):
for j in range(0, m):
if grid[i][j] == 2:
que.append((i, j))
visited[i][j] = True
if grid[i][j] != 0:
orange_num += 1
while que:
length = len(que)
sum += length
mintues += 1
for _ in range(length):
curi, curj = que.popleft()
for i, j in direction:
curi_new, curi_new = curi + i, curj+j
if curi_new in range(0, n) and curi_new in range(0, m) and grid[curi_new][curi_new] == 1 and not visited[curi_new][curi_new]:
grid[curi + i][curj + j] = 2
que.append((curi+i, curj+j))
visited[curi+i][curj+j] = True
if orange_num == 0:
return 0
if sum != orange_num:
return -1
return mintues-1
if __name__ == '__main__':
grid = [[2,1,1],[1,1,0],[0,1,1]]
Solution().orangesRotting(grid)
课程表
拓扑排序
1、ru_map = {item:0 for item in range(numCourses) } # 字典推导,这里初始化值为0 # 优化点 du = [0]*numCourses #存放每个结点的入读
2、ddd = [[] for _ in range(numCourses)] #存放每个结点的子结点
3、result = [] # 进入过队列的所有点
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
ru_map = {item:0 for item in range(numCourses) } # 这里初始化值为0 # 优化点 du = [0]*numCourses #存放每个结点的入读
ddd = [[] for _ in range(numCourses)] #存放每个结点的子结点
result = [] # 进入过队列的所有点
for item in prerequisites:
n1, n2 = item
ru_map[n1] += 1
ddd[n2].append(n1)
que = deque([key for key,val in ru_map.items() if val == 0])
while que:
cur = que.popleft()
result.append(cur)
for item in ddd[cur]:
ru_map[item] -= 1
if ru_map[item] == 0:
que.append(item)
return len(result) == numCourses
作者:YingL
链接:https://leetcode.cn/problems/course-schedule/solutions/2745954/207-ke-cheng-biao-by-user5776-6nxn/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
前缀树
插入过程:
遍历所有的字符,如果已经在树上就获取该节点的儿子节点,如果不在就创建一个新的节点。
注: self.son = defaultdict(Node) 这行代码有两个作用。1、新增节点时会创建一个新的node 2 代表每个节点可能有多个子节点且关键字不一样
查询过程:
遍历所有的字符,如果已经在树上就获取该节点的儿子节点,继续找,如果找不到就报false,如果全找到了但不是最终节点,也会报错
startwith过程:
和查询过程一样,如果全找到了但不是最终节点,不会报错
作者:YingL
链接:https://leetcode.cn/problems/implement-trie-prefix-tree/solutions/2754175/208-shi-xian-trie-qian-zhui-shu-by-user5-smyf/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Node:
__slots__ = 'son','isend'
def __init__(self):
self.son = defaultdict(Node) # 子节点。每个节点可能有多个子节点且关键字不一样
self.isend = False # 标记该节点是否为尾节点
class Trie:
def __init__(self):
self.root = Node()
def insert(self, word: str) -> None:
cur = self.root
for char in word:
cur = cur.son[char] # 新增节点时会创建一个新的node
cur.isend = True # 将最后一个节点的isend赋值为true
def search(self, word: str) -> bool:
cur = self.root
for char in word:
if char not in cur.son:
return False
cur = cur.son[char]
if cur.isend:
return True
else:
return False
def startsWith(self, prefix: str) -> bool:
cur = self.root
for char in prefix:
if char not in cur.son:
return False
cur = cur.son[char]
return True
作者:YingL
链接:https://leetcode.cn/problems/implement-trie-prefix-tree/solutions/2754175/208-shi-xian-trie-qian-zhui-shu-by-user5-smyf/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
多维动态规划
62. 不同路径
法一: 多维动态规划
每一个格子的位数等于其左边和上边的位数之和。
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp = [[1]*m for _ in range(n)]
for i in range(1,n):
for j in range(1,m):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[-1][-1]
作者:小天才才
链接:https://leetcode.cn/problems/unique-paths/solutions/2705807/62-bu-tong-lu-jing-by-xtcc-2doc/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
最小路径和
题解:
多维动态规划
1、dp[i][j]代表到当前格点所走的最小值
2、dp[i][j]的值由上节点或者左节点过来
import copy
from typing import List
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
n, m = len(grid), len(grid[0])
dp = copy.deepcopy(grid)
# 初始化边缘
for j in range(1, m):
dp[0][j] = dp[0][j] + dp[0][j-1]
# 初始化边缘
for i in range(1, n):
dp[i][0] = dp[i][0] + dp[i-1][0]
# 每一个格点代表到当前格点所走的最小值
for i in range(1, n):
for j in range(1, m):
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + dp[i][j]
return dp[-1][-1]
最长回文字串(中心扩展)
题解:
中心扩展法
class Solution:
def expandAroundCenter(self, s, left, right):
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return left + 1, right - 1
def longestPalindrome(self, s: str) -> str:
start, end = 0, 0
for i in range(len(s)):
left1, right1 = self.expandAroundCenter(s, i, i)
left2, right2 = self.expandAroundCenter(s, i, i + 1)
# 取最长
if right1 - left1 > end - start:
start, end = left1, right1
if right2 - left2 > end - start:
start, end = left2, right2
return s[start: end + 1]
最长公共子序列[(n + 1) * (m+1)的二维数组 ]
题解:

dp 为 (n + 1) * (m+1)的二维数组
1、dp[i][j] 表示 text1[0:i]和 text2[0:j]的最长公共子序列的长度。
2、状态转移方程
dp[i][j] = dp[i - 1][j - 1] + 1 当 text1[i - 1] == text2[j - 1]:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) 当 text1[i - 1] != text2[j - 1]:
3 边界均为0, 注这里的 dp 为 (n + 1) * (m+1)的二维数组
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
m, n = len(text1), len(text2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i - 1] == text2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
return dp[m][n]
作者:力扣官方题解
链接:https://leetcode.cn/problems/longest-common-subsequence/solutions/696763/zui-chang-gong-gong-zi-xu-lie-by-leetcod-y7u0/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
编辑距离[(n + 1) * (m+1)的二维数组 ]
题解:
计算每一步的最小步数,然后慢慢转移
多维dp
1、dp[i][j] 代表 word1 到 i 位置转换成 word2 到 j 位置需要最少步数(word1[0:i] 和 word2[0:j])
2、 状态转移
当 word1[i] == word2[j],dp[i][j] = dp[i-1][j-1];(无需任何操作)
当 word1[i] != word2[j],dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1 (替换,删除,插入任选一种)

class Solution:
def minDistance(self, word1: str, word2: str) -> int:
n1 = len(word1)
n2 = len(word2)
dp = [[0] * (n2 + 1) for _ in range(n1 + 1)]
# 第一行
for j in range(1, n2 + 1):
dp[0][j] = dp[0][j-1] + 1
# 第一列
for i in range(1, n1 + 1):
dp[i][0] = dp[i-1][0] + 1
for i in range(1, n1 + 1):
for j in range(1, n2 + 1):
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1] ) + 1
#print(dp)
return dp[-1][-1]
作者:powcai
链接:https://leetcode.cn/problems/edit-distance/solutions/6455/zi-di-xiang-shang-he-zi-ding-xiang-xia-by-powcai-3/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
技巧
136. 只出现一次的数字
方法1:位预算
异或运算:相同为0,不同为1,且满足交换率
class Solution:
def singleNumber(self, nums: List[int]) -> List[int]:
x = 0
for num in nums: # 1. 遍历 nums 执行异或运算
x ^= num
return x; # 2. 返回出现一次的数字 x
作者:Krahets
链接:https://leetcode.cn/problems/single-number/solutions/2361995/136-zhi-chu-xian-yi-ci-de-shu-zi-wei-yun-iyd0/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
169. 多数元素
取中位数即可
class Solution:
def majorityElement(self, nums: List[int]) -> int:
nums.sort()
return nums[(len(nums)//2)]
颜色分类
单指针一次遍历
遇到0删掉放最左边,指针+1
遇到1不操作,指针+1
遇到2删掉,放最右边,指针不加,因为删了一位。
一开始在列表加了一个3,遇到3代表可以停止了,后面都是2。
作者:-刚好中意♥
链接:https://leetcode.cn/problems/sort-colors/solutions/1732931/by-lx666-87rz4/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
https://leetcode.cn/problems/sort-colors/solutions/1732931/by-lx666-87rz4
from typing import List
class Solution:
def sortColors(self, nums: List[int]) -> None:
# 使用Counter来计数
color_count = Counter(nums)
# 排序颜色,这里我们假设只有三种颜色,0, 1, 2
# 实际情况中,你可能需要根据具体情况调整排序逻辑
nums2 = []
for color in sorted(color_count.keys()):
# 对于每种颜色,按照计数重复写入数组
nums2.extend([color] * color_count[color])
nums[:] = nums2
作者:-刚好中意♥
链接:https://leetcode.cn/problems/sort-colors/solutions/1732931/by-lx666-87rz4/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
下一个排列
题解:
以排列 [4,5,2,6,3,1]为例:
1、当我们完成交换后排列变为 [4,5,3,6,2,1]
2、重排「较小数」右边的序列6 2 1 ,序列变为 [4,5,3,1,2,6]
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
i = len(nums) - 2
// 从后向前找到凸点
while i >= 0 and nums[i] >= nums[i + 1]:
i -= 1
if i >= 0:
// 找到升序序列中最后一个小于降序序列中的值 2 < 3
j = len(nums) - 1
while j >= 0 and nums[i] >= nums[j]:
j -= 1
// 交换这两个值
nums[i], nums[j] = nums[j], nums[i]
// 反转后面的序列即可
left, right = i + 1, len(nums) - 1
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
作者:力扣官方题解
链接:https://leetcode.cn/problems/next-permutation/solutions/479151/xia-yi-ge-pai-lie-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
寻找重复数
题解:
1、从小到大遍历i,如果没有重复,可以将数据通过交换的方式排列成123
2、如何有重复,会发生循环交换的问题。且此时i>num[i]
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
i = 0
# 如果没有重复,可以将数据通过交换的方式排列成123
while i < len(nums):
if nums[i] == i:
i += 1
continue
if nums[nums[i]] == nums[i]: return nums[i] # 此时i>num[i]
nums[nums[i]], nums[i] = nums[i], nums[nums[i]] # 将i处的值换到它对应idx = num[i]的地方去,一直到i == num[i]为止
return -1

浙公网安备 33010602011771号