数据结构
-
数组
- 很简单,也很常用,一般结合其他知识点考察
题目: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
经典的思路转变,从后往前遍历
代码模板:
# 法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
如果种类固定,使用数组也可以实现哈希表类似的查重功能
-
字符串
- 在python中,字符串是不可变
代码模板:
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种方式,都要熟悉
代码模板:
# 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
-
栈和队列
- 比较基本,无例题
-
树
- Morris遍历(熟悉Morris序,其中后序需要注意是第二次回到自己的时候,逆序打印左子树右边界)
- 一题多解,递归和迭代2个版本,dfs和bfs
- 题目就不放了
算法
-
二分
- 排序的,或者间接排序的
- 注意边界条件,因为每次循环只触发一个条件,所以 i i i或者 j j j只能移动一步,循环条件是 i ≤ j i\le j i≤j
训练计划
题目: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
代码模板:
# 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<j i<j的条件判断了,并且每次只有一个指针移动。
**为什么会有这样的区别呢?**事实上是因为二分的区间条件不同,判断数字是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
代码模板:
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]
代码模板:
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
代码模板:
# 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
因为删除当前节点需要其上一个节点,所以添加哑节点
-
滑动窗口
- 感觉和双指针差不多,考虑的范围在窗口内
代码模板:
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
- 从简单的开始
- 可以类比二叉树的遍历方式。bfs使用队列,将一个满足条件的点对应的其他所有满足条件的点加入队列,从左侧弹出最早的节点;dfs使用栈,有时候会借用字典防止重复遍历,当前步不满足的情况就会回溯到上一步
题目: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》的题:
- 剑指 Offer 27. 二叉树的镜像
- 剑指 Offer 28. 对称的二叉树
- 以下是一些必须使用bfs的例子
题目: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长度也行
代码模板:
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
-
动态规划
- 二维dp
- 范围上考虑
- 存在枚举
代码模板:
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]
- 一维dp
- 无后效性
题目: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])
- 伪二维dp
- 主要以买股票为主,即另一维的几个状态都会用到,并且是状态数是常量
代码模板:
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]
代码模板:
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次
-
位运算
- 这里就是要多记一些
- 由
n
&
(
n
−
1
)
n \& (n-1)
n&(n−1)衍生,得到的结果是将最低位的1变为0
- 结果为0,说明之前只有一个1,即为2的幂
- 记录可以循环几次,则原本的二进制有几个1
- 由 n & 1 n\&1 n&1可以判断奇偶
- 判断是不是交替1,可以用 n ⊕ ( n > > 1 ) n \oplus (n >> 1) n⊕(n>>1)
- 异或的性质
- 0 ⊕ a = a 0 \oplus a = a 0⊕a=a
- a ⊕ a = 0 a \oplus a = 0 a⊕a=0
- 交换律