数组
题目:217. 存在重复元素
代码模板:
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
# 遍历
dct = set()
for n in nums:
if n not in dct:
dct.add(n)
else:
return True
return False
题目:1. 两数之和
代码模板:
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
dct = {}
for i, v in enumerate(nums):
if v in dct:
return [dct[v], i]
else:
dct[target-v] = i
力扣第一题,很经典了
题目:88. 合并两个有序数组
代码模板:
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
# 从后往前遍历,双指针
k = len(nums1)-1
m -= 1
n -= 1
while k > -1:
if n > -1 and m > -1:
if nums1[m] < nums2[n]:
nums1[k] = nums2[n]
n -= 1
else:
nums1[k] = nums1[m]
m -= 1
elif n > -1:
nums1[k] = nums2[n]
n -= 1
# 如果 n== -1,可以直接return
else:
return
k -= 1
经典的思路转变,从后往前遍历
题目:350. 两个数组的交集 II
代码模板:
# 法1
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 不借助Counter
nums1.sort()
nums2.sort()
res = []
n1, n2 = len(nums1), len(nums2)
i = j = 0
while i < n1 and j < n2:
if nums1[i] < nums2[j]:
i += 1
elif nums1[i] > nums2[j]:
j += 1
else:
res.append(nums1[i])
i += 1
j += 1
return res
# 法2
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 借助Counter,在二者长度差异大的时候效率高
if len(nums1) > len(nums2):
return self.intersect(nums2, nums1)
res = []
ctr = Counter(nums1)
for n in nums2:
if n in ctr:
ctr[n] -= 1
res.append(n)
if ctr[n] == 0:
ctr.pop(n)
return res
这道题还是使用哈希表查询效率更好,尤其是nums1和nums2长度差异大的时候
题目:36. 有效的数独
代码模板:
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
row = [[0] * 9 for _ in range(9)]
col = [[0] * 9 for _ in range(9)]
mat = [[[0] * 9 for _ in range(3)] for _ in range(3)]
# mat最先定义的在最后索引
for i in range(9):
for j in range(9):
c = board[i][j]
if c != '.':
idx = int(c) - 1
row[i][idx] += 1
col[idx][j] += 1
mat[i//3][j//3][idx] += 1
if row[i][idx]>1 or col[idx][j]>1 or mat[i//3][j//3][idx]>1:
return False
return True
如果种类固定,使用数组也可以实现哈希表类似的查重功能
字符串
题目:387. 字符串中的第一个唯一字符
代码模板:
class Solution:
def firstUniqChar(self, s: str) -> int:
# 2次遍历,查询字典中的次数
ctr = Counter(s)
for idx, i in enumerate(s):
if ctr[i] == 1:
return idx
return -1
这道题方法很多,重点理解一下队列的方法,不能局限于一种思路
链表
题目:206. 反转链表(经典题)
代码模板:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head or not head.next:
return head
newhead = self.reverseList(head.next)
head.next.next = head
head.next = None
return newhead
迭代和递归2种方式,都要熟悉
题目:83. 删除排序链表中的重复元素
代码模板:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
# 递归,原地算法
if not head or not head.next:
return head
if head.val == head.next.val:
head.next = head.next.next
self.deleteDuplicates(head)
return head
else:
self.deleteDuplicates(head.next)
return head
题目:203. 移除链表元素
代码模板:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
# 递归
if head is None:
return None
elif head.val == val:
head.next = self.removeElements(head.next, val)
return head.next
else:
head.next = self.removeElements(head.next, val)
return head
栈和队列
树
二分
题目:704. 二分查找
代码模板:
class Solution:
def search(self, nums: List[int], target: int) -> int:
i, j = 0, len(nums)-1
while i <= j:
# 取靠左的,即 1 2 3 4 取2,1 2 3 取2
# 防止计算时溢出
mid = i + ((j-i) >> 1)
if nums[mid] == target:
return mid
elif nums[mid] < target:
i = mid+1
else:
j = mid-1
return -1
题目:278. 第一个错误的版本
代码模板:
# The isBadVersion API is already defined for you.
# @param version, an integer
# @return an integer
# def isBadVersion(version):
class Solution:
def firstBadVersion(self, n):
"""
:type n: int
:rtype: int
"""
# 特殊之处在于只有2种状态,即当跳出循环时:
# 如果是i未出错,i += 1,返回i;如果j未出错,一定是上一个版本出错,也返回i
i, j = 1, n
while i <= j:
mid = i + ((j-i) >> 1)
if isBadVersion(mid):
j = mid-1
else:
i = mid+1
return i
这道题也可以使用 i < j i
**为什么会有这样的区别呢?**事实上是因为二分的区间条件不同,判断数字是3种情况,最终范围长度大于0时即是有效范围;但是判断正误只有2种情况,只有范围长度为1时,才是最终的答案,这里需要好好思考,将问题泛化到各种情况下。
题目:35. 搜索插入位置
代码模板:
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
# 3种情况,对比普通二分将返回-1变为返回合适的索引
# 考虑找不到的情况,此时i=j,并且有一个变化使while条件不成立,大了j减1,小了i+1,所以返回i
# 插入位置i表示插入到i索引之前
i, j = 0, len(nums)-1
while i <= j:
mid = i + ((j-i) >> 1)
if nums[mid] == target:
return mid
elif nums[mid] < target:
i = mid+1
else:
j = mid-1
return i
题目:34. 在排序数组中查找元素的第一个和最后一个位置(重点)
代码模板:
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
# i和j就是区间2端点
# 3种情况
i, j = 0, len(nums)-1
while i <= j:
mid = i + ((j-i) >> 1)
if nums[mid] < target:
i = mid + 1
elif nums[mid] > target:
j = mid - 1
# mid到j都等于target
elif nums[i] != target:
i += 1
# i到mid都等于target
elif nums[j] != target:
j -= 1
else:
return [i, j]
return [-1, -1]
这道题就比较有趣了,我的实现并不是完全的二分查找,但是在一定程度上使用二分查找优化了整体的效率,有时间可以学习一下官方的题解
题目:374. 猜数字大小
代码模板:
# The guess API is already defined for you.
# @param num, your guess
# @return -1 if my number is lower, 1 if my number is higher, otherwise return 0
# def guess(num: int) -> int:
class Solution:
def guessNumber(self, n: int) -> int:
i, j = 1, n
while i <= j:
mid = i + ((j-i) >> 1)
if guess(mid) == 0:
return mid
elif guess(mid) == -1:
j = mid-1
else:
i = mid+1
# 返回0表示pick不在范围内
return 0
题目:658. 找到 K 个最接近的元素(考察coding能力)
代码模板:
class Solution:
def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
# 二分找到恰好大于x一点点的位置,等价于之前的查找插入位置
i, j = 0, len(arr)-1
idx = -100
while i <= j:
mid = i + ((j-i) >> 1)
if arr[mid] == x:
idx = mid
break
elif arr[mid] < x:
i = mid+1
else:
j = mid-1
if idx == -100:
idx = i
# print(idx)
# 缩小区间
if idx == 0:
return arr[:k]
elif idx == len(arr):
return arr[-k:]
else:
i, j = max(idx-k, 0), min(idx+k, len(arr)-1)
while j-i >= k:
if arr[j] - x > x - arr[i]:
j -= 1
elif arr[j] - x < x - arr[i]:
i += 1
else:
j -= 1
return arr[i:j+1]
双指针
题目:977. 有序数组的平方
代码模板:
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
# 双指针,绝对值最大的在2边
i, j = 0, len(nums)-1
res = [0] * len(nums)
k = j
while k > -1:
if nums[i]**2 < nums[j]**2:
res[k] = nums[j]**2
j -= 1
else:
res[k] = nums[i]**2
i += 1
k -= 1
return res
题目:189. 轮转数组(重点题)
代码模板:
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# 法2,使用额外空间
n = len(nums)
idx = n - k%n
res = [0] * n
i = j = 0
while i < n:
res[i] = nums[(i+idx) % n]
i += 1
while j < n:
nums[j] = res[j]
j += 1
题目:283. 移动零(思路很清晰)
代码模板:
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# 同向指针,不同条件判断是否右移
i = j = 0
while j < len(nums):
if nums[j] != 0:
# 左侧换完之后保证都是非0数
nums[i], nums[j] = nums[j], nums[i]
i += 1
j += 1
题目:167. 两数之和 II - 输入有序数组
代码模板:
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
# 首尾双指针,常量空间
i, j = 0, len(numbers)-1
while i < j:
if numbers[i] + numbers[j] == target:
return [i+1, j+1]
elif numbers[i] + numbers[j] > target:
j -= 1
else:
i += 1
return [-1, -1]
题目:557. 反转字符串中的单词 III
代码模板:
class Solution:
def reverseWords(self, s: str) -> str:
# 将s转变为列表,原地修改
s = list(s)
i = j = 0
while j < len(s):
while j < len(s) and s[j] != ' ':
j += 1
# 反转s[i:j+1]部分
k = j
j -= 1
while i < j:
s[i], s[j] = s[j], s[i]
i += 1
j -= 1
j = k+1
i = k+1
return ''.join(s)
伪原地算法
题目:876. 链表的中间结点
代码模板:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head: ListNode) -> ListNode:
# 快慢指针
low, fast = head, head
while fast and fast.next:
low = low.next
fast = fast.next.next
return low
题目:19. 删除链表的倒数第 N 个结点
代码模板:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
# 伪头,n在要求范围内
ori = ListNode
ori.next = head
slow = fast = head
while n > 0:
fast = fast.next
n -= 1
slow = ori
while fast:
slow = slow.next
fast = fast.next
slow.next = slow.next.next
return ori.next
因为删除当前节点需要其上一个节点,所以添加哑节点
滑动窗口
题目:3. 无重复字符的最长子串
代码模板:
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if not s:
return 0
dct = set()
i = j = maxlen = 0
while j < len(s):
if s[j] not in dct:
dct.add(s[j])
j += 1
else:
dct.remove(s[i])
i += 1
maxlen = max(maxlen, j-i)
return maxlen
代码效率不高,待优化
题目:567. 字符串的排列(重点题)
代码模板:
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
# 滑动窗口,对2个ctr的比较可以优化为对差值的比较
# 这里有正有负,使用ctr不方便,因为字母是有限的,可以使用数组
if len(s1) > len(s2):
return False
diffctr = [0] * 26
for i in range(len(s2)):
# 每次都加
if i < len(s1):
diffctr[ord(s2[i])-ord('a')] -= 1
diffctr[ord(s1[i])-ord('a')] += 1
else:
if all(k == 0 for k in diffctr):
return True
diffctr[ord(s2[i])-ord('a')] -= 1
diffctr[ord(s2[i-len(s1)])-ord('a')] += 1
return all(k == 0 for k in diffctr)
提交错了好多次,思绪已经很乱了;这道题可以将比较相同转变为比较差值
dfs和bfs
题目:733. 图像渲染
代码模板:
class Solution:
def floodFill(self, image: List[List[int]], sr: int, sc: int, newColor: int) -> List[List[int]]:
# 把所有连通值都更改
def dfs(r, c):
if -1 < r < m and -1 < c < n and (r, c) not in dct and image[r][c] == oldcolor:
image[r][c] = newColor
dct.add((r, c))
dfs(r-1, c)
dfs(r+1, c)
dfs(r, c+1)
dfs(r, c-1)
m, n = len(image), len(image[0])
oldcolor = image[sr][sc]
dct = set()
dfs(sr, sc)
return image
遇到更改原值的情况,就可以不使用哈希表避免重复
题目:200. 岛屿数量
代码模板:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(r, c):
if -1 < r < m and -1 < c < n and grid[r][c] == '1':
grid[r][c] = '2'
dfs(r-1, c)
dfs(r+1, c)
dfs(r, c-1)
dfs(r, c+1)
m, n = len(grid), len(grid[0])
cnt = 0
for r in range(m):
for c in range(n):
if grid[r][c] == '1':
dfs(r, c)
cnt += 1
return cnt
题目:695. 岛屿的最大面积
代码模板:
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
def dfs(r, c):
nonlocal cnt
if -1 < r < m and -1 < c < n and grid[r][c] == 1:
grid[r][c] = 2
cnt += 1
dfs(r-1, c)
dfs(r+1, c)
dfs(r, c-1)
dfs(r, c+1)
m, n = len(grid), len(grid[0])
maxcnt = 0
for r in range(m):
for c in range(n):
cnt = 0
dfs(r, c)
maxcnt = max(maxcnt, cnt)
return maxcnt
题目:463. 岛屿的周长(简单题,但是做的很乱)
代码模板:
class Solution:
def islandPerimeter(self, grid: List[List[int]]) -> int:
def dfs(r, c):
nonlocal cnt
if r < 0 or r >= m or c < 0 or c >= n or grid[r][c] == 0:
cnt += 1
elif grid[r][c] == 1:
grid[r][c] = 2
for x, y in ((r-1, c), (r+1, c), (r, c+1), (r, c-1)):
dfs(x, y)
m, n = len(grid), len(grid[0])
cnt = 0
for r in range(m):
for c in range(n):
if grid[r][c] == 1:
dfs(r, c)
return cnt
题目:617. 合并二叉树(重要题)
代码模板:
# 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 mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
if not root1:
return root2
if not root2:
return root1
node = TreeNode(root1.val + root2.val)
node.left = self.mergeTrees(root1.left, root2.left)
node.right = self.mergeTrees(root1.right, root2.right)
return node
虽然是简单题,但是对递归还是梳理不够,可以通过这道题反思以下《剑指offer》的题:
题目:542. 01 矩阵(重要题)
代码模板:
class Solution:
def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]:
m, n = len(matrix), len(matrix[0])
res = [[0] * n for _ in range(m)]
zreopos = [(i, j) for i in range(m) for j in range(n) if matrix[i][j] == 0]
q = deque(zreopos)
dct = set(zreopos)
# bfs
while q:
r, c = q.popleft()
for x, y in [(r-1, c), (r+1, c), (r, c-1), (r, c+1)]:
if -1 < x < m and -1 < y < n and (x, y) not in dct:
res[x][y] = res[r][c] + 1
dct.add((x, y))
q.append((x, y))
return res
题目:994. 腐烂的橘子
代码模板:
class Solution:
def orangesRotting(self, grid: List[List[int]]) -> int:
# 和01矩阵差不多,从所有腐烂的橘子开始一起扩散
# 最后一个被扩散到需要的时间也就是其到最近腐烂句子的距离
m, n = len(grid), len(grid[0])
dist = [[0] * n for _ in range(m)]
bad = [(i, j) for i in range(m) for j in range(n) if grid[i][j] == 2]
good = [(i, j) for i in range(m) for j in range(n) if grid[i][j] == 1]
# 没有新鲜的
if not good:
return 0
# 如果全部新鲜
if not bad:
return -1
q = deque(bad)
dct = set(bad)
while q:
r, c = q.popleft()
for x, y in [(r-1, c), (r+1, c), (r, c-1), (r, c+1)]:
# 这里增加条件,必须有橘子才能腐烂
if -1 < x < m and -1 < y < n and (x, y) not in dct and grid[x][y] == 1:
dist[x][y] = dist[r][c] + 1
grid[x][y] = 2
dct.add((x, y))
q.append((x, y))
# 还有新鲜的
good = [(i, j) for i in range(m) for j in range(n) if grid[i][j] == 1]
if good:
return -1
return max(max(r) for r in dist)
这2道题很有代表性,都是多源广度优先搜索的题目,和普通的bfs稍有差别
要利用左侧pop的元素信息,来更新这一层新加入节点的信息
递归和回溯
题目:77. 组合(重点)
代码模板:
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
# 排列使用交换法,组合使用dp法
# 1-n中取m,分解为2-n中取m-1(要1)和2-n中取m(不要1)
def huisu(i, size):
path.append(i)
if size == k:
res.append(list(path))
# 无后效性
for j in range(i+1, n+1):
huisu(j, size+1)
path.pop()
path = []
res = []
for j in range(1, n-k+2):
huisu(j, 1)
return res
题目:46. 全排列(重点)
代码模板:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
# 全排列,交换法;或者是用字典避免重复
def huisu(k):
if k == len(nums):
res.append(path[:])
return
for j in range(len(nums)):
# 下一次遇到1的时候不添加1去dfs,否则添加进字典,与路径
if nums[j] in dct:
continue
dct.add(nums[j])
path.append(nums[j])
huisu(k+1)
path.pop()
dct.remove(nums[j])
dct = set()
res = []
path = []
huisu(0) # 当前长度为0
return res
# 也可以不加入参数k,直接判断path长度是否为nums长度也行
题目:784. 字母大小写全排列
代码模板:
class Solution:
def letterCasePermutation(self, s: str) -> List[str]:
# 相当于dfs时有2种情况(大写、小写)
def huisu(k):
if k == len(s):
res.append(''.join(path))
return
# 是数字也要回溯,这个的意义是什么
if s[k].isdigit():
path.append(s[k])
huisu(k+1)
path.pop()
else:
if s[k].islower():
path.append(s[k])
huisu(k+1)
path.pop()
path.append(s[k].upper())
huisu(k+1)
path.pop()
else:
path.append(s[k])
huisu(k+1)
path.pop()
path.append(s[k].lower())
huisu(k+1)
path.pop()
path = []
res = []
huisu(0)
return res
动态规划
题目:375. 猜数字大小 II
代码模板:
class Solution:
def getMoneyAmount(self, n: int) -> int:
# dp下标范围0~n,为避免歧义,多加一个没有意义的0列
dp = [[0] * (n+1) for _ in range(n+1)]
# 初始化,i,j相邻时,取小的
for i in range(2, n+1):
dp[i-1][i] = i-1
# 过程
for i in range(n-2, 0, -1):
for j in range(i+2, n+1):
mini = i+1 + max(dp[i][i], dp[i+2][j])
for k in range(i+1, j):
mini = min(mini, k+max(dp[i][k-1], dp[k+1][j]))
dp[i][j] = mini
# print(dp)
return dp[1][n]
题目:53. 最大子数组和(重点题)
代码模板:
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# 子数组和小于0,重新开始
# dp[i]表示以i结尾的连续子数组最大和
dp = [0] * len(nums)
summ = 0
for i in range(len(nums)):
summ += nums[i]
dp[i] = summ
if summ < 0:
summ = 0
return max(dp)
官方的答案更有动态规划的思想,即求: d p [ i ] = m a x ( d p [ i − 1 ] + n u m s [ i ] , n u m s [ i ] ) dp[i]=max(dp[i−1]+nums[i], nums[i]) dp[i]=max(dp[i−1]+nums[i],nums[i])
题目:121. 买卖股票的最佳时机
代码模板:
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# 使用动态规划解题
# 伪二维数组,其中一个维度是“持有股票”或者“不持有股票”,分别是0和1
dp = [[0] * 2 for _ in range(len(prices))]
dp[0][0], dp[0][1] = prices[0], 0
for i in range(1, len(prices)):
# 当天如果持有,则选择最低的时候买入
dp[i][0] = min(dp[i-1][0], prices[i])
# 当天如果不持有,则选择最高的时候卖出
dp[i][1] = max(dp[i-1][1], prices[i]-dp[i-1][0])
return dp[len(prices)-1][1]
题目:122. 买卖股票的最佳时机 II
代码模板:
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# 使用动态规划解题
# 伪二维数组,其中一个维度是“持有股票”或者“不持有股票”,分别是0和1
dp = [[0] * 2 for _ in range(len(prices))]
dp[0][0], dp[0][1] = -prices[0], 0
for i in range(1, len(prices)):
# 持有的状态,如果之前持有,则为之前的状态;之前不持有,可以持有,选最大
dp[i][0] = max(dp[i-1][0], dp[i-1][1]-prices[i])
# 不持有的状态,可以将之前持有的卖出去,或者继续不持有
dp[i][1] = max(dp[i-1][0]+prices[i], dp[i-1][1])
return dp[len(prices)-1][1]
这2道题的不同需要好好思考一下
还有这2者的进阶版,既不是只交易一次,也不是可重复交易,而是交易2次
位运算