算法能力提升计划 Algorithm Capability Enhancement Program
写在前面:
督促自己学习,顺便记录题解
更新记录
- 5/19 更新211. 添加与搜索单词 - 数据结构设计、52. N 皇后 II、918. 环形子数组的最大和、97. 交错字符串(昨天尸体不舒服,所以没做,哈哈哈给自己找了个借口,不过今天补上)
- 5/17 更新221. 最大正方形、289. 生命游戏
- 5/16 更新230. 二叉搜索树中第K小的元素、530. 二叉搜索树的最小绝对差
- 5/15 更新98. 验证二叉搜索树、127. 单词接龙
- 5/14 更新19. 删除链表的倒数第 N 个结点、25. K 个一组翻转链表
- 5/13 更新210. 课程表 II、207. 课程表
- 5/11 更新433. 最小基因变化、909. 蛇梯棋(感觉有点懈怠了,现在刷题有种例行公事的感觉,没有当初的那种激情了)
- 5/10 更新399. 除法求值、133. 克隆图
- 5/9 更新130. 被围绕的区域、200. 岛屿数量(重温一下岛屿数量,经典dfs)
- 5/8 更新103. 二叉树的锯齿形层序遍历、102. 二叉树的层序遍历、637. 二叉树的层平均值、199. 二叉树的右视图(今天全是层序遍历)
- 5/7 更新124. 二叉树中的最大路径和、173. 二叉搜索树迭代器(二叉树的部分终于完结了★,°:.☆( ̄▽ ̄)/$:.°★ 。)
- 5/6 更新222. 完全二叉树的节点个数、236. 二叉树的最近公共祖先(今天这个第一个题有点难度,理解了半天,coze也不是完全靠谱呀)
- 5/5 更新129. 求根节点到叶节点数字之和、112. 路径总和(这两道题的思路基本一样,所以做一个就能做另一个)
- 5/4 更新114. 二叉树展开为链表、117. 填充每个节点的下一个右侧节点指针 II(五一给自己放了个假~看来复习的事情要提上日程了,看了最开始做的题,已经不知道怎么做了……)
- 4/30 更新105. 从前序与中序遍历序列构造二叉树、106. 从中序与后序遍历序列构造二叉树
- 4/29 更新101. 对称二叉树、226. 翻转二叉树(对二叉树的理解还是不够,还是得好好复习)
- 4/28 更新104. 二叉树的最大深度、100. 相同的树(今天开始做一些二叉树的题目,适时可以看看之前的数据结构PPT)
- 4/27 更新86. 分隔链表、61. 旋转链表(今天感觉有点懈怠了,请继续加油!人在做,天在看)
- 4/26 更新71. 简化路径、155. 最小栈
- 4/25 更新452. 用最少数量的箭引爆气球、57. 插入区间
- 4/24 更新🌟76. 最小覆盖子串、🌟30. 串联所有单词的子串
- 4/23 更新300. 最长递增子序列、198. 打家劫舍、🌟373. 查找和最小的 K 对数字、215. 数组中的第K个最大元素(堆和动态规划)
- 4/22 更新162. 寻找峰值、74. 搜索二维矩阵
- 4/21 更新148. 排序链表、108. 将有序数组转换为二叉搜索树
- 4/20 更新120. 三角形最小路径和、64. 最小路径和
- 4/19 更新191. 位1的个数、201. 数字范围按位与(位运算)、35. 搜索插入位置、70. 爬楼梯
- 4/18 更新136. 只出现一次的数字、137. 只出现一次的数字 II
- 4/17 更新190. 颠倒二进制位、67. 二进制求和
- 4/16 更新22. 括号生成、17. 电话号码的字母组合、77. 组合、79. 单词搜索、39. 组合总和、46. 全排列(回溯day~)
- 4/15 更新92. 反转链表 II、82. 删除排序链表中的重复元素 II(有点没有带脑子)
- 4/14 更新21. 合并两个有序链表、141. 环形链表(生病了leetcode也不能停!)
- 4/13 更新322. 零钱兑换、139. 单词拆分
- 4/12 更新228. 汇总区间、56. 合并区间
- 4/11 更新73. 矩阵置零、48. 旋转图像
- 4/10 更新290. 单词规律、205. 同构字符串、219. 存在重复元素 II(三个easy)
- 4/9 更新392. 判断子序列、167. 两数之和 II - 输入有序数组
- 4/8 更新149. 直线上最多的点数、172. 阶乘后的零、151. 反转字符串中的单词、380. O(1) 时间插入、删除和获取随机元素
- 4/7 更新274. H 指数、122. 买卖股票的最佳时机 II、121. 买卖股票的最佳时机
- 4/3 更新16. 最接近的三数之和
目录
(手动更新好累,暂时不更新了吧~)
211. 添加与搜索单词 - 数据结构设计
1 |
|
52. N 皇后 II
1 |
|
918. 环形子数组的最大和
1 |
|
97. 交错字符串
1 |
|
221. 最大正方形
1 |
|
289. 生命游戏
1 |
|
230. 二叉搜索树中第K小的元素
1 |
|
530. 二叉搜索树的最小绝对差
1 |
|
98. 验证二叉搜索树
1 |
|
127. 单词接龙
1 |
|
19. 删除链表的倒数第 N 个结点
1 |
|
25. K 个一组翻转链表
1 |
|
210. 课程表 II
1 |
|
207. 课程表
1 |
|
433. 最小基因变化
1 |
|
909. 蛇梯棋
1 |
|
399. 除法求值
1 |
|
133. 克隆图
1 |
|
130. 被围绕的区域
1 |
|
200. 岛屿数量
1 |
|
103. 二叉树的锯齿形层序遍历
1 |
|
102. 二叉树的层序遍历
1 |
|
637. 二叉树的层平均值
1 |
|
199. 二叉树的右视图
1 |
|
124. 二叉树中的最大路径和
1 |
|
173. 二叉搜索树迭代器
1 |
|
222. 完全二叉树的节点个数
- 优化版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25# 完全二叉树的节点个数
def countNodes(self, root: Optional[TreeNode]) -> int:
def countHeight(node):
height = 0
while node:
node = node.left
height += 1
return height
if not root:
return 0
# 分别求左右子树的高度
leftHeight = countHeight(root.left)
rightHeight = countHeight(root.right)
# 判断左右子树的高度关系
if leftHeight == rightHeight:
# 如果左右子树高度相等
# 说明现在左子树是满二叉树,需要递归判断右子树的情况
return (1 << leftHeight) - 1 + self.countNodes(root.right) + 1
else:
# 说明现在右子树是满的,需要递归判断左子树
return (1 << rightHeight) - 1 + self.countNodes(root.left) + 1 - 普通版
1
2
3
4
5# 普通版
def countNodes(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
return self.countNodes(root.left) + self.countNodes(root.right) + 1
236. 二叉树的最近公共祖先
1 |
|
112. 路径总和
1 |
|
129. 求根节点到叶节点数字之和
1 |
|
114. 二叉树展开为链表
1 |
|
117. 填充每个节点的下一个右侧节点指针 II
1 |
|
105. 从前序与中序遍历序列构造二叉树
- 这两个题都利用了一个性质,左右子树的元素个数是一致的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25# 从前序与中序遍历序列构造二叉树
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
# 给定的输入如下:
# 前序遍历:[3, 9, 20, 15, 7]
# 中序遍历:[9, 3, 15, 20, 7]
# 首先,根据前序遍历,我们知道根节点是3。
# 对于中序遍历[9, 3, 15, 20, 7]:
# 根节点3之前的所有节点[9]构成左子树。
# 根节点3之后的所有节点[15, 20, 7]构成右子树。
# 接下来,我们就有了两个子问题:
# 左子树的前序遍历是[9],中序遍历是[9]。
# 右子树的前序遍历是[20, 15, 7],中序遍历是[15, 20, 7]。
# 确定根节点:在前序遍历中找到树的根节点(序列的第一个元素)。
# 划分树的左右子树:在中序遍历序列中找到根节点,这样就可以确定左子树和右子树的节点。
# 递归构造子树:对于根节点的左侧和右侧序列,重复以上过程,构造左子树和右子树。
if not preorder or not inorder:
return None
# 第一个一定是根节点
root = TreeNode(preorder[0])
mid = inorder.index(preorder[0])
root.left = self.buildTree(preorder[1:mid+1], inorder[:mid])
root.right = self.buildTree(preorder[mid+1:], inorder[mid+1:])
return root
106. 从中序与后序遍历序列构造二叉树
1 |
|
101. 对称二叉树
1 |
|
226. 翻转二叉树
1 |
|
100. 相同的树
1 |
|
104. 二叉树的最大深度
1 |
|
86. 分隔链表
1 |
|
61. 旋转链表
1 |
|
71. 简化路径
1 |
|
155. 最小栈
1 |
|
452. 用最少数量的箭引爆气球
1 |
|
57. 插入区间
1 |
|
🌟76. 最小覆盖子串
1 |
|
🌟30. 串联所有单词的子串
1 |
|
🌟373. 查找和最小的 K 对数字
- 使用堆
- 掌握的不是很牢,需要巩固
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21# 查找和最小的 K 对数字
def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]:
# 不会做,抄一下
if not nums1 or not nums2:
return []
minHeap = []
for i in range(min(k, len(nums1))):
heapq.heappush(minHeap, (nums1[i]+nums2[0], i, 0))
result = []
while k > 0 and minHeap:
sumVal, i, j = heapq.heappop(minHeap)
result.append([nums1[i], nums2[j]])
if j + 1 < len(nums2):
heapq.heappush(minHeap, (nums1[i]+nums2[j + 1], i, j+1))
k -= 1
return result
215. 数组中的第K个最大元素
1 |
|
300. 最长递增子序列
1 |
|
198. 打家劫舍
- 普通dp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 打家劫舍
def rob(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
elif len(nums) == 1:
return nums[0]
# 首先创建dp数组
dp = [0] * len(nums)
dp[0] = nums[0]
# 在计算 dp[1] 的值时,您应该考虑抢第一家和不抢第一家、
# 只抢第二家之间的选择,即 dp[1] 应该是 nums[0] 和 nums[1] 中的较大者,
# 而不是直接赋值 nums[1]
dp[1] = max(nums[1], nums[0])
for i in range(2, len(nums)):
dp[i] = max(dp[i-1], dp[i-2]+nums[i])
return dp[len(nums)-1] - 状态压缩dp
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 使用状态压缩进行优化
def rob(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
elif len(nums) == 1:
return nums[0]
# 创建两个状态
prev, cur = nums[0], max(nums[0], nums[1])
for i in range(2, len(nums)):
# 先保存好cur
temp = cur
cur = max(cur, nums[i] + prev)
prev = temp
return cur
74. 搜索二维矩阵
1 |
|
162. 寻找峰值
1 |
|
148. 排序链表
- 链表中点的寻找
- 实际上是对链表进行归并排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37# 排序链表
def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 使用快慢指针技术来找到链表的中点
# 利用他们的速度是二倍的关系
# 奇数:fast == end slow == mid
# 偶数:fast == end + 1 slow == mid
if not head or not head.next:
return head
slow, fast = head, head.next
while fast and fast.next:
fast = fast.next.next
slow = slow.next
# 切分
# 解耦链表
mid, slow.next = slow.next, None
# 递归的机型排序
left, right = self.sortList(head), self.sortList(mid)
return self.merge(left, right)
def merge(self, l1: ListNode, l2: ListNode) -> ListNode:
dummy = ListNode()
tail = dummy
while l1 and l2:
if l1.val < l2.val:
tail.next, l1 = l1, l1.next
else:
tail.next, l2 = l2, l2.next
tail = tail.next
tail.next = l1 if l1 else l2
return dummy.next
108. 将有序数组转换为二叉搜索树
- 递归程序的编写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# 将有序数组转换为二叉搜索树
def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
# 这个题目就是很经典的分治算法
# 由于是排好序的数组,所以可以直接二分,进行树的构建
# 先找mid值
if not nums:
return None
mid = len(nums) // 2
root = TreeNode(nums[mid])
root.left = self.sortedArrayToBST(nums=nums[0:mid])
# nums[mid+1:-1] 从索引 mid+1 处开始截取,但是最后一个元素(索引为 -1 的那个元素)不包括在内。
# 列表的切片操作是左闭右开的,因此 -1 表示停在倒数第一个元素之前。
root.right = self.sortedArrayToBST(nums=nums[mid+1:])
return root
64. 最小路径和
1 |
|
120. 三角形最小路径和
- 经典动态规划问题
1
2
3
4
5
6
7
8
9
10
11# 三角形最小路径和
# 二维动态规划
def minimumTotal(self, triangle: List[List[int]]) -> int:
# 自底向上更新triangle数组,这样能够节省空间
n = len(triangle)
# 注意:最后一行不需要更新,所以起始是n-1行
for row in range(n-2, -1, -1):
for col in range(len(triangle[row])):
triangle[row][col] += min(triangle[row+1]
[col], triangle[row+1][col+1])
return triangle[0][0]
70. 爬楼梯
1 |
|
35. 搜索插入位置
- 注意右边边界的初始索引应该是length - 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# 搜索插入位置
def searchInsert(self, nums: List[int], target: int) -> int:
if not nums:
return 0
left, right = 0, len(nums) - 1
mid = (left + right) >> 1
while left <= right:
if nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid - 1
elif nums[mid] == target:
return mid
mid = (left + right) >> 1
return left
191. 位1的个数
- 利用位检查或者位消除法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# 位检查
def hammingWeight(self, n: int) -> int:
# 每次右移一位
count = 0
while n:
# 这里是按位与,1 的前面部分都是0
count += n & 1
n = n >> 1
return count
# 位清除
def hammingWeight(self, n: int) -> int:
# 利用n & n-1去清除最低位的1
# 可以用这个方法来统计有多少个1(有多少1就要操作多少次)
count = 0
while n:
n = n & (n-1)
count += 1
return count
201. 数字范围按位与
- 这个题目的解题角度比较刁钻
- 例如两个数11100b和11011b两个数
- 他们有公共的前缀11,而一个连续的数值区间,一定会有后面的值为0的数,所以按位与之后的结果就是11000b
- 那么就变得简单了,只需要记录下向右移动的次数,和最后剩下的前缀,就可以恢复得到最后的结果,bingo~
1
2
3
4
5
6
7
8
9
10
11# 数字范围按位与
def rangeBitwiseAnd(self, left: int, right: int) -> int:
# 这个代码理解起来有点复杂
shift = 0
# 找到共同前缀(移动的部分,至少有一个位为0,所以可以直接忽略)
while left < right:
left >>= 1
right >>= 1
shift += 1
# 恢复结果(因为向右边移动了)
return left << shift
136. 只出现一次的数字
- 简单的方法:哈希表
1
2
3
4
5
6
7
8def singleNumber(self, nums: List[int]) -> int:
# 看上去可以用哈希表解决
numDict = dict()
for i in range(len(nums)):
numDict[nums[i]] = numDict.get(nums[i], 0) + 1
# 获取只出现了一次的数字
keys = [key for key, value in numDict.items() if value == 1]
return keys[0] - 高效的办法:异或运算的性质
1
2
3
4
5
6
7
8# 利用异或运算快速操作
def singleNumber(self, nums: List[int]) -> int:
# 利用一个数如果与自身异或的结果是0
# 0与任何数的异或结果都是那个数
res = 0
for num in nums:
res ^= num
return res
137. 只出现一次的数字 II
- 简单的方法:哈希表
- 高效的办法:32位统计每一位出现的次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# 只出现一次的数字 II
def singleNumber(self, nums: List[int]) -> int:
# 这个题目可以换一种方式来思考
# 首先转换成二进制的位
res = 0
for i in range(32):
count = 0
for num in nums:
# 检查第i位是否位1
# 1 << i表示将1左移i位,即在第i位上的值为1,其他位上的值为0。然后,我们用这个值和num做与操作,如果结果不为0,表明num在第i位上的值为1。
if num & (1 << i):
count += 1
# 对3取余
# 主要是为了正确设置结果的值
res |= (count % 3) << i
# 处理负数
# 实际上不是32位的,但是人为限定成了32位
if res >= 2**31:
res -= 2**32
return res - 超高效的办法:使用真值表求数字电路的表达式(饶了我吧)
190. 颠倒二进制位
- 一种低效的算法
1
2
3
4
5
6
7
8def reverseBits(self, n: int) -> int:
# 把数字转换成二进制字符串,去除0b
binN = bin(n)[2:].zfill(32)
# 翻转二进制字符串
# 切片操作的格式是 [start:stop:step]
reverseBinN = binN[::-1]
# 转换成整数
return int(reverseBinN, 2) - 一种高效的算法:分组位翻转算法(解释见[[分组位翻转算法]])
1
2
3
4
5
6
7def reverseBits(self, n: int) -> int:
n = (n >> 16) | (n << 16)
n = ((n & 0xff00ff00) >> 8) | ((n & 0x00ff00ff) << 8)
n = ((n & 0xf0f0f0f0) >> 4) | ((n & 0x0f0f0f0f) << 4)
n = ((n & 0xcccccccc) >> 2) | ((n & 0x33333333) << 2)
n = ((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1)
return n
67. 二进制求和
1 |
|
22. 括号生成
- 这个题目是一个比较经典的回溯算法题目
- 两个条件
- (括号的数量如果小于n,说明可以再加
- )括号的数量如果小于左括号,说明可以再加
- 终止条件:当括号字符串的长度=2 * n
1
2
3
4
5
6
7
8
9
10
11
12
13# 伪代码(默写复习)
def xxx(n: int):
res = []
def backTrack(s, left, right):
if s == 2 * n:
res.append(s)
return
if left < n:
backTrack(s+'(', left + 1, right)
if right < left:
backTrack(s+')', left, right + 1)
backTrack('',0,0)
return res - 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17def generateParenthesis(self, n: int) -> List[str]:
# 使用回溯法解决问题
res = []
def backTrack(s: str, left: int, right: int):
if len(s) == 2 * n:
res.append(s)
return
# 如果左边括号未达到n个,说明还能再加括号
if left < n:
backTrack(s + "(", left + 1, right)
# 如果右边括号没有左边的多,说明需要加闭括号
if right < left:
backTrack(s + ")", left, right + 1)
backTrack('', 0, 0)
return res
17. 电话号码的字母组合
1 |
|
77. 组合
- 4/17复习
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# 组合
def combine(self, n: int, k: int) -> List[List[int]]:
# 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
if k == 0:
return []
res = []
def backTrack(current: List[int], start: int):
if len(current) == k:
# 使用切片复制一份
res.append(current[:])
return
for i in range(start, n + 1):
current.append(i)
backTrack(current, i + 1)
# 恢复状态
current.pop()
backTrack([], 1)
return res
79. 单词搜索
- 4/17复习
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34# 单词搜索
def exist(self, board: List[List[str]], word: str) -> bool:
if not board:
return False
col, row = len(board[0]), len(board)
# 定义一下搜索方向
direct = [(0, 1), (1, 0), (0, -1), (-1, 0)]
# 创建visited数组
visited = [[False] * col for _ in range(row)]
def dfs(x, y, index):
if index == len(word):
return True
# 判断是否越界
# 判断条件有点多呀
if x < 0 or x >= row or y < 0 or y >= col or board[x][y] != word[index] or visited[x][y]:
return False
# 检查下一个
visited[x][y] = True
for item in direct:
if dfs(x+item[0], y+item[1], index + 1):
return True
visited[x][y] = False
return False
for i in range(row):
for j in range(col):
if dfs(i, j, 0):
return True
return False
39. 组合总和
1 |
|
- 下面的代码有问题
1 |
|
46. 全排列
1 |
|
82. 删除排序链表中的重复元素 II
1 |
|
92. 反转链表 II
1 |
|
21. 合并两个有序链表
1 |
|
141. 环形链表
- 4/17复习
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 环形链表
def hasCycle(self, head: Optional[ListNode]) -> bool:
# 如何检测链表中有没有环
# 使用快慢指针!一个移动1个,一个移动2个,如果他们总会相遇,则说明有环
if not head:
return False
slow = head
fast = head.next()
while slow != fast:
# 如果快指针走到头了 说明没必要走了
if not fast or not fast.next:
return False
slow = slow.next
fast = fast.next.next
return True
322. 零钱兑换
- 4/17复习
- 为什么贪心不行?因为可能使用最大的面值最大数量导致最后不够分了,并不是最优解
- 什么时候贪心可以?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 零钱兑换
def coinChange(self, coins: List[int], amount: int) -> int:
# 这个题目一眼动态规划 二眼贪心
# 创建dp数组
# 含义 要达到i金额最小的硬币数量
dp = [float('inf')] * (amount + 1)
# 初始化dp数组
dp[0] = 0
# 按照硬币来遍历
for coin in coins:
for x in range(coin, amount + 1):
dp[x] = min(dp[x], dp[x-coin] + 1)
# 最后返回最后一个值
return dp[amount] if dp[amount] != float('inf') else -1
139. 单词拆分
- 4/17复习
1
2
3
4
5
6
7
8
9
10
11
12def wordBreak(self, s: str, wordDict: List[str]) -> bool:
# 一个简单的动态规划问题
# 首先初始化dp数组和dict集合
wordSet = set(wordDict)
dp = [False] * (len(s) + 1)
dp[0] = True
for i in range(1,len(s) + 1):
for j in range(i):
if dp[j] and s[j:i] in wordSet:
dp[i] = True
break
return dp[len(s)] - 原始代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 单词拆分
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
# 一个简单的动态规划问题
# 初始化dp数组
# 建议将wordDict转换为集合,这样在查找时可以实现更快的操作,集合的查找时间复杂度是O(1)。
wordSet = set(wordDict)
dp = [False] * (len(s) + 1)
dp[0] = True
for i in range(1, len(s)+1):
for j in range(i):
if dp[j] and s[j:i] in wordSet:
dp[i] = True
break
return dp[len(s)]
56. 合并区间
1 |
|
228. 汇总区间
1 |
|
73. 矩阵置零
- 这个题目
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# 矩阵置零
# 性能比较差的解法
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
# 感觉这个题的思路是:先记录下需要改变的行和列,最后统一置0
rowSet = set()
colSet = set()
for i in range(len(matrix)):
for j in range(len(matrix[0])):
if matrix[i][j] == 0:
rowSet.add(i)
colSet.add(j)
for item in rowSet:
for i in range(len(matrix[item])):
matrix[item][i] = 0
for item in colSet:
for j in range(len(matrix)):
matrix[j][item] = 0
48. 旋转图像
- 开始想复杂了,实际上比较简单
- 就是一个转置+水平翻转
1
2
3
4
5
6
7
8
9
10
11
12
13# 旋转图像
def rotate(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
# 原来旋转=转置+翻转行
for i in range(len(matrix)):
for j in range(i, len(matrix[0])):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
# 翻转行
for i in range(len(matrix)):
matrix[i].reverse()
219. 存在重复元素 II
- 两种解法:哈希表(和两数之和有点像)、滑动窗口+set
1
2
3
4
5
6
7
8def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
# 创建一个记录坐标的哈希表
posDict = {}
for i in range(len(nums)):
if nums[i] in posDict and i - posDict[nums[i]] <= k:
return True
posDict[nums[i]] = i
return False - 滑动窗口的解法(很巧妙呀)
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 滑动窗口
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
if len(nums) == 0:
return False
s = set()
for i, x in enumerate(nums):
# 超过窗口就移除之前那个
if i > k:
s.remove(nums[i-k-1])
if x in s:
return True
s.add(x)
return False
290. 单词规律
- 和205简直一模一样
- 也是需要创建双向映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25# 单词规律
def wordPattern(self, pattern: str, s: str) -> bool:
# 创建一个映射哈希表
sArr = s.split(' ')
sLen, tLen = len(sArr), len(pattern)
if sLen != tLen:
return False
# 创建两个哈希表
s2t = {}
t2s = {}
for i in range(sLen):
charS, charT = pattern[i], sArr[i]
if charS in s2t and s2t[charS] != charT:
return False
if charT in t2s and t2s[charT] != charS:
return False
s2t[charS] = charT
t2s[charT] = charS
return True
205. 同构字符串
- 注意读题啊!
- 首先需要理解什么是同构字符串—两个字符串中的单词映射应当是唯一的,而不是相差的距离一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# 同构字符串
def isIsomorphic(self, s: str, t: str) -> bool:
sLen, tLen = len(s), len(t)
if sLen != tLen:
return False
# 创建两个哈希表
s2t = {}
t2s = {}
for i in range(sLen):
charS,charT = s[i],t[i]
if charS in s2t and s2t[charS] != charT:
return False
if charT in t2s and t2s[charT] != charS:
return False
s2t[charS] = charT
t2s[charT] = charS
return True
392. 判断子序列
- 乍一看很简单,两个指针,一个指向s,一个指向t,慢慢遍历。进阶:
1
2
3
4
5
6
7
8
9
10
11
12def isSubsequence(self, s: str, t: str) -> bool:
# 创建两个指针分别指向s、t
sPointer, tPointer = 0,0
while sPointer < len(s) and tPointer < len(t):
if s[sPointer] == t[tPointer]:
sPointer += 1
tPointer += 1
else:
tPointer += 1
if sPointer == len(s):
return True
return False
如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
167. 两数之和 II - 输入有序数组
- 一个非常简单的双指针
1
2
3
4
5
6
7
8
9
10
11
12# 两数之和 II - 输入有序数组
def twoSum(self, numbers: List[int], target: int) -> List[int]:
# 感觉可以用双指针
left, right = 0, len(numbers) - 1
while left < right:
if numbers[left] + numbers[right] == target:
return [left+1, right+1]
elif numbers[left] + numbers[right] > target:
right -= 1
else:
left += 1
return []
149. 直线上最多的点数
- 注意点:斜率的处理(特殊情况、正负数)
- 思考点:只考虑斜率,不考虑截距不会出问题吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37# 直线上最多的点数
def maxPoints(self, points: List[List[int]]) -> int:
# 两点确定一条直线,所以可以计算两两之间的斜率?
# 使用哈希表计算所有可能的斜率?
# 斜率计算函数
def getSlope(p1, p2):
dx, dy = p2[0] - p1[0], p2[1] - p1[1]
# 特殊情况
# 斜率无穷大
if dx == 0:
return 'inf'
# 斜率为0
if dy == 0:
return 0
# 约分,保持精度
# 同时除以最大公约数
d = gcd(dx, dy)
# 如果是负数,需要统一,防止-1 / 2 和 1 / -2 不一样
if dy < 0:
dy = -dy
dx = -dx
return (dy // d, dx // d)
result = 0
for i in range(len(points)):
slopeDict = defaultdict(int)
duplicate = 1
for j in range(i + 1, len(points)):
if points[i] == points[j]:
duplicate += 1
continue
slope = getSlope(points[i], points[j])
slopeDict[slope] += 1
# 当前的最大值为重复点+max 斜率value
result = max(result, (max(slopeDict.values())
if slopeDict else 0) + duplicate)
return result
172. 阶乘后的零
- 找质因子10的个数,转换为2*5的个数,由于5比2的多,所以直接找5的
- 首先从5开始数,找5的倍数,5的倍数进行分解,看看有多少个因子5
1
2
3
4
5
6
7
8
9
10# 阶乘后的零
def trailingZeroes(self, n: int) -> int:
# 思路很难想到 质因子为10的个数
# 10 = 2 x 5
ans = 0
for i in range(5, n + 1, 5):
while i % 5 == 0:
i //= 5
ans += 1
return ans
151. 反转字符串中的单词
- 思路:去除前后空格 -> 翻转整个串 -> 按’ ‘进行split -> 翻转每个单词 -> 重新整合成串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# 反转字符串中的单词
# 不优雅版本
def reverseWords(self, s: str) -> str:
import re
sPie = re.sub(r'\s+', ' ', s).strip()[::-1]
sReverse = sPie[::-1]
sArr = sReverse.split(' ')
sArrAfter = []
for i in sArr:
sArrAfter.append(i[::-1])
res = ' '.join(sArrAfter)
return res
pass
# 优雅版
def reverseWords(self, s: str) -> str:
import re
sPie = re.sub(r'\s+', ' ', s).strip()
word = sPie.split(' ')
wordReverse = [item[::-1] for item in word]
return ' '.join(wordReverse)
380. O(1) 时间插入、删除和获取随机元素
- 数组+哈希表
- 各司其责,主要的难点在于删除
- 删除的时候,移动要删除的元素到数组末尾,再pop,时间复杂度降到最低!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40# O(1) 时间插入、删除和获取随机元素
class RandomizedSet:
# Your RandomizedSet object will be instantiated and called as such:
# obj = RandomizedSet()
# param_1 = obj.insert(val)
# param_2 = obj.remove(val)
# param_3 = obj.getRandom()
def __init__(self):
# 创建数组和哈希表
self.numList = []
self.numDict = {}
def insert(self, val: int) -> bool:
# 插入元素直接插入到数组
if val in self.numDict:
return False
self.numDict[val] = len(self.numList)
self.numList.append(val)
return True
def remove(self, val: int) -> bool:
# 删除元素比较难
if val not in self.numDict:
return False
# 思路是什么?
# 将要删那个数变成数组中的最后一个数!~太妙了
# 获取要删除的值的索引,并将其与列表中的最后一个元素交换
index = self.numDict[val]
lastVal = self.numList[-1]
self.numList[index] = lastVal
self.numDict[lastVal] = index
# 删除数组中最后一个元素
self.numList.pop()
del self.numDict[val]
return True
def getRandom(self) -> int:
return random.choice(self.numList)
274. H 指数
- 一个朴素的思想
1
2
3
4
5
6
7
8
9def hIndex(self, citations: List[int]) -> int:
# 看了之后就是先排序
citations.sort(reverse=True)
print(citations)
for i in range(len(citations)):
# 找到一个index+1 < citations[index]的值
if i + 1 > citations[i]:
return i
return len(citations)
122. 买卖股票的最佳时机 II
- 思考一下我的代码为什么这么拉跨(想必答案就是缺乏思考哈哈哈哈 形成了闭环)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18def maxProfit(self, prices: List[int]) -> int:
# 感觉是找上升序列的问题
# 找出所有的上升序列即可
# 标记上升序列的长度
start,end = 0,0
isUp = 0
res = 0
for i in range(1, len(prices)):
if prices[i] >= prices[i-1]:
isUp = 1
end += 1
else:
res += prices[end] - prices[start]
start = end+1
end = start
isUp = 0
res += prices[end] - prices[start]
return res - 优雅的代码
1
2
3
4
5
6
7
8
9# 优雅的代码就是不一样
def maxProfit(self, prices: List[int]) -> int:
profit = 0
# 遍历价格数组
for i in range(1, len(prices)):
# 如果今天的价格比昨天高,就认为可以进行一次交易
if prices[i] > prices[i-1]:
profit += prices[i] - prices[i-1]
return profit
121. 买卖股票的最佳时机
- 看了以前写的代码写出来了
- 核心思想是维护两个变量
- 如果有多只股票呢
1
2
3
4
5
6
7
8
9
10def maxProfit(self, prices: List[int]) -> int:
# 关键就是遍历与维护变量
cost = sys.maxsize
profit = 0
for item in prices:
if item < cost:
cost = item
if item - cost > profit:
profit = item - cost
return profit
16. 最接近的三数之和
- 思考这和传统“三数之和”有什么区别?
- 本题说明:最后只包含一个解,所以不需要考虑去重
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23# 最接近的三数之和
def threeSumClosest(self, nums: List[int], target: int) -> int:
# 三数之和的简化版
if len(nums) < 3:
return 0
# 首先对数组进行排序
nums.sort()
minSub = nums[0] + nums[1] + nums[2] - target
# 首先固定一个元素 然后进行双指针
for i in range(len(nums)):
left, right = i + 1, len(nums) - 1
while left < right:
sumThree = nums[i] + nums[left] + nums[right]
minSub = sumThree-target if abs(sumThree - target) <= abs(minSub) else minSub
if sumThree > target:
right -= 1
elif sumThree < target:
left += 1
else:
return minSub + target
return minSub + target
10. 正则表达式匹配
这个问题是一个经典的动态规划(Dynamic Programming,DP)问题,你正在尝试实现一个匹配字符串 s
和模式 p
的函数。在这种情况下,模式 p
可以包含普通字符和两个特殊字符:
.
- 可以匹配任何单个字符*
- 匹配零个或多个前面的那一个元素
![[Pasted image 20240402211458.png]]
这种情况下:dp[i][j] = dp[i-2][j]
![[Pasted image 20240402211440.png]]
这种情况下:dp[i][j] = dp[i][j-1]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22def isMatch(self, s: str, p: str) -> bool:
l1, l2 = len(s), len(p)
dp = [[False] * (l1+1) for _ in range(l2+1)]
# 初始化dp数组
dp[0][0] = True
for i in range(2, l2+1):
if p[i-1] == '*':
dp[i][0] = dp[i-2][0]
# 开始dp过程
for i in range(1, l2+1):
for j in range(1, l1+1):
if p[i-1] == '*':
if p[i-2] == s[j-1] or p[i-2] == '.':
# a * 出现1次或多次
dp[i][j] = dp[i][j-1]
# a * 可匹配0次
# 如果之前 dp[i][j] 已经是 True,或者如果消掉 *(和它前面的字符)后能够匹配 (dp[i-2][j] 是 True), 那么 dp[i][j] 应该为 True
# 等价于dp[i][j] = dp[i][j] or dp[i-2][j]
dp[i][j] |= dp[i-2][j]
elif p[i-1] == '.' or p[i-1] == s[j-1]:
dp[i][j] = dp[i-1][j-1]
return dp[l2][l1]
14. 最长公共前缀
- 简单题
- 注意边界条件的判定
1
2
3
4
5
6
7
8
9
10def longestCommonPrefix(self, strs: List[str]) -> str:
if not strs: # 检查 strs 是否为空
return ""
for i in range(len(strs[0])):
cur = strs[0][i]
for item in strs[1:]:
# 需要判断长度是否合适
if i >= len(item) or item[i] != cur:
return strs[0][: i]
return strs[0]
72. 编辑距离
- 一步一步递推的公式编辑距离 - 动态规划解法 Edit Distance - Dynamic Programming_哔哩哔哩_bilibili
![[Pasted image 20240401142732.png]] - 注意:
dp = [[0] * l1] * l2
这行代码创建了一个二维数组dp
,但方式不正确。因为[[0] * l1] * l2
这种方式复制的是同一个列表的引用。当你修改任意一个dp[j][i]
的值时,dp
的每一行都会被改变,因为它们实际上是同一个列表的引用。
应该用这个dp = [[0] * l1 for _ in range(l2)]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22def minDistance(self, word1: str, word2: str) -> int:
# 创建dp数组
l1, l2 = len(word1)+1, len(word2)+1
dp = [[0] * l1 for _ in range(l2)]
# 初始化dp数组的第一行和第一列
for i in range(1, l1):
dp[0][i] = dp[0][i-1] + 1
for j in range(1, l2):
dp[j][0] = dp[j-1][0] + 1
# 进行dp
for i in range(1, l2):
for j in range(1, l1):
# 这里需要考虑边界条件,因为i和j包含了空字符串的情况,因此需要-1
if word2[i-1] == word1[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
# 两个不相同
dp[i][j] = min(dp[i-1][j] + 1, dp[i]
[j-1] + 1, dp[i-1][j-1]+1)
return dp[l2-1][l1-1]
42. 接雨水
- 左右数组进行分别遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19def trap(self, height: List[int]) -> int:
left, right = [0] * len(height), [0] * len(height)
# 首先遍历左边,看左边的最大高度
maxLeft = height[0]
for i in range(1, len(height)):
if maxLeft < height[i - 1]:
maxLeft = height[i - 1]
left[i] = maxLeft
maxRight = height[len(height) - 1]
for j in range(len(height) - 2, -1, -1):
if maxRight < height[j + 1]:
maxRight = height[j + 1]
right[j] = maxRight
# 遍历
res = 0
for k in range(1, len(height) - 1):
res += max(0, min(right[k], left[k]) - height[k])
return res - 双指针?
15. 三数之和
- 这个题目特别的经典
- 但是我已经忘了怎么做了
- 参考一下coze的思路
- 首先进行排序,固定第一个数的index
- 然后使用双指针l、r对剩下的数进行筛选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28def threeSum(self, nums: List[int]) -> List[List[int]]:
# 和为0的话
# 原地排序
nums.sort()
result = []
# 我的思路是先固定一个数
for i in range(len(nums) - 2):
# 剩下的就是两数之和问题了
# 如果不是第一个数
if i > 0 and nums[i] == nums[i-1]:
# 跳过重复值
continue
# 双指针
l, r = i + 1, len(nums) - 1
while l < r:
total = nums[i] + nums[l] + nums[r]
if total < 0:
l += 1
elif total > 0:
r -= 1
else:
result.append([nums[i],nums[l],nums[r]])
# 去重复
while l < r and nums[l] == nums[l + 1]: l+=1
while l< r and nums[r] == nums[r-1]:r-=1
l+=1
r-=1
return result
3083. 字符串及其反转中是否存在同一子字符串
1 |
|
3084. 统计以给定字符开头和结尾的子字符串总数
1 |
|
2697. 字典序最小回文串
- 朴素的想法
- 实际上不是我的想法,而是灵神的想法哈哈哈
1
2
3
4
5
6
7
8
9
10
11
12# 字典序最小回文串
def makeSmallestPalindrome(self, s: str) -> str:
# 朴素的想法 使用哈希表 找到出现次数不是单数的(中间的不算)
# 然而事实证明 想得太复杂了
# 只需要一次遍历就好了
sList = list(s)
for i in range(len(s) // 2):
if sList[i] > sList[len(s) - i - 1]:
sList[i] = sList[len(s) - i - 1]
elif sList[i] < sList[len(s) - i - 1]:
sList[len(s) - i - 1] = sList[i]
return ''.join(sList)
2375. 根据模式串构造最小数字
- 先回忆一下灵神讲得东西
- 实际上采用的策略是贪心
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# 根据模式串构造最小数字
# 下面是抄的
def smallestNumber(self, pattern: str) -> str:
# 根据灵神的思路
n = len(pattern)
# digits = '0123456789'
print(digits)
ans = list(digits[1:n+2])
i = 0
while i < n:
# 如果处于升序
if pattern[i] == 'I':
i += 1
continue
i0 = i
i += 1
while i < n and pattern[i] == 'D':
i += 1
# 反转
ans[i0:i+1] = ans[i0:i+1][::-1]
return ''.join(ans)
2374. 边积分最高的节点
- 使用一种优雅的做法(来自灵神)
1
2
3
4
5
6
7
8
9
10
11
12
13# 边积分最高的节点
# 一下子从吊车尾的时空占有情况提升到前面,果然代码还得是要优化啊
def edgeScore(self, edges: List[int]) -> int:
# 定义一个score数组 和 最后的maxKey
maxKey,maxVal, score = 0,0, [0]* len(edges)
# 一次遍历搞定
for i in range(len(edges)):
to = edges[i]
score[to] = score[to] + i
if score[to] > maxVal or (score[to]==maxVal and to < maxKey):
maxKey = to
maxVal = score[to]
return maxKey - 有点脱裤子放屁的做法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# 边积分最高的节点
def edgeScore(self, edges: List[int]) -> int:
# 首先明确边积分的定义:
# 指向节点i的所有边的起始编号之和
# 朴素的想法是使用map
degree = OrderedDict()
for i in range(len(edges)):
degree[edges[i]] = i + degree.get(edges[i], 0)
# 取最大的key,但是这里的key其实是没有排序的,所以会有问题
maxKey = 0
for key in degree.keys():
if degree.get(maxKey, -1) < degree[key] or (degree.get(maxKey, -1) == degree[key] and maxKey > key):
maxKey = key
return maxKey
2373. 矩阵中的局部最大值
- 实际上就是深度学习中的Maxpooling操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 矩阵中的局部最大值
def largestLocal(self, grid: List[List[int]]) -> List[List[int]]:
# 基本思想是将每次计算的值放在左上角
for i in range(len(grid) - 2):
for j in range(len(grid) - 2):
# 计算3 * 3矩阵的最大值
# 请我不要犯傻了,这么简单的问题都要错
# 首先不要重复使用循环变量♻️
# 其次,不要只在内层最大值求整行的最大值,而是窗口内的最大值
# 都没有使用到变量j,怎么会得到正确答案呢?
grid[i][j] = max(max([item for item in grid[k][j:j+3]])
for k in range(i, i+3))
grid[i].pop()
grid[i].pop()
grid.pop()
grid.pop()
return grid
200. 岛屿数量
- 使用dfs深度优先遍历,本质上是递归
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# 不含连续1的非负整数
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(i, j):
# 请注意i和j的索引范围,一定要包含等号!!!
if i < 0 or i >= len(grid) or j < 0 or j >= len(grid[0]) or grid[i][j] == '0':
return
grid[i][j] = '0'
dfs(i+1, j)
dfs(i-1, j)
dfs(i, j+1)
dfs(i, j-1)
# 如果grid = null直接返回
if not grid:
return 0
count = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == '1':
# 这一步会把所有临近的陆地标记成为海洋,一举两得
dfs(i, j)
count += 1
return count
135. 分发糖果
- 好久没做了,想到了解法的一半(纯纯凭直觉做的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# 分发糖果
def candy(self, ratings: List[int]) -> int:
# 看到相邻问题 直接使用左右两个数组
# 需要注意的是 左右两边只能计算一次 而不能考虑得过于周全
# 基础糖果数量
baseVal = len(ratings)
# 由于相邻的两个孩子有一个要求是评分更高的糖果更多
left = [1] * baseVal
right = [1] * baseVal
for i in range(1, len(ratings)):
if ratings[i-1] < ratings[i]:
left[i] = left[i-1] + 1
for i in range(len(ratings)-2, -1, -1):
if ratings[i] > ratings[i+1]:
right[i] = right[i+1] + 1
res = 0
for i in range(0, baseVal):
res += max(right[i], left[i])
return res - 常数级别遍历
![[Pasted image 20240323203335.png]]
![[Pasted image 20240323203347.png]]
需要注意的是,评分相同时,后面的那个孩子应该给1个糖果(因为题目中没有说评分一致时糖果也应该一致),同时增加序列长度的记忆功能,当升降序列长度相等时,应该为降序列再增加一个糖果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20def candy(self, ratings: List[int]) -> int:
# 使用增长序列的方法来做
# 初始化一些变量
length = len(ratings)
dec, inc, pre, res = 0, 1, 1, 1
for i in range(1, length):
if ratings[i] >= ratings[i-1]:
dec = 0
# 说明在升序序列
pre = pre + 1 if ratings[i] != ratings[i-1] else 1
res += pre
# 升序序列的长度
inc = pre
else:
dec += 1
if dec == inc:
dec += 1
res += dec
pre = 1
return res
1835. 所有数对按位与结果的异或和
- 优化后的结果(根据分配律进行优化)
- a&c ^b&c = (a^b) & c
1
2
3
4
5
6
7
8
9
10
11
12
13# 考虑优化算法
def getXORSum(self, arr1: List[int], arr2: List[int]) -> int:
# a&c ^ b&c = (a^b) & c
# 根据这个公式
a1 = arr1[0]
a2 = arr2[0]
# 注意边界条件,因为一个数与自己做异或的结果是0,所以一定要注意边界条件
# 0 位置的已经取出来了,所以需要从1开始
for i in range(1, len(arr1)):
a1 = a1 ^ arr1[i]
for i in range(1, len(arr2)):
a2 = a2 ^ arr2[i]
return a1 & a2 - 一个看似正确但是超过内存限制的暴力算法
1
2
3
4
5
6
7
8
9
10
11
12
13# 下面的算法会超过内存限制 gg
def getXORSum(self, arr1: List[int], arr2: List[int]) -> int:
# 先写一个试试吧
andSum = []
for i in range(0,len(arr1)):
for j in range(0,len(arr2)):
andSum.append(arr1[i] & arr2[j])
if len(andSum)<=1: return andSum[0]
res = andSum[0]
for i in range(1, len(andSum)):
res = res ^ andSum[i]
return res
1840. 最高建筑高度
- 视频讲解LeetCode LeetCode 1840. Maximum Building Height | Weekly Contest 239
这个题搞了我半天,最主要的其实是最终高度的计算,视频里的点很关键,如果左右两边的限制高度不一样,则可以人为让他们一样,即让少的一个多爬x个,再直接求两个相同高度的限制中的最高高度。 - 如果两个高度一样
![[Pasted image 20240504231729.png]] - 如果不一样呢,那就先让两个变得一样,再按上面的方法计算
![[Pasted image 20240504231816.png]]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32# 最大建筑高度
def maxBuilding(self, n: int, restrictions: List[List[int]]) -> int:
# 补充额外限制条件
restrictions.append([1, 0])
restrictions.append([n, n-1])
# 排序
restrictions.sort()
# 遍历限制数组
for i in range(1, len(restrictions)):
# 有如下限制
# 相邻两个高度差不能超过1
# 注意:
# restrictions[i][0] 表示地的编号
# restrictions[i][1] 表示地的限制高度
restrictions[i][1] = min(
restrictions[i][1], restrictions[i-1][1] + (restrictions[i][0] - restrictions[i-1][0]))
for i in range(len(restrictions)-2, -1, -1):
restrictions[i][1] = min(
restrictions[i][1], restrictions[i+1][1] + (restrictions[i+1][0] - restrictions[i][0]))
# 遍历得到最大高度
maxHeight = 0
for i in range(1, len(restrictions)):
id1, h1 = restrictions[i-1]
id2, h2 = restrictions[i]
maxHeight = max(maxHeight, (h2 + h1 + id2 - id1)//2)
# 可以除2是因为从一个高度到另一个高度可以先上升后下降
# 来一个形象的例子
# / \
# / \
# \
return maxHeight - coze写的代码
补充说明图片(5.4)
![[Pasted image 20240504231444.png]]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33def maxBuilding(self, n: int, restrictions: List[List[int]]) -> int:
# 这个题不会做啊
# 只能看题解了
# 首先补充约束条件
# 第一块地不能修建筑
restrictions.append([1, 0])
# 对建筑进行排序
restrictions.sort()
# 最后一块地的限制建筑高度(实际没有这个限制,主要是给n-1号用的)
restrictions.append([n, n-1])
# 从左往右遍历,应用限制
for i in range(1, len(restrictions)):
# 设置restrictions[i][0]块地的限制高度
restrictions[i][1] = min(
restrictions[i][1], restrictions[i-1][1] +
restrictions[i][0]-restrictions[i-1][0]
)
# 从右往左遍历,应用限制(为什么第二个参数是-1?因为0是最后一个需要遍历的值,所以0的下一个就是-1)
for i in range(len(restrictions)-2, -1, -1):
restrictions[i][1] = min(
restrictions[i][1], restrictions[i + 1][1] +
restrictions[i + 1][0] - restrictions[i][0]
)
# 在满足所有限制条件下找最高建筑
max_h = 0
for i in range(1, len(restrictions)):
id1, h1 = restrictions[i - 1]
id2, h2 = restrictions[i]
max_h = max(max_h, (h2 - h1 + id2 - id1) // 2 + h1)
return max_h - 复习写的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25def maxBuilding(self, n: int, restrictions: List[List[int]]) -> int:
# 首先添加虚拟建筑
restrictions.append([1, 0])
restrictions.append([n, n-1])
restrictions.sort()
# 从左到右遍历
for i in range(1, len(restrictions)):
restrictions[i][1] = min(
restrictions[i][1], restrictions[i-1][1]+(restrictions[i][0]-restrictions[i-1][0]))
# 从右往左遍历
for i in range(len(restrictions)-2, -1, -1):
restrictions[i][1] = min(
restrictions[i][1], restrictions[i+1][1]+(restrictions[i+1][0]-restrictions[i][0]))
# 找到最大建筑
maxHeight = 0
for i in range(1, len(restrictions), 1):
index1, height1 = restrictions[i-1]
index2, height2 = restrictions[i]
maxHeight = max(
maxHeight, ((index2-index1)+(height2-height1))//2+height1)
return maxHeight
670. 最大交换
- 5.4复习
![[Pasted image 20240504230333.png]] - 3.23复习
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# 3/23最大交换
def maximumSwap(self, num: int) -> int:
# 目标:找到x右侧比x大的数
# 忘了昨天怎么做的了
# 盲猜从右向左遍历
# 首先转换int为数组
numList = [int(digit) for digit in str(num)]
# 记录每个数字最后出现的位置
lastSeen = {x: i for i, x in enumerate(numList)}
for i, x in enumerate(numList):
# 从9-(x+1)的范围内找数,看看是不是在右边,从大往小找
for j in range(9, x, -1):
if lastSeen.get(j, -1) > i:
# 说明找到了
# 交换
numList[i], numList[lastSeen.get(
j)] = numList[lastSeen.get(j)], numList[i]
return int(''.join(map(str, numList)))
return num - 一种朴素且错误的思维
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# 这个版本有问题
def maximumSwap(self, num: int) -> int:
# 一个朴素的想法是把最大的值和第一位交换,得到的总是较大的
# 遍历一次数组
# 把num转换成字符串
listNum = self.intToList(num)
maxIndex = 0
for i in range(0, len(listNum)):
maxIndex = maxIndex if listNum[maxIndex] > listNum[i] else i
# swap
listNum[0], listNum[maxIndex] = listNum[maxIndex], listNum[0]
return self.listToInt(listNum)
# 想法果然还是太过于朴素了,因为完全没有考虑到最大的值已经在最高位的情况
# 那应该怎么做?把第二大的放到第二位?也不可行,需要考虑的有点多
# 比如98368这个数字
# 第二大的是8 但是8已经在第2位了
# 继续下去?
# 复杂度直接进化到O(N^2)了 - 我愿称coze为yyds
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26class Solution:
# 最大交换
def intToList(self, n: int) -> List:
return [int(digit) for digit in str(n)]
def listToInt(self, nums: List) -> int:
return int(''.join(map(str, nums)))
# coze协作编程代码
# 方法很巧妙
def maximumSwap(self, num: int) -> int:
# 把num转换成数组
numList = self.intToList(num)
# 记录每个数字最后出现的位置(用了一个字典,如果有重复值,则会更新位置,由于是从前到后遍历,所以可以保证是最后出现的)
last = {x: i for i, x in enumerate(numList)}
# 从左到右遍历数组
for i, x in enumerate(numList):
# 从9到x + 1查找可能的最大值(倒序查找,找到x右侧比x大的数)
for d in range(9, x, -1):
# 如果找到更大值,并且该更大值的索引在当前索引的右侧
if last.get(d, -1) > i:
# 交换这两个值
numList[i], numList[last[d]] = numList[last[d]], numList[i]
# 由于只允许交换一次,直接返回结果
return self.listToInt(numList)
# 如果没有交换发生,直接返回原数
return num
53. 最大子数组和
- 错误的双指针法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18# 最大子数组和
def maxSubArray(self, nums: List[int]) -> int:
# 看到题目想到的就是滑动窗口
if len(nums) < 2:
return nums[0]
# 初始化两个指针
left, right = 0, 1
maxVal = -sys.maxsize - 1
while (right < len(nums)):
# 当前窗口和
curVal = self.subArraySum(nums, left, right)
maxVal = maxVal if maxVal >= curVal else curVal
# 移动指针
# 做到这里 感觉好像找不到移动指针的方法了...
# 但是通过思考发现 好像可以用动态规划
right += 1
pass
pass - 正确的动态规划法(卡登算法?)
1
2
3
4
5
6
7
8# 最大子数组和
def maxSubArray(self, nums: List[int]) -> int:
# 使用动态规划来解决这个问题
# 首先初始化dp数组
dp = [nums[0]] * len(nums)
for i in range(1, len(nums)):
dp[i] = max(dp[i-1]+nums[i], nums[i])
return max(dp)
⚠️思考为什么最后一个值不是最大值?
因为dp[i]的值是以nums[i]结尾的子数组的最大和,所以并不是全局最大和
704. 二分查找
很简单,注意边界条件需要包含等于,否则只有一个元素的情况下会直接返回-1
1 |
|
27. 移除元素
1 |
|
977. 有序数组的平方
1 |
|
414. 第三大的数
- 一种错误的解法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Solution:
# 第三大的数
def thirdMax(self, nums: List[int]) -> int:
# 最简单的方法就是排序之后选第3大的
# 但是进一步思考,如果只需要第3大的,还需要对所有数组排序吗
# 看上去是不需要的,因此这里使用冒泡排序找到第三大的即可
# 时间复杂度O(3N)
length = len(nums)
for i in range(0, length if length <= 3 else 3):
for j in range(i + 1, length):
if nums[i] > nums[j]:
# swap
nums[i], nums[j] = nums[j], nums[i]
print(nums)
# 事实证明这样的解法是有bug的,因为nums中可能有重复的值,所以还需要进行去重操作
# 一种想到的思路是维护一个集合,用来去重,但是不如一开始就用集合,所以这样是脱裤子放屁
return nums[-3] if length >= 3 else nums[-1] - 正确的高效解法:运用集合去重(注意事项:边界条件)
1
2
3
4
5
6
7
8
9
10
11
12def thirdMax(self, nums: List[int]) -> int:
# 维护一个集合,使得集合中只有3个数,并且超出时删除最小的
s = set()
# 遍历数组
for i in range(0,len(nums)):
s.add(nums[i])
if len(s) > 3:
# 删除集合中最小的值
minValue = min(s)
s.remove(minValue)
print(s)
return min(s) if len(s) >= 3 else max(s)
算法能力提升计划 Algorithm Capability Enhancement Program
https://yintel12138.github.io/2024/04/01/Algorithm-Improve/