算法刷题记录——LeetCode篇(3) [第201~300题](持续更新)
(优先整理热门100及面试150,不定期持续更新,欢迎关注)
207. 课程表
你这个学期必须选修 numCourses
门课程,记为 0
到 numCourses - 1
。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites
给出,其中 prerequisites[i]
= [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1]
表示:想要学习课程 0
,你需要先完成课程 1
。
请你判断是否可能完成所有课程的学习?如果可以,返回 true
;否则,返回 false
。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
提示:
1 <= numCourses <= 2000
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i] 中的所有课程对互不相同
方法一:拓扑排序(BFS)
- 核心思想:通过入度表逐层消除无依赖的节点,若最终所有节点都被消除则无环。
- 步骤:
- 构建邻接表和入度表。
- 将入度为0的节点入队,依次处理并减少其后继节点的入度。
- 若处理节点数等于总节点数,则无环。
代码实现(Java):
class Solution {public boolean canFinish(int numCourses, int[][] prerequisites) {// 构建邻接表和入度表List<List<Integer>> adj = new ArrayList<>();int[] inDegree = new int[numCourses];for (int i = 0; i < numCourses; i++) {adj.add(new ArrayList<>());}for (int[] p : prerequisites) {int ai = p[0], bi = p[1];adj.get(bi).add(ai); // 添加边 bi → aiinDegree[ai]++; // ai 的入度增加}// 初始化队列,入度为0的课程入队Queue<Integer> queue = new LinkedList<>();for (int i = 0; i < numCourses; i++) {if (inDegree[i] == 0) {queue.offer(i);}}// 处理队列中的课程int count = 0;while (!queue.isEmpty()) {int course = queue.poll();count++;for (int next : adj.get(course)) {inDegree[next]--;if (inDegree[next] == 0) {queue.offer(next);}}}return count == numCourses;}
}
方法二:DFS检测环
- 核心思想:通过递归遍历节点,若在递归路径中遇到正在访问的节点,说明存在环。
- 步骤:
- 构建邻接表。
- 对每个未访问节点进行DFS,通过状态标记检测环。
代码实现(Java):
class Solution {public boolean canFinish(int numCourses, int[][] prerequisites) {// 构建邻接表List<List<Integer>> adj = new ArrayList<>();for (int i = 0; i < numCourses; i++) {adj.add(new ArrayList<>());}for (int[] p : prerequisites) {int ai = p[0], bi = p[1];adj.get(bi).add(ai); // 添加边 bi → ai}// 访问状态:0-未访问,1-访问中,2-已访问int[] visited = new int[numCourses];for (int i = 0; i < numCourses; i++) {if (visited[i] == 0) {if (!dfs(adj, visited, i)) {return false;}}}return true;}private boolean dfs(List<List<Integer>> adj, int[] visited, int node) {if (visited[node] == 1) return false; // 发现环if (visited[node] == 2) return true; // 已处理过visited[node] = 1; // 标记为访问中for (int neighbor : adj.get(node)) {if (!dfs(adj, visited, neighbor)) {return false;}}visited[node] = 2; // 标记为已访问return true;}
}
复杂度分析
- 拓扑排序(BFS):时间复杂度
O(V + E)
,适用于大多数场景,尤其适合检测有向无环图(DAG)。 - DFS检测环:时间复杂度
O(V + E)
,可能因递归深度导致栈溢出,适用于较小规模的图。
对比总结
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
拓扑排序 | 无递归栈溢出风险 | 需要额外存储入度表 | 大规模图或稳定性要求高 |
DFS检测环 | 代码简洁,无需额外空间 | 递归深度可能受限 | 小规模图或代码简洁需求 |
208. 实现 Trie (前缀树)
Trie
(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补全和拼写检查。
请你实现 Trie
类:
Trie();
// 初始化前缀树对象。
void insert(String word);
// 向前缀树中插入字符串 word 。
boolean search(String word);
// 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix);
//如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
示例:
输入:
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
输出:
[null, null, true, false, true, null, true]
解释:
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // 返回 True
trie.search("app"); // 返回 False
trie.startsWith("app"); // 返回 True
trie.insert("app");
trie.search("app"); // 返回 True
提示:
1 <= word.length, prefix.length <= 2000
word 和 prefix 仅由小写英文字母组成
insert、search 和 startsWith 调用次数总计不超过 3*10^4 次
实现方法
Trie(前缀树)是一种树形数据结构,用于高效存储和检索字符串。每个节点包含子节点数组(对应26个小写字母)和一个结束标志。插入时逐层创建节点,结束时标记;搜索时检查路径存在且结束标志为真;前缀匹配只需路径存在。
- 节点结构:
TrieNode
类包含一个长度为26的子节点数组和一个结束标记isEnd
。 - 初始化:构造时创建根节点。
- 插入:遍历单词字符,逐层创建子节点,结尾设置
isEnd
。 - 搜索:检查路径存在且结尾
isEnd
为真。 - 前缀检查:只需路径存在,不检查结束标记。
代码实现(Java):
class Trie {private class TrieNode {TrieNode[] children;boolean isEnd;TrieNode() {children = new TrieNode[26];isEnd = false;}}private TrieNode root;public Trie() {root = new TrieNode();}public void insert(String word) {TrieNode current = root;for (char c : word.toCharArray()) {int index = c - 'a';if (current.children[index] == null) {current.children[index] = new TrieNode();}current = current.children[index];}current.isEnd = true;}public boolean search(String word) {TrieNode node = searchPrefix(word);return node != null && node.isEnd;}public boolean startsWith(String prefix) {return searchPrefix(prefix) != null;}private TrieNode searchPrefix(String prefix) {TrieNode current = root;for (char c : prefix.toCharArray()) {int index = c - 'a';if (current.children[index] == null) {return null;}current = current.children[index];}return current;}
}
复杂度分析
- 时间复杂度
- 插入:
O(L)
,L
为单词长度; - 搜索/前缀:
O(L)
,L
为查询字符串长度。
- 插入:
- 空间复杂度:
O(N×L)
,N
为插入单词数,L
为平均长度。
215. 数组中的第K个最大元素
给定整数数组 nums
和整数 k
,请返回数组中第 k
个最大的元素。
请注意,你需要找的是数组排序后的第 k
个最大的元素,而不是第 k
个不同的元素。
你必须设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 1:
输入: [3,2,1,5,6,4], k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4
提示:
1 <= k <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
方法:三向切分快速选择法
利用快速选择算法结合三向切分,高效定位第K
大元素。
-
三向切分:
- 将数组分为三个区域:大于基准值、等于基准值、小于基准值;
- 采用类似荷兰国旗问题的分区方式,一次遍历完成分类;
-
递归选择:
- 根据当前分区后的元素数量分布,决定递归处理方向;
- 大于区域元素足够时递归处理前部;
- 数量不足但包含等于区域时直接返回基准值;
- 否则递归处理后部并调整k值;
随机选择基准值避免最坏情况,每次递归至少排除一个区域元素。
代码实现(Java):
class Solution {public int findKthLargest(int[] nums, int k) {return quickSelect(nums, 0, nums.length - 1, k);}private int quickSelect(int[] nums, int left, int right, int k) {if (left == right) {return nums[left];}Random rand = new Random();int pivotIndex = left + rand.nextInt(right - left + 1);int pivot = nums[pivotIndex];// 三向切分(荷兰国旗问题)int low = left;int high = right;int mid = left;while (mid <= high) {if (nums[mid] > pivot) { // 大于pivot的交换到前部swap(nums, low, mid);low++;mid++;} else if (nums[mid] < pivot) { // 小于pivot的交换到后部swap(nums, mid, high);high--;} else { // 等于时继续后移mid++;}}// 计算各区域元素数量int gtCount = low - left; // 大于pivot的数目int eqCount = high - low + 1; // 等于pivot的数目if (k <= gtCount) { // 目标在大于区域return quickSelect(nums, left, low - 1, k);} else if (k <= gtCount + eqCount) { // 目标在等于区域return pivot;} else { // 目标在小于区域return quickSelect(nums, high + 1, right, k - gtCount - eqCount);}}private void swap(int[] nums, int i, int j) {int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}
}
复杂度分析
平均时间复杂度 O(n)
,最坏情况 O(n^2)
。
226. 翻转二叉树
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
示例 1:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
示例 2:
输入:root = [2,1,3]
输出:[2,3,1]
示例 3:
输入:root = []
输出:[]
提示:
树中节点数目范围在 [0, 100] 内
-100 <= Node.val <= 100
方法一:递归法
递归交换每个节点的左右子树,自顶向下逐层翻转。
代码实现(Java):
class Solution {public TreeNode invertTree(TreeNode root) {if (root == null) {return null;}// 交换当前节点的左右子节点TreeNode temp = root.left;root.left = root.right;root.right = temp;// 递归处理左右子树invertTree(root.left);invertTree(root.right);return root;}
}
方法二:广度优先搜索(BFS)
利用队列层序遍历,逐个节点交换左右子树。
代码实现(Java):
class Solution {public TreeNode invertTree(TreeNode root) {if (root == null) return null;Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {TreeNode node = queue.poll();// 交换左右子节点TreeNode temp = node.left;node.left = node.right;node.right = temp;// 将子节点加入队列继续处理if (node.left != null) queue.offer(node.left);if (node.right != null) queue.offer(node.right);}return root;}
}
方法三:深度优先搜索(DFS)迭代法
使用栈模拟递归过程,前序遍历时交换左右子树。
代码实现(Java):
class Solution {public TreeNode invertTree(TreeNode root) {if (root == null) return null;Stack<TreeNode> stack = new Stack<>();stack.push(root);while (!stack.isEmpty()) {TreeNode node = stack.pop();// 交换左右子节点TreeNode temp = node.left;node.left = node.right;node.right = temp;// 将子节点压入栈(先右后左保证处理顺序)if (node.left != null) stack.push(node.left);if (node.right != null) stack.push(node.right);}return root;}
}
复杂度分析
- 递归法:从根节点开始,交换左右子节点,然后递归处理左右子树。每个节点只需处理一次,时间复杂度为 (O(n)),空间复杂度为 (O(h))(树的高度)。
- BFS迭代:借助队列层序遍历,每次处理节点时交换其左右子节点,并将子节点加入队列。时间复杂度 (O(n)),空间复杂度 (O(n))(最坏情况)。
- DFS迭代:使用栈实现深度优先遍历,处理节点时交换左右子节点。时间复杂度 (O(n)),空间复杂度 (O(h))(树的高度)。
230. 二叉搜索树中第 K 小的元素
给定一个二叉搜索树的根节点 root
,和一个整数 k
,请你设计一个算法查找其中第 k
小的元素(从 1 开始计数)。
示例 1:
输入:root = [3,1,4,null,2], k = 1
输出:1
示例 2:
输入:root = [5,3,6,2,4,null,null,1], k = 3
输出:3
提示:
树中的节点数为 n 。
1 <= k <= n <= 10^4
0 <= Node.val <= 10^4
进阶:
如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化算法?
方法一:递归中序遍历(提前终止)
- 利用中序遍历左-根-右的顺序天然有序的特点。
- 遍历时对访问的节点计数,当计数达到
k
时记录结果并立即终止递归(通过返回值true
提前退出递归栈)。 - 剪枝优化,左子树找到目标后直接返回,避免无意义的右子树遍历。
代码实现(Java):
class Solution {private int count;private int result;public int kthSmallest(TreeNode root, int k) {count = k;inorder(root);return result;}private boolean inorder(TreeNode node) {if (node == null) return false;if (inorder(node.left)) return true; // 左子树已找到目标if (--count == 0) { // 处理当前节点result = node.val;return true; // 找到目标,终止递归}return inorder(node.right); // 继续遍历右子树}
}
方法二:迭代中序遍历(显式栈)
- 用栈显式保存遍历路径,先深度优先遍历到最左节点。
- 每次弹出栈顶元素时计数,找到第
k
个元素时直接返回。 - 栈的深度为树高,避免递归栈溢出的风险。
代码实现(Java):
class Solution {public int kthSmallest(TreeNode root, int k) {Stack<TreeNode> stack = new Stack<>();TreeNode curr = root;int count = 0;while (curr != null || !stack.isEmpty()) {// 遍历到最左节点while (curr != null) {stack.push(curr);curr = curr.left;}// 回溯处理节点curr = stack.pop();if (++count == k) return curr.val; // 找到第k小的元素curr = curr.right; // 转向右子树}return -1; // 题目保证k有效,此处不会执行}
}
复杂度分析
- 时间复杂度:两种方法均为 O(k),最坏情况为 O(n)(当
k=n
时需要遍历整棵树)。 - 空间复杂度:递归法 O(h)(h为树高),迭代法 O(h)(显式栈的空间占用)。
236. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T
的两个节点 p
、q
,最近公共祖先表示为一个节点 x
,满足 x
是 p
、q
的祖先且 x
的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
示例 3:
输入:root = [1,2], p = 1, q = 2
输出:1
提示:
树中节点数目在范围 [2, 1e5] 内
-10^9 <= Node.val <= 10^9
所有 Node.val 互不相同
p != q
p 和 q 均存在于给定的二叉树中
方法一:递归法(后序遍历)
- 核心思想:利用二叉树的后序遍历特性,自底向上查找公共祖先。
- 关键点:
- 当某节点左右子树分别包含p和q时,该节点即为LCA。
- 若仅单侧子树包含目标节点,则返回该侧结果。
代码实现(Java):
class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {// 当前节点为null或等于p/q时直接返回if (root == null || root == p || root == q) return root;// 递归处理左右子树TreeNode left = lowestCommonAncestor(root.left, p, q);TreeNode right = lowestCommonAncestor(root.right, p, q);// 情况1:左右子树均包含目标节点,当前节点为LCAif (left != null && right != null) return root;// 情况2:仅一侧有目标节点,返回该侧结果return left != null ? left : right;}
}
方法二:迭代法(父指针回溯)
- 核心思想:通过层序遍历建立父节点映射,回溯祖先链求交点。
- 关键步骤:
- 使用队列进行层序遍历,记录每个节点的父节点。
- 回溯p的路径存入集合,再回溯q的路径寻找交点。
代码实现(Java):
class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {// 存储所有节点的父指针Map<TreeNode, TreeNode> parent = new HashMap<>();Queue<TreeNode> queue = new LinkedList<>();parent.put(root, null);queue.add(root);// 层序遍历建立父指针映射while (!queue.isEmpty()) {TreeNode node = queue.poll();if (node.left != null) {parent.put(node.left, node);queue.add(node.left);}if (node.right != null) {parent.put(node.right, node);queue.add(node.right);}}// 回溯p的祖先链Set<TreeNode> ancestors = new HashSet<>();while (p != null) {ancestors.add(p);p = parent.get(p);}// 查找q的祖先链中第一个公共节点while (!ancestors.contains(q)) {q = parent.get(q);}return q;}
}
复杂度分析
-
递归法(后序遍历):
- 时间复杂度:
O(n)
,每个节点访问一次。 - 空间复杂度:
O(h)
,递归栈深度(h
为树高)。
- 时间复杂度:
-
迭代法(父指针回溯):
- 时间复杂度:
O(n)
,两次线性扫描。 - 空间复杂度:
O(n)
,存储父指针和祖先集合。
- 时间复杂度:
对比总结
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
递归法 | 代码简洁,空间效率高 | 递归深度较大时可能栈溢出 | 树结构较平衡的情况 |
迭代法 | 避免递归栈溢出 | 需要额外存储父节点 | 树结构深或需要路径操作 |
238. 除自身以外数组的乘积
给你一个整数数组 nums
,返回 数组 answer
,其中 answer[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积 。
题目数据 保证 数组 nums
之中任意元素的全部前缀元素和后缀的乘积都在32 位整数范围内。
请不要使用除法,且在 O(n)
时间复杂度内完成此题。
示例 1:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
提示:
2 <= nums.length <= 105
-30 <= nums[i] <= 30
输入保证数组 answer[i] 在32 位整数范围内
进阶:
你可以在 O(1) 的额外空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为 额外空间。)
方法:前后缀乘积优化
- 前缀乘积:从左到右遍历数组,计算每个元素左侧所有元素的乘积,存入结果数组。
- 后缀乘积动态计算:从右到左遍历数组,用变量动态维护右侧乘积,并乘到结果数组中,实现原地更新。
代码实现(Java):
class Solution {public int[] productExceptSelf(int[] nums) {int n = nums.length;int[] answer = new int[n];// 计算左侧乘积answer[0] = 1;for (int i = 1; i < n; i++) {answer[i] = answer[i - 1] * nums[i - 1];}// 计算右侧乘积并相乘int right = 1;for (int i = n - 1; i >= 0; i--) {answer[i] *= right;right *= nums[i];}return answer;}
}
复杂度分析
- 时间复杂度:
O(n)
,两次遍历数组。 - 空间复杂度:
O(1)
(输出数组不计入额外空间),仅使用常数变量right
。
239. 滑动窗口最大值
给你一个整数数组 nums
,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 31 [3 -1 -3] 5 3 6 7 31 3 [-1 -3 5] 3 6 7 51 3 -1 [-3 5 3] 6 7 51 3 -1 -3 [5 3 6] 7 61 3 -1 -3 5 [3 6 7] 7
示例 2:
输入:nums = [1], k = 1
输出:[1]
提示:
1 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
1 <= k <= nums.length
方法一:双端队列
使用双端队列维护一个单调递减队列,存储可能成为窗口最大值的元素索引。遍历数组时,维护队列的单调性,并确保队列中的元素在窗口范围内。队列头部始终是当前窗口的最大值。
代码实现(Java):
public class Solution {public int[] maxSlidingWindow(int[] nums, int k) {if (nums == null || k <= 0) return new int[0];int n = nums.length;int[] result = new int[n - k + 1];Deque<Integer> deque = new LinkedList<>();for (int i = 0; i < n; i++) {// 移除超出窗口范围的元素while (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {deque.pollFirst();}// 维护单调递减特性while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {deque.pollLast();}deque.offerLast(i);// 记录窗口最大值if (i >= k - 1) {result[i - k + 1] = nums[deque.peekFirst()];}}return result;}
}
复杂度分析:
- 时间复杂度:
O(n)
,每个元素最多入队和出队一次。 - 空间复杂度:
O(k)
,队列最多存储k
个元素。
方法二:大根堆(优先队列)
使用大根堆存储元素值和索引。每次滑动窗口时,将新元素加入堆中,并移除堆顶不在当前窗口范围内的元素。堆顶元素即为当前窗口的最大值。
代码实现(Java):
public class Solution {public int[] maxSlidingWindow(int[] nums, int k) {int n = nums.length;int[] result = new int[n - k + 1];PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> b[0] - a[0]);for (int i = 0; i < n; i++) {pq.offer(new int[]{nums[i], i});// 移除窗口外的最大值while (!pq.isEmpty() && pq.peek()[1] <= i - k) {pq.poll();}// 记录结果if (i >= k - 1) {result[i - k + 1] = pq.peek()[0];}}return result;}
}
复杂度分析:
- 时间复杂度:
O(n log k)
,堆操作的时间复杂度为O(log k)
。 - 空间复杂度:
O(n)
,堆最多存储n
个元素。
方法三:分块预处理
将数组分成大小为k的块,预处理每个块的前缀最大值和后缀最大值。对于每个窗口,最大值可以通过组合相邻块的后缀和前缀最大值得到。
代码实现(Java):
public class Solution {public int[] maxSlidingWindow(int[] nums, int k) {int n = nums.length;int[] prefixMax = new int[n];int[] suffixMax = new int[n];// 计算前缀最大值for (int i = 0; i < n; i++) {if (i % k == 0) {prefixMax[i] = nums[i];} else {prefixMax[i] = Math.max(prefixMax[i - 1], nums[i]);}}// 计算后缀最大值for (int i = n - 1; i >= 0; i--) {if (i == n - 1 || (i + 1) % k == 0) {suffixMax[i] = nums[i];} else {suffixMax[i] = Math.max(suffixMax[i + 1], nums[i]);}}// 生成结果int[] result = new int[n - k + 1];for (int i = 0; i <= n - k; i++) {int j = i + k - 1;result[i] = Math.max(suffixMax[i], prefixMax[j]);}return result;}
}
复杂度分析:
- 时间复杂度:
O(n)
,预处理和结果生成各需要O(n)时间。 - 空间复杂度:
O(n)
,存储前缀和后缀最大值数组。
对比总结
- 双端队列:通过维护单调队列实现高效的最大值获取,是本题最优解。
- 大根堆:逻辑简单,但在极端情况下性能较差。
- 分块预处理:通过预处理块的最大值,实现线性时间复杂度,适合对空间不敏感的场景。
279. 完全平方数
给你一个整数 n
,返回和为 n
的完全平方数的最少数量 。
完全平方数是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
示例 1:
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
示例 2:
输入:n = 13
输出:2
解释:13 = 4 + 9
提示:
1 <= n <= 10^4
方法一:动态规划
通过构建 dp
数组,其中 dp[i]
表示组成整数 i
所需的最少完全平方数的数量。利用动态规划逐步填充 dp
数组,最终得到 dp[n]
的值。
- 初始化:
dp[0] = 0
(基础情况),其他位置初始化为最大值(如Integer.MAX_VALUE
)。 - 状态转移:对于每个数
i
,遍历所有可能的平方数j*j
,更新dp[i] = min(dp[i], dp[i - j*j] + 1)
。 - 遍历顺序:外层遍历目标值
i
,内层遍历可能的平方数j
。
代码实现(Java):
class Solution {public int numSquares(int n) {int[] dp = new int[n + 1];Arrays.fill(dp, Integer.MAX_VALUE);dp[0] = 0; // 初始条件// 遍历每个整数i,计算其最少平方数个数for (int i = 1; i <= n; i++) {// 遍历所有可能的平方数j*j(j*j <= i)for (int j = 1; j * j <= i; j++) {// 更新dp[i]为最小值dp[i] = Math.min(dp[i], dp[i - j * j] + 1);}}return dp[n];}
}
方法二:四平方和定理(数学解法)
四平方和定理 (Lagrange’s four-square theorem) 说明每个正整数均可表示为4个整数的平方和。其中,当且仅当该数满足4^k*(8m+7)
时只能表示为四平方和,其他形式都可以表示为三平方和。
- 检查是否为完全平方数:如果是,直接返回1。
- 检查是否满足形式4^k*(8m +7):若是,返回4。
- 检查是否能表示为两个平方数之和:若是,返回2。
- 其他情况返回3:根据定理,剩余情况只需3个平方数。
代码实现(Java):
class Solution {public int numSquares(int n) {// 判断是否为完全平方数if (isPerfectSquare(n)) {return 1;}// 检查是否满足4^k*(8m +7)的条件int num = n;while (num % 4 == 0) {num /= 4;}if (num % 8 == 7) {return 4;}// 检查是否可以表示为两个平方数的和for (int i = 1; i * i <= n; i++) {int j = n - i * i;if (isPerfectSquare(j)) {return 2;}}// 其他情况返回3return 3;}// 辅助函数,判断x是否为完全平方数private boolean isPerfectSquare(int x) {int s = (int) Math.sqrt(x);return s * s == x;}
}
复杂度分析
- 方法一时间复杂度:外层循环:
O(n)
,内层循环:O(√i)
,总复杂度:O(n√n)
。 - 方法二时间复杂度:
O(√n)
295. 数据流的中位数
中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
例如 arr = [2,3,4]
的中位数是 3
。
例如 arr = [2,3]
的中位数是 (2 + 3) / 2 = 2.5
。
实现 MedianFinder
类:
MedianFinder();
// 初始化 MedianFinder 对象。
void addNum(int num);
// 将数据流中的整数 num 添加到数据结构中。
double findMedian();
// 返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。
示例 1:
输入:
["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"]
[[], [1], [2], [], [3], []]
输出:
[null, null, null, 1.5, null, 2.0]
解释:
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1); // arr = [1]
medianFinder.addNum(2); // arr = [1, 2]
medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3); // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0
提示:
-10^5 <= num <= 10^5
在调用 findMedian 之前,数据结构中至少有一个元素
最多 5 * 10^4 次调用 addNum 和 findMedian
方法:双堆法
使用两个堆(大顶堆和小顶堆)分别存储数据流的较小和较大半部分,实现中位数的快速查询。
-
数据结构设计:
- 大顶堆存储较小半部分元素,堆顶为最大元素
- 小顶堆存储较大半部分元素,堆顶为最小元素
- 始终维持大顶堆元素数 ≥ 小顶堆元素数
-
添加元素流程:
- 新元素先加入大顶堆
- 将大顶堆的最大值传递给小顶堆
- 若小顶堆元素更多,返还最小值到大顶堆
-
中位数计算:
- 当元素总数为奇数时,直接返回大顶堆堆顶
- 当元素总数为偶数时,返回两堆顶平均值
代码实现(Java):
class MedianFinder {private PriorityQueue<Integer> maxHeap; // 存储较小的一半(大顶堆)private PriorityQueue<Integer> minHeap; // 存储较大的一半(小顶堆)public MedianFinder() {maxHeap = new PriorityQueue<>(Collections.reverseOrder());minHeap = new PriorityQueue<>();}public void addNum(int num) {// 1. 先加入大顶堆并传递最大值到小顶堆maxHeap.offer(num);minHeap.offer(maxHeap.poll());// 2. 平衡堆大小,保证大顶堆不小于小顶堆if (minHeap.size() > maxHeap.size()) {maxHeap.offer(minHeap.poll());}}public double findMedian() {// 根据堆大小差异返回中位数return maxHeap.size() > minHeap.size() ? maxHeap.peek() : (maxHeap.peek() + minHeap.peek()) / 2.0;}
}
复杂度分析
- 时间复杂度:
addNum()
:O(log n)
堆插入和删除操作findMedian()
:O(1)
直接访问堆顶
- 空间复杂度:
O(n)
存储所有元素
300. 最长递增子序列
给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
1 <= nums.length <= 2500
-10^4 <= nums[i] <= 10^4
进阶:
你能将算法的时间复杂度降低到 O(n log(n))
吗?
方法一:动态规划
使用动态规划数组 dp[i]
表示以 nums[i]
结尾的最长递增子序列长度。对于每个元素,遍历其之前的所有元素,若当前元素更大,则更新 dp[i]
。
代码实现(Java):
class Solution {public int lengthOfLIS(int[] nums) {int[] dp = new int[nums.length];Arrays.fill(dp, 1);int max = 1;for (int i = 1; i < nums.length; i++) {for (int j = 0; j < i; j++) {if (nums[i] > nums[j]) {dp[i] = Math.max(dp[i], dp[j] + 1);}}max = Math.max(max, dp[i]);}return max;}
}
方法二:贪心 + 二分查找
维护一个 tails
数组,tails[i]
表示长度为 i+1
的递增子序列的最小末尾元素。通过二分查找快速定位插入或替换位置。
代码实现(Java):
class Solution {public int lengthOfLIS(int[] nums) {List<Integer> tails = new ArrayList<>();for (int num : nums) {int left = 0, right = tails.size();// 二分查找第一个 >= num 的位置while (left < right) {int mid = left + (right - left) / 2;if (tails.get(mid) < num) {left = mid + 1;} else {right = mid;}}if (left == tails.size()) {tails.add(num);} else {tails.set(left, num);}}return tails.size();}
}
复杂度分析
方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
---|---|---|---|
动态规划 | O(n²) | O(n) | 简单直观,小规模数据 |
二分+贪心 | O(n log n) | O(n) | 大规模数据,效率要求高 |
博客源文件Gitee仓库:
gitee.com/richardmilos/allen-csdn-notes
(持续更新,未完待续)
相关文章:
算法刷题记录——LeetCode篇(3) [第201~300题](持续更新)
(优先整理热门100及面试150,不定期持续更新,欢迎关注) 207. 课程表 你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequ…...
navicat导出文件密码解密
文章目录 一、概念二、导出文件1、创建的数据库连接信息2、导出带密码的连接信息3、查看导出后的文件 三、Python代码解析四、参考地址 一、概念 Navicat中导出的带密码的文件后缀是.ncx结尾的,里面是xml格式的文件,存储了数据库的连接,方便…...
uniapp vue3项目定义全局变量,切换底部babar时根据条件刷新页面
前言 uniapp项目中,每个tabbar页面来回点时候,不会触发页面更新。但是有时页面上有数据发生改变需要更新模版时,就得能及时的通知到页面。如果在onshow生命周期里每次都调用异步请求更新数据,有些不合理,况且页面有时…...
Linux上的`i2c-tools`工具集的详细介绍;并利用它操作IMX6ULL的I2C控制器进而控制芯片AP3216C读取光照值和距离值
IC-Tools 工具集介绍 i2c-tools 是 Linux 下用于 IC 设备调试 的用户空间工具集(你也可以把它看成是一个库,类似于之前自己用过的触摸屏库tslib库、FreeType矢量字符库),它提供了一系列命令行工具,可以扫描、读取、写入 IC 设备,…...
## DeepSeek写射击手机小游戏
DeepSeek写射击手机小游戏 提问 根据提的要求,让DeepSeek整理的需求,进行提问,内容如下: 请生成一个包含以下功能的可运行移动端射击小游戏H5文件: 要求 可以重新开始游戏 可以暂停游戏 射击位置在底部中间ÿ…...
奇安信全流量(天眼)面试题
一、全流量设备(天眼)的部署架构 天眼系统采用旁路部署模式,通过流量镜像实现非侵入式监测,核心组件包括流量传感器、分析平台和文件威胁鉴定器,具体部署架构如下: 传感器部署 关键节点覆盖:在…...
计算机四级 - 数据库原理(操作系统部分)- 第2章「操作系统运行机制」
系统调用是应用程序请求操作系统核心完成某一特定功能的一种过程调用,与一般调用的最大区别就是调用程序运行在用户态,而被调用程序则运行在系统态寄存器类型: 用户不可见寄存器:程序计数器、指令寄存器、程序状态字(P…...
【css酷炫效果】纯CSS实现虫洞穿越效果
【css酷炫效果】纯CSS实现穿越效果 缘创作背景html结构css样式完整代码基础版进阶版(虫洞穿越) 效果图 想直接拿走的老板,链接放在这里:https://download.csdn.net/download/u011561335/90491973 缘 创作随缘,不定时…...
火山引擎(豆包大模型)(抖音平台)之火山方舟的Prompt的使用测试
前言 在大模型的使用过程当中,Prompt的使用非常的关键。原来,我对Prompt的理解不深,觉得Prompt的产生并不是很有必要。但是,自从使用了火山方舟中的“Prompt优解”之后,感受加深了,觉得Prompt是我们和大模型…...
多线程(四)----线程安全
线程安全问题的万恶之源就是多线程的抢占式执行所带来的随机性. 有了多线程, 此时抢占式执行下, 代码执行的顺序, 会出现更多的变数, 代码执行顺序的可能性就从一种情况变成了无数种情况. 只要有一种情况使得代码结果不正确, 都是视为bug, 线程不安全. 有线程安全的代码 以下…...
跨系统投屏:Realme手机(远程)投屏到Linux系统的简单方法
家里长辈年纪上来了,有点老花眼,平常看手机总是觉得字体不够大,还一个劲儿地将手机拿很远。其实那台手机的字体已经调到最大了。 为了让长辈刷手机的时候可以轻松快乐一点,我们帮他将手机投屏到电脑上。毕竟电脑屏幕比手机大多了&…...
【eNSP基础使用教程-1】
座右铭: 纵有疾风起,人生不言弃。 文章目录 前言一、更改设备名称指令1、双击路由器进入2、 进入系统视图3、更改设备名称为R14、使用同样的办法修改路由器R2、R3 二、配置路由物理接口的IP 地址1、查看R1路由器当前接口IP 地址配置与路由表2、查看路由器上的路由表…...
android开发:组件事件汇总
在 Android 开发中,Java 文件中有许多组件事件可以处理用户交互。以下是一些常见的组件事件及其用途和示例: 1. 点击事件 (Click) 用于处理用户点击控件的操作。 示例代码: Button button findViewById(R.id.button); button.setOnClickL…...
C++|向函数传递对象
在 C 里,对象作为函数的参数和返回值,有值传递、指针传递和引用传递这三种传递方式,下面为你详细介绍。 1.值传递 在值传递时,把实参对象的值复制给形参对象,函数会接收实参的一个副本,而非实参本身。函数…...
网络爬虫【爬虫库urllib】
我叫不三不四,很高兴见到大家,欢迎一起学习交流和进步 今天来讲一讲爬虫 urllib介绍 Urllib是Python自带的标准库,无须安装,直接引用即可。 Urllib是一个收集几个模块来使用URL的软件包,大致具备以下功能。 ● urlli…...
【一起来学kubernetes】17、Configmap使用详解
前言概述核心特性创建 ConfigMap使用 ConfigMap1. **环境变量**2. **Volume 挂载**3. **命令行参数** 更新与热重载Docker容器中Java服务使用Configmap**一、通过环境变量注入****步骤说明****示例配置** **二、通过 Volume 挂载配置文件****步骤说明****示例配置** **三、动态…...
QT程序双击可执行文件运行方法
1、qt编译选择release模式 在pro文件添加:QMAKE_LFLAGS -no-pie 2、cmake编译qt界面程序 在CMakeLists.txt文件中添加: set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -no-pie") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -no-pie")注意 …...
【css酷炫效果】实现鱼群游动动态效果
【css酷炫效果】实现小鱼游动动态效果 缘创作背景css代码创建div容器引入jquery引入鱼群js完整代码效果图成品资源下载链接:点击下载 缘 在开发系统功能的时候,无意间看到了小鱼游动特效,感觉很有意思,就在网上找了相关教程,分享给大家。 创作背景 刚看到csdn出活动了…...
【GNN】GAT
消息传递 层数越多,聚合更多的消息...
Prims region.Views 为null
原因: 导航未完成或异步问题 解决方式:使用回调确认导航完成后再操作视图 _regionManager.RequestNavigate("MonitorRegion", "MonitorView", nps, navigationResult > {if (navigationResult.Result true){var region _regio…...
在windows10系统上安装docker,然后在容器中运行GPU版本的Pytorch,并使用vscode连接该容器
一 . 安装Docker Desktop 首先打开网址https://docs.docker.com/desktop/install/windows-install/ 下载完后,双击下面的exe文件进行安装,默认情况下,Docker Desktop 安装在C:\Program Files\Docker\Docker 出现提示时,请确保…...
WPS 搭配 Zotero 插件使用
安装Zotero后,Word自动引入了插件,但WPS却没有,做为WPS的重度用户,这是不行的。 解决方案: 1.找到 Zotero.dotm 一般在安装目录下, 2.然后复制到WPS的startup下 我的目录是:C:\Users\lianq…...
卷积神经网络 - 卷积层(具体例子)
为了更一步学习卷积神经网络之卷积层,本文我们来通过几个个例子来加深理解。 一、灰度图像和彩色图像的关于特征映射的例子 下面我们通过2个例子来形象说明卷积层中“特征映射”的概念,一个针对灰度图像,一个针对彩色图像。 例子 1&#x…...
新造车不再比拼排名,恰是曲终人散时,剩者为王
据称新能源汽车周销量不再发布,这可能也预示着新造车终于到了给出答案的时候了,新造车企业前三强已基本确立,其余那些落后的车企已很难有突围的机会,而特斯拉无疑是其中的最大赢家。 3月份第一周的数据显示,销量最高的…...
学有所得-Deepin linux操作系统在安装nvidia显卡驱动后的问题修复
目标: 装有deepin V20.9的移动硬盘在系统启动后无法进入图形化界面,修复系统。 背景: 为了方便随时随地开发研究,又不破坏笔记本电脑原装的正版操作系统,在一个朗科(容量50&…...
【QT:网络编程】
网络编程的本质就是在编写应用层代码。需要传输层支持。而传输层的协议有UDP、TCP等 使用QT网络编程的API,需要在.pro文件中添加network模块,而QT中的控件和其他内容都是包含在QtCore模块中的(默认添加) QT为什么要划分模块&…...
基于srpingboot高校智慧校园教学管理服务平台的设计与实现(源码+文档+部署讲解)
技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…...
分布式事务3PC解决了2PC哪些问题?
三阶段提交(3PC,Three-Phase Commit) 是对 二阶段提交(2PC,Two-Phase Commit) 的改进,旨在解决 2PC 的一些固有缺陷,特别是在分布式系统中的容错性和性能问题。以下是 3PC 比 2PC 更好的原因及其优势的详细分析: 1. 二阶段提交(2PC)的问题 2PC 是一种经典的分布式事…...
Dify 使用 - 创建 翻译 工作流
文章目录 1、选择 模板2、设置 和 基本使用3、运行应用 1、选择 模板 2、设置 和 基本使用 翻译模板 自带了系统提示词,你也可以修改 3、运行应用 右上角 点击 发布 – 更新,运行应用,就可以在新的对话界面中使用此功能 2025-03-18&#x…...
集成学习(上):Bagging集成方法
一、什么是集成学习? 在机器学习的世界里,没有哪个模型是完美无缺的。就像古希腊神话中的"盲人摸象",单个模型往往只能捕捉到数据特征的某个侧面。但当我们把多个模型的智慧集合起来,就能像拼图一样还原出完整的真相&a…...
c盘清理宝藏小工具
引言 在数字化时代,电脑的存储空间和系统性能直接影响着我们的工作效率和用户体验。C盘作为系统盘,常常因为文件堆积、缓存冗余等问题变得臃肿不堪,导致电脑运行缓慢。为了解决这一问题,我最近试用了一款名为“小番茄C盘清理”的…...
QT多媒体播放器类:QMediaPlayer
QMediaPlayer 是 Qt Multimedia 模块中的核心类,用于播放音频和视频媒体文件。它支持本地文件、网络流媒体以及实时数据源,具备播放控制、状态管理、元数据访问等功能。QMediaPlayer的基本用法可能包括设置媒体源、控制播放(播放、暂停、停止…...
Java动态代理模式深度解析
1. 动态代理基础 1.1 核心组件 Proxy 类:动态生成代理对象的工厂类,核心方法为 newProxyInstance()。 InvocationHandler 接口:代理逻辑的处理器,所有方法调用会转发到其 invoke() 方法。 1.2 实现步骤 定义接口:代…...
【WRF模拟】垂直层设置/与观测数据对比
【WRF模拟】垂直层设置/与观测数据对比 WRF 中 有关垂直层的namelist变量1. 主要垂直层设置参数2. 详细解释3. 典型设置示例WRF 输出的垂直剖面数据与观测数据进行比较WRF 采用 地形跟随坐标(terrain-following coordinate)WRF 输出的垂直剖面数据与观测数据进行比较参考WRF …...
植物知识分享论坛毕设
1.这四个文件直接是什么关系?各自都是什么作用?他们之间是如何联系的? 关系与联系 UserController.java 负责接收外部请求,调用 UserService.java 里的方法来处理业务, 而 UserService.java 又会调用 UserMapper.jav…...
可视化图解算法:链表中倒数(最后)k个结点
1. 题目 描述 输入一个长度为 n 的链表,设链表中的元素的值为ai ,返回该链表中倒数第k个节点。 如果该链表长度小于k,请返回一个长度为 0 的链表。 数据范围:0≤n≤105,0 ≤ai≤109,0 ≤k≤109 要求&am…...
qt下载和安装教程国内源下载地址
qt不断在更新中,目前qt6日渐成熟,先前我们到官方下载或者国内镜像直接可以下载到exe文件安装,但是最近几年qt官方似乎在逐渐关闭旧版本下载通道,列为不推荐下载。但是qt5以其广泛使用和稳定性,以及积累大量代码使得qt5…...
html5表格实战-跨行跨列
效果如图 代码如图...
使用OBS进行webRTC推流参考
参考腾讯云官方文档: 云直播 OBS WebRTC 推流_腾讯云 说明非常详细,分为通过WHIP和OBS插件的形式进行推流。 注意:通过OBS插件的形式进行推流需要使用较低的版本,文档里有说明,需要仔细阅读。...
Rockchip --- 图像时延优化
通过配置wait-line,即图像采集多少行后提前输出buffer给ISP,而无需等待图像全部采集完毕。一般设置为图像采集一半后提前输出buffer给ISP (一)VICAP提前输出 Video Input CAPture是用于图像采集和处理的子系统 1. 通过dts配置 …...
微软 LIDA 库:基于大模型的自动化数据分析与可视化
微软 LIDA 库:基于大模型的自动化数据分析与可视化 一、核心架构与 LLM 交互流程 #mermaid-svg-UzSwZNKPlgrJUpej {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-UzSwZNKPlgrJUpej .error-icon{fill:#5…...
java-正则表达式-集合-泛型
正则表达式 正则表达式到底是什么东西? 在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。 http://tool.oschina.net/r…...
力扣刷题994. 腐烂的橘子
994. 腐烂的橘子 - 力扣(LeetCode) 使用bfs,先遍历所有的橘子,统计所有的橘子数量,然后把腐烂的橘子放入队列当中,然后进行bfs遍历,套用bfs的模版,但是每一次出队的橘子(…...
Kubernetes的Service详解
一、Service介绍 在 kubernetes 中, pod 是应用程序的载体,我们可以通过 pod 的 ip 来访问应用程序,但是 pod 的 ip 地址不是固定的,这也就意味着不方便直接采用pod 的 ip 对服务进行访问。 为了解决这个问题,kuberne…...
Linux目录理解
前言 最近在复习linux,发现有些目录总是忘记内容,发现有些还是得从原义和实际例子去理解会记忆深刻些。以下是个人的一些理解 Linux目录 常见的Linux下的目录如下: 1. 根目录 / (Root Directory) 英文含义:/ 是文件系统的根…...
vue中js简单创建一个事件中心/中间件/eventBus
vue中js简单创建一个事件中心/中间件/eventBus 目录结构如下: eventBus.js class eventBus {constructor() {this.events {};}// 监听事件on(event, callback) {if (!this.events[event]) {this.events[event] [];}this.events[event].push(callback);}// 发射…...
1~2 课程简介+ESP32-IDF环境搭建(虚拟机Linux环境下)
哔站“宸芯IOT”视频链接 一、课程内容介绍 1.什么是ESP32 ESP32是集成2.4GHz Wi-Fi和蓝牙双模的单芯片方案,具有超高的射频性能、稳定性、通用性和可靠性,以及超低的功耗,满足不同的功耗需求,适用于各种应用场景。ESP32是ESP8…...
Linux系统移植篇(十一)Linux 内核启动流程
要分析 Linux 启动流程,同样需要先编译一下 Linux 源码,因为有很多文件是需要编译才 会生成的。首先分析 Linux 内核的连接脚本文件 arch/arm/kernel/vmlinux.lds,通过链接脚本可以 找到 Linux 内核的第一行程序是从哪里执行的。vmlinux.lds …...
React19源码系列之Hooks(useId)
useId的介绍 https://zh-hans.react.dev/reference/react/useId useId 是 React 18 引入的一个新 Hook,主要用于生成全局唯一的 ID。在开发中,我们经常需要为元素(如表单元素、模态框等)生成唯一 ID,以便在 JavaScri…...
深度学习-149-langchain之如何不使用with_structured_output()从模型中返回结构化数据
文章目录 1 不使用with_structured_output()方法1.1 问题背景1.2 输出解析器1.3 远程deepseek大模型API2 基于提示词2.1 直接使用提示词2.2 少样本提示词3 直接提示和解析模型输出3.1 使用PydanticOutputParser3.1.1 构建解析器3.1.2 构建提示模板3.1.3 调用大模型3.1.4 调用链…...