算法刷题记录——LeetCode篇(2) [第101~200题](持续更新)
(优先整理热门100及面试150,不定期持续更新,欢迎关注)
101. 对称二叉树
给你一个二叉树的根节点 root
, 检查它是否轴对称。
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
提示:
树中节点数目在范围 [1, 1000] 内
-100 <= Node.val <= 100
进阶:
你可以运用递归和迭代两种方法解决这个问题吗?
方法一:递归法
- 定义辅助函数
isMirror
,比较两棵树是否镜像对称。 - 镜像条件:根节点值相同,左子树与右子树镜像对称,右子树与左子树镜像对称。
代码实现(Java):
class Solution {public boolean isSymmetric(TreeNode root) {if (root == null) return true;return isMirror(root.left, root.right);}private boolean isMirror(TreeNode left, TreeNode right) {if (left == null && right == null) return true;if (left == null || right == null) return false;return (left.val == right.val) && isMirror(left.left, right.right) && isMirror(left.right, right.left);}
}
方法二:迭代法
- 使用队列成对存储需要比较的节点(左子树左节点与右子树右节点,左子树右节点与右子树左节点)。
- 成对取出节点比较值,若结构或值不匹配则直接返回
false
。
代码实现(Java):
class Solution {public boolean isSymmetric(TreeNode root) {if (root == null) return true;Queue<TreeNode> queue = new LinkedList<>();queue.offer(root.left);queue.offer(root.right);while (!queue.isEmpty()) {TreeNode left = queue.poll();TreeNode right = queue.poll();if (left == null && right == null) continue;if (left == null || right == null || left.val != right.val) return false;queue.offer(left.left);queue.offer(right.right);queue.offer(left.right);queue.offer(right.left);}return true;}
}
复杂度分析
-
递归法:
- 时间复杂度:
O(n)
,每个节点访问一次。 - 空间复杂度:
O(h)
,递归栈深度与树高度相关。
- 时间复杂度:
-
迭代法:
- 时间复杂度:
O(n)
,每个节点访问一次。 - 空间复杂度:
O(n)
,队列最多存储一层节点。
- 时间复杂度:
102. 二叉树的层序遍历
给你二叉树的根节点 root
,返回其节点值的 层序遍历 。(即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
提示:
树中节点数目在范围 [0, 2000] 内
-1000 <= Node.val <= 1000
方法一:迭代法(队列实现)
- 使用队列按层存储节点,每次处理一层所有节点。
- 将当前层节点的子节点按顺序入队,保证层级顺序。
代码实现(Java):
class Solution {public List<List<Integer>> levelOrder(TreeNode root) {List<List<Integer>> result = new ArrayList<>();if (root == null) return result;Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {int levelSize = queue.size();List<Integer> currentLevel = new ArrayList<>();for (int i = 0; i < levelSize; i++) {TreeNode node = queue.poll();currentLevel.add(node.val);if (node.left != null) queue.offer(node.left);if (node.right != null) queue.offer(node.right);}result.add(currentLevel);}return result;}
}
方法二:递归法(DFS)
- 通过递归深度优先遍历,记录当前节点层级。
- 根据层级动态扩展结果列表,保证节点值按层收集。
代码实现(Java):
class Solution {public List<List<Integer>> levelOrder(TreeNode root) {List<List<Integer>> result = new ArrayList<>();traverse(root, 0, result);return result;}private void traverse(TreeNode node, int level, List<List<Integer>> result) {if (node == null) return;if (result.size() == level) {result.add(new ArrayList<>());}result.get(level).add(node.val);traverse(node.left, level + 1, result);traverse(node.right, level + 1, result);}
}
复杂度分析
-
迭代法:
- 时间复杂度:
O(n)
,每个节点进出队列一次。 - 空间复杂度:
O(n)
,队列存储最多n/2
个节点(完全二叉树)。
- 时间复杂度:
-
递归法:
- 时间复杂度:
O(n)
,每个节点访问一次。 - 空间复杂度:
O(h)
,递归栈深度与树高相关,最坏情况O(n)
。
- 时间复杂度:
104. 二叉树的最大深度
给定一个二叉树 root
,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:3
示例 2:
输入:root = [1,null,2]
输出:2
提示:
树中节点的数量在 [0, 10^4] 区间内
-100 <= Node.val <= 100
方法一:递归法
自顶向下分解问题,当前树的最大深度等于左右子树最大深度的较大值加一。
代码实现(Java):
class Solution {public int maxDepth(TreeNode root) {if (root == null) {return 0;}int leftDepth = maxDepth(root.left);int rightDepth = maxDepth(root.right);return Math.max(leftDepth, rightDepth) + 1;}
}
方法二:广度优先搜索(BFS)
逐层遍历树,每遍历完一层深度加一,最终深度即为最大深度。
代码实现(Java):
class Solution {public int maxDepth(TreeNode root) {if (root == null) return 0;Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);int depth = 0;while (!queue.isEmpty()) {int size = queue.size();while (size-- > 0) {TreeNode node = queue.poll();if (node.left != null) queue.offer(node.left);if (node.right != null) queue.offer(node.right);}depth++;}return depth;}
}
方法三:深度优先搜索(DFS)迭代法
显式使用栈保存节点和当前深度,通过前序遍历更新最大深度。
代码实现(Java):
class Solution {public int maxDepth(TreeNode root) {if (root == null) return 0;Deque<TreeNode> stack = new LinkedList<>();Deque<Integer> depthStack = new LinkedList<>();stack.push(root);depthStack.push(1);int max = 0;while (!stack.isEmpty()) {TreeNode node = stack.pop();int currentDepth = depthStack.pop();max = Math.max(max, currentDepth);if (node.right != null) {stack.push(node.right);depthStack.push(currentDepth + 1);}if (node.left != null) {stack.push(node.left);depthStack.push(currentDepth + 1);}}return max;}
}
105. 从前序与中序遍历序列构造二叉树
给定两个整数数组 preorder
和 inorder
,其中 preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
示例 1:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
示例 2:
输入: preorder = [-1], inorder = [-1]
输出: [-1]
提示:
1 <= preorder.length <= 3000
inorder.length == preorder.length
-3000 <= preorder[i], inorder[i] <= 3000
preorder 和 inorder 均无重复元素
inorder 均出现在 preorder
preorder 保证 为二叉树的前序遍历序列
inorder 保证 为二叉树的中序遍历序列
方法:递归分治+哈希优化
-
核心思想:
- 前序遍历首元素为根节点;
- 中序遍历中根节点左侧为左子树,右侧为右子树;
- 通过递归分治思想逐层构建子树。
-
关键步骤:
- 哈希表加速:预存中序遍历值到索引的映射,实现
O(1)
时间定位根节点; - 子树分割:
- 左子树节点数 =
根节点中序位置
-中序起始位置
; - 前序左子树范围:
preStart+1
至preStart+leftSize
; - 前序右子树范围:
preStart+leftSize+1
至preEnd
。
- 左子树节点数 =
- 哈希表加速:预存中序遍历值到索引的映射,实现
-
边界处理:
- 空数组直接返回
null
; - 单节点直接返回根节点;
- 完全左/右斜树通过
leftSize
正确分割区间。
- 空数组直接返回
代码实现(Java):
class Solution {private Map<Integer, Integer> indexMap;private int[] preorder;private int[] inorder;public TreeNode buildTree(int[] preorder, int[] inorder) {this.preorder = preorder;this.inorder = inorder;indexMap = new HashMap<>();for (int i = 0; i < inorder.length; i++) {indexMap.put(inorder[i], i);}return buildSubtree(0, preorder.length - 1, 0, inorder.length - 1);}private TreeNode buildSubtree(int preStart, int preEnd, int inStart, int inEnd) {if (preStart > preEnd) return null;// 前序首元素为当前根节点int rootVal = preorder[preStart];TreeNode root = new TreeNode(rootVal);// 获取中序中根节点位置int rootInIndex = indexMap.get(rootVal);int leftSize = rootInIndex - inStart; // 左子树节点数// 递归构建左右子树root.left = buildSubtree(preStart + 1, preStart + leftSize, inStart, rootInIndex - 1);root.right = buildSubtree(preStart + leftSize + 1, preEnd, rootInIndex + 1, inEnd);return root;}
}
复杂度分析
- 时间复杂度:
O(n)
,每个节点处理一次; - 空间复杂度:
O(n)
,哈希表存储n
个元素,递归栈深度平均O(logn)
。
108. 将有序数组转换为二叉搜索树
给你一个整数数组 nums
,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。
示例 1:
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:
示例 2:
输入:nums = [1,3]
输出:[3,1]
解释:[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。
提示:
1 <= nums.length <= 10^4
-10^4 <= nums[i] <= 10^4
nums 按严格递增顺序排列
方法一:递归法(中序遍历)
- 选择中间节点:每次取有序数组的中间元素作为根节点。当数组长度为偶数时,可以选择中间偏左或偏右的节点;
- 递归构建子树:将中间元素左侧的子数组构建为左子树,右侧的子数组构建为右子树;
- 树平衡性保证:由于每次均匀分割数组,左右子树节点数差不超过1,自然形成平衡二叉搜索树。
代码实现(Java):
class Solution {public TreeNode sortedArrayToBST(int[] nums) {return buildBST(nums, 0, nums.length - 1);}private TreeNode buildBST(int[] nums, int left, int right) {if (left > right) return null;// 计算中间索引(防止整数溢出)int mid = left + (right - left) / 2;// 选择中间偏右的节点:mid = left + (right - left + 1) / 2;TreeNode root = new TreeNode(nums[mid]);root.left = buildBST(nums, left, mid - 1);root.right = buildBST(nums, mid + 1, right);return root;}
}
复杂度分析
- 时间复杂度:
O(n)
,每个节点被访问一次。 - 空间复杂度:
O(logn)
,递归栈深度与树高相关。此方法保证平衡,故树高保持logn
。
114. 二叉树展开为链表
给你二叉树的根结点 root
,请你将它展开为一个单链表:
展开后的单链表应该同样使用 TreeNode
,其中 right
子指针指向链表中下一个结点,而左子指针始终为 null
。
展开后的单链表应该与二叉树 先序遍历 顺序相同。
示例 1:
输入:root = [1,2,5,3,4,null,6]
输出:[1,null,2,null,3,null,4,null,5,null,6]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [0]
输出:[0]
提示:
树中结点数在范围 [0, 2000] 内
-100 <= Node.val <= 100
进阶:
你可以使用原地算法(O(1)
额外空间)展开这棵树吗?
方法一:迭代先序遍历(显式栈)
利用栈模拟先序遍历,维护前驱节点prev,实时修改指针建立链表。
- 核心思想:利用栈按「根→右→左」顺序压入节点,确保弹出顺序为「根→左→右」。
- 指针调整:维护前驱节点
prev
,每次将prev
的right
指向当前节点,并清空left
指针。 - 空间优化:栈的深度为树高,平均空间复杂度为
O(log n)
。
代码实现(Java):
public class Solution {public void flatten(TreeNode root) {if (root == null) return;Stack<TreeNode> stack = new Stack<>();stack.push(root);TreeNode prev = null;while (!stack.isEmpty()) {TreeNode current = stack.pop();// 将前驱节点的right指向当前节点,left置空if (prev != null) {prev.right = current;prev.left = null;}prev = current;// 先压入右子节点,再压入左子节点(栈的LIFO特性)if (current.right != null) stack.push(current.right);if (current.left != null) stack.push(current.left);}}
}
方法二:递归后序遍历(连接左右子树)
递归处理左右子树,将左子树连接到右子树之前,并更新末尾节点。
- 核心思想:将左子树末尾连接到右子树头部,再让根节点指向左子树。
- 末尾处理:返回展开后的最后一个节点,避免每次遍历链表末尾,时间复杂度优化至
O(n)
。 - 逻辑简化:通过后序遍历自底向上处理,确保左子树完全展开后再处理根节点。
代码实现(Java):
public class Solution {public void flatten(TreeNode root) {flattenHelper(root);}private TreeNode flattenHelper(TreeNode node) {if (node == null) return null;// 递归处理左右子树,获取展开后的末尾节点TreeNode leftTail = flattenHelper(node.left);TreeNode rightTail = flattenHelper(node.right);// 将左子树插入到当前节点与右子树之间if (node.left != null) {leftTail.right = node.right;node.right = node.left;node.left = null;}// 返回当前子树展开后的最后一个节点return (rightTail != null) ? rightTail : (leftTail != null) ? leftTail : node;}
}
复杂度分析
- 时间复杂度:两种方法均为
O(n)
,每个节点被访问一次。 - 空间复杂度:
- 迭代法:
O(h)
,h为树高,最坏情况(链表结构)为O(n)
。 - 递归法:
O(h)
,递归栈深度为树高。
- 迭代法:
对比总结
- 迭代法:适合大规模数据或树结构较深时,避免递归栈溢出。
- 递归法:代码简洁,适合对代码可读性要求高,且无栈溢出风险的场景。
124. 二叉树中的最大路径和
二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root
,返回其 最大路径和 。
示例 1:
输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6
示例 2:
输入:root = [-10,9,20,null,null,15,7]
输出:42
解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42
提示:
树中节点数目范围是 [1, 3*10^4]
-1000 <= Node.val <= 1000
方法:递归法(后序遍历)
通过后序遍历计算每个节点的最大贡献值,并更新全局最大路径和。
- 每个节点计算其左右子树的最大贡献值(若贡献为负则取0)。
- 当前节点的总路径和为
自身值 + 左贡献 + 右贡献
,更新全局最大值。 - 返回当前节点能为父节点提供的单边最大贡献(即
自身值 + max(左贡献, 右贡献)
)。
代码实现(Java):
class Solution {private int maxSum = Integer.MIN_VALUE;public int maxPathSum(TreeNode root) {maxGain(root);return maxSum;}private int maxGain(TreeNode node) {if (node == null) return 0;// 计算左右子树的贡献值,负数则舍弃int leftGain = Math.max(maxGain(node.left), 0);int rightGain = Math.max(maxGain(node.right), 0);// 当前节点作为路径中间节点的总路径和int currentPathSum = node.val + leftGain + rightGain;maxSum = Math.max(maxSum, currentPathSum);// 返回当前节点能提供的最大单边贡献(给父节点使用)return node.val + Math.max(leftGain, rightGain);}
}
复杂度分析
- 时间复杂度:
O(n)
,所有节点仅访问一次。 - 空间复杂度:
O(h)
,递归栈深度(h
为树的高度,最坏情况下为O(n)
)。
131. 分割回文串
给你一个字符串 s
,请你将 s
分割成一些 子串,使每个子串都是 回文串 。返回 s
所有可能的分割方案。
示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a"
输出:[["a"]]
提示:
1 <= s.length <= 16
s 仅由小写英文字母组成
方法:动态规划预处理 + 回溯法
通过动态规划预处理所有回文子串信息,再利用回溯法生成所有可能的分割方案,有效避免重复计算。
- 动态规划预处理:构建二维数组
dp[i][j]
,表示子串s[i...j]
是否为回文。通过从后向前遍历,确保计算dp[i][j]
时dp[i+1][j-1]
已确定。 - 回溯法生成方案:从起始位置
start
开始,枚举所有可能的结束位置end
。若s[start...end]
是回文,则将其加入路径,并递归处理剩余子串s[end+1...]
,回溯时移除最后添加的子串。
代码实现(Java):
class Solution {public List<List<String>> partition(String s) {List<List<String>> res = new ArrayList<>();if (s == null || s.isEmpty()) return res;int n = s.length();boolean[][] dp = new boolean[n][n];char[] arr = s.toCharArray();// 预处理回文判断矩阵for (int i = n - 1; i >= 0; i--) {for (int j = i; j < n; j++) {if (i == j) {dp[i][j] = true;} else if (arr[i] == arr[j]) {dp[i][j] = (j - i == 1) || dp[i + 1][j - 1];}}}backtrack(s, 0, new ArrayList<>(), res, dp);return res;}private void backtrack(String s, int start, List<String> path, List<List<String>> res, boolean[][] dp) {if (start == s.length()) {res.add(new ArrayList<>(path));return;}for (int end = start; end < s.length(); end++) {if (dp[start][end]) {path.add(s.substring(start, end + 1));backtrack(s, end + 1, path, res, dp);path.remove(path.size() - 1);}}}
}
复杂度分析
时间复杂度: 预处理阶段 O(n²)
,回溯阶段最坏情况 O(n·2ⁿ)
;综合时间复杂度为 O(n·2ⁿ)
,其中 n
为字符串长度。
空间复杂度: 预处理矩阵 O(n²)
,递归栈深度 O(n)
;综合空间复杂度 O(n²)
。
138. 随机链表的复制
给你一个长度为 n
的链表,每个节点包含一个额外增加的随机指针 random
,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n
个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next
指针和 random
指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X
和 Y
两个节点,其中 X.random
--> Y
。那么在复制链表中对应的两个节点 x
和 y
,同样有 x.random
--> y
。
返回复制链表的头节点。
用一个由 n
个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index]
表示:
val
:一个表示 Node.val
的整数。
random_index
:随机指针指向的节点索引(范围从 0
到 n-1
);如果不指向任何节点,则为 null
。
你的代码 只 接受原链表的头节点 head
作为传入参数。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
提示:
0 <= n <= 1000
-10^4 <= Node.val <= 10^4
Node.random 为 null 或指向链表中的节点
方法一:哈希表映射法
通过哈希表建立原节点与复制节点的映射关系。第一次遍历创建所有新节点,第二次遍历设置指针,通过哈希表快速定位对应的复制节点。
- 创建节点映射:遍历原链表,创建每个节点的复制节点并存入哈希表。
- 设置指针:再次遍历原链表,通过哈希表获取复制节点,设置其
next
和random
指针。
代码实现(Java):
class Solution {public Node copyRandomList(Node head) {if (head == null) return null;Map<Node, Node> map = new HashMap<>();Node curr = head;// 创建所有新节点while (curr != null) {map.put(curr, new Node(curr.val));curr = curr.next;}// 设置指针curr = head;while (curr != null) {Node clone = map.get(curr);clone.next = map.get(curr.next);clone.random = map.get(curr.random);curr = curr.next;}return map.get(head);}
}
复杂度分析
- 时间复杂度:
O(n)
,两次线性遍历。 - 空间复杂度:
O(n)
,哈希表存储所有节点映射。
方法二:原地复制拆分法
不借助额外空间,通过三次遍历完成复制。第一次复制节点并插入原链表,第二次设置random指针,第三次拆分恢复原链表并构建新链表。
- 复制插入节点:将每个复制节点插入原节点之后,形成交替链表。
- 设置random指针:利用原节点的位置关系,设置复制节点的random。
- 拆分链表:恢复原链表的next,同时构建复制链表。
代码实现(Java):
class Solution {public Node copyRandomList(Node head) {if (head == null) return null;// 插入复制节点Node curr = head;while (curr != null) {Node clone = new Node(curr.val);clone.next = curr.next;curr.next = clone;curr = clone.next;}// 设置random指针curr = head;while (curr != null) {Node clone = curr.next;clone.random = (curr.random != null) ? curr.random.next : null;curr = clone.next;}// 拆分链表Node newHead = head.next;curr = head;while (curr != null) {Node clone = curr.next;curr.next = clone.next; // 恢复原链表if (clone.next != null) {clone.next = clone.next.next; // 构建新链表}curr = curr.next;}return newHead;}
}
复杂度分析
- 时间复杂度:
O(n)
,三次线性遍历。 - 空间复杂度:
O(1)
,仅使用常量额外空间。
对比总结
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
哈希表映射法 | 逻辑清晰,实现简单 | 需要O(n)额外空间 | 常规场景,快速实现 |
原地复制拆分法 | 空间效率高,无需额外存储 | 修改原链表结构 | 空间敏感,允许修改原链表 |
139. 单词拆分
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s
则返回 true
。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 “leetcode” 可以由 “leet” 和 “code” 拼接成。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 “applepenapple” 可以由 “apple” “pen” “apple” 拼接成。
注意,你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
提示:
1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s 和 wordDict[i] 仅由小写英文字母组成
wordDict 中的所有字符串互不相同
方法:动态规划
使用动态规划数组 dp[i]
表示字符串前 i
个字符能否被字典中的单词拆分。通过遍历字符串的每个位置,并检查所有可能的子串是否存在于字典中,逐步填充 dp
数组。
- 字典预处理:将字典存入
HashSet
实现 O(1) 时间查询,同时记录字典中最长单词长度maxLen
,减少不必要的子串检查。 - 动态规划填充:
- 初始化:
dp[0] = true
表示空字符串可拆分。 - 遍历每个位置
i
:从1
到n
(字符串长度),检查所有可能的拆分点j
。 - 剪枝优化:
start = Math.max(0, i - maxLen)
确保仅检查长度不超过maxLen
的子串,避免全量遍历。 - 状态转移:若
dp[j]
为true
且子串s.substring(j, i)
存在于字典中,则dp[i] = true
。
- 初始化:
代码实现(Java):
class Solution {public boolean wordBreak(String s, List<String> wordDict) {Set<String> wordSet = new HashSet<>(wordDict);int maxLen = 0;for (String word : wordDict) {maxLen = Math.max(maxLen, word.length());}int n = s.length();boolean[] dp = new boolean[n + 1];dp[0] = true; // 空字符串默认可以拆分for (int i = 1; i <= n; i++) {// 仅检查长度不超过 maxLen 的子串int start = Math.max(0, i - maxLen);for (int j = start; j < i; j++) {if (dp[j] && wordSet.contains(s.substring(j, i))) {dp[i] = true;break;}}}return dp[n];}
}
复杂度分析
- 时间复杂度:预处理字典:
O(M)
,其中M
为字典总字符数;动态规划循环:O(n * maxLen)
;总时间复杂度:O(M + n * maxLen)
,其中n
是字符串长度,maxLen
是字典中最长单词长度。 - 空间复杂度:
HashSet
存储字典:O(K)
;dp
数组:O(n)
;总空间复杂度:O(K + n)
,K
为字典中不同单词的个数。
141. 环形链表
给你一个链表的头节点 head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0
开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true
。 否则,返回 false
。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
链表中节点的数目范围是 [0, 104]
-10^5 <= Node.val <= 10^5
pos 为 -1 或者链表中的一个有效索引
进阶:
你能用 O(1)
(常量)内存解决此问题吗?
方法一:快慢指针(Floyd判圈算法)
使用两个指针,一个快指针每次移动两步,一个慢指针每次移动一步。如果链表中有环,快指针最终会追上慢指针;如果快指针到达链表末尾(遇到null
),则说明链表无环。
- 初始化快慢指针都指向头节点。
- 快指针每次移动两步,慢指针每次移动一步。
- 如果快慢指针相遇,说明有环;若快指针遍历到链表末尾,说明无环。
代码实现(Java):
public class Solution {public boolean hasCycle(ListNode head) {ListNode slow = head;ListNode fast = head;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;if (slow == fast) {return true;}}return false;}
}
方法二:哈希表
遍历链表,用哈希表记录已访问过的节点。如果遇到重复节点,说明存在环;否则遍历完成无环。
- 创建哈希表存储访问过的节点。
- 遍历链表,检查当前节点是否已存在于哈希表中。
- 存在则返回true,否则将节点加入哈希表。
- 遍历结束返回false。
代码实现(Java):
public class Solution {public boolean hasCycle(ListNode head) {Set<ListNode> visited = new HashSet<>();while (head != null) {if (visited.contains(head)) {return true;}visited.add(head);head = head.next;}return false;}
}
复杂度分析
- 快慢指针法:时间复杂度
O(n)
,空间复杂度O(1)
。通过双指针的追赶机制高效检测环。 - 哈希表法:时间复杂度
O(n)
,空间复杂度O(n)
。利用哈希表的唯一性记录访问过的节点,实现简单但空间占用较高。
142. 环形链表 II
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0
开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
提示:
链表中节点的数目范围在范围 [0, 10^4] 内
-10^5 <= Node.val <= 10^5
pos 的值为 -1 或者链表中的一个有效索引
方法一:快慢指针(Floyd判圈算法)
判断是否有环:快慢指针相遇说明有环;寻找环入口:相遇后,将其中一个指针重置到头节点,两指针以相同速度前进,再次相遇的节点即为环入口。
- 快指针每次走两步,慢指针每次走一步,找到相遇点。
- 初始化两个指针分别从头节点和相遇点出发,同步移动直至相遇。
代码实现(Java):
public class Solution {public ListNode detectCycle(ListNode head) {ListNode slow = head, fast = head;boolean hasCycle = false;// 判断是否存在环while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;if (slow == fast) {hasCycle = true;break;}}if (!hasCycle) return null;// 寻找环入口ListNode ptr1 = head, ptr2 = slow;while (ptr1 != ptr2) {ptr1 = ptr1.next;ptr2 = ptr2.next;}return ptr1;}
}
方法二:哈希表
遍历链表并记录访问过的节点,第一个重复的节点即为环入口。
- 使用哈希表存储已访问的节点。
- 遍历链表,若当前节点已存在于哈希表,则返回该节点;否则继续遍历。
代码实现(Java):
public class Solution {public ListNode detectCycle(ListNode head) {Set<ListNode> visited = new HashSet<>();ListNode node = head;while (node != null) {if (visited.contains(node)) {return node;}visited.add(node);node = node.next;}return null;}
}
复杂度分析
- 快慢指针法:时间复杂度
O(n)
,空间复杂度O(1)
。通过数学推导找到环入口,高效且节省内存。 - 哈希表法:时间复杂度
O(n)
,空间复杂度O(n)
。实现简单,但需要额外空间存储节点,适用于空间不敏感的场景。
146. LRU 缓存
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache
类:
LRUCache(int capacity);
// 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key);
// 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value);
// 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。
// 如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get
和 put
必须以 O(1)
的平均时间复杂度运行。
示例:
输入:
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出:
[null, null, null, 1, null, -1, null, -1, 3, 4]
解释:
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4
提示:
1 <= capacity <= 3000
0 <= key <= 10000
0 <= value <= 10^5
最多调用 2*10^5 次 get 和 put
方法:哈希表结合双向链表
LRU缓存机制要求快速定位元素是否存在,并维护元素的访问顺序。利用哈希表实现O(1)
时间的查找,双向链表维护访问顺序,最近访问的节点置于链表头部,尾部则为最久未使用的节点。
- 数据结构设计:
- 双向链表节点包含键、值、前驱和后继指针。
- 哈希表存储键到链表节点的映射。
- 初始化:
- 创建虚拟头尾节点,形成空链表。
- get操作:
- 若键存在,将对应节点移至链表头部并返回值;否则返回-1。
- put操作:
- 若键存在,更新值并将节点移至头部。
- 若不存在,创建新节点并添加到头部及哈希表中。若容量超限,删除尾部节点并更新哈希表。
- 代码实现关键点:
- 双向链表操作:通过虚拟头尾节点简化链表操作,确保插入和删除的指针调整正确。
- 哈希表维护:哈希表与链表同步更新,保证快速访问和空间管理。
- LRU策略实现:通过链表顺序维护访问时间,头部为最新,尾部为最旧,容量超限时删除尾部节点。
代码实现(Java):
class LRUCache {class Node {int key;int value;Node prev;Node next;public Node() {}public Node(int key, int value) {this.key = key;this.value = value;}}private Map<Integer, Node> cache;private int capacity;private int size;private Node dummyHead;private Node dummyTail;public LRUCache(int capacity) {this.capacity = capacity;this.size = 0;cache = new HashMap<>();dummyHead = new Node();dummyTail = new Node();dummyHead.next = dummyTail;dummyTail.prev = dummyHead;}public int get(int key) {Node node = cache.get(key);if (node == null) {return -1;}moveToHead(node);return node.value;}public void put(int key, int value) {Node node = cache.get(key);if (node != null) {node.value = value;moveToHead(node);} else {Node newNode = new Node(key, value);cache.put(key, newNode);addToHead(newNode);size++;if (size > capacity) {Node tail = removeTail();cache.remove(tail.key);size--;}}}private void addToHead(Node node) {node.prev = dummyHead;node.next = dummyHead.next;dummyHead.next.prev = node;dummyHead.next = node;}private void removeNode(Node node) {node.prev.next = node.next;node.next.prev = node.prev;}private void moveToHead(Node node) {removeNode(node);addToHead(node);}private Node removeTail() {Node tail = dummyTail.prev;removeNode(tail);return tail;}
}
复杂度分析
- 时间复杂度:
get
和put操作均为O(1)
。 - 空间复杂度:
O(capacity)
,用于存储哈希表和链表节点。
148. 排序链表
给你链表的头结点 head
,请将其按 升序 排列并返回 排序后的链表 。
示例 1:
输入:head = [4,2,1,3]
输出:[1,2,3,4]
示例 2:
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
示例 3:
输入:head = []
输出:[]
提示:
链表中节点的数目在范围 [0, 5*10^4] 内
-10^5 <= Node.val <= 10^5
方法一:归并排序(递归)
采用归并排序算法,通过分治策略将链表不断分割成子链表,直到每个子链表只有一个节点,然后逐步合并有序链表。关键点在于正确分割链表和高效合并两个有序链表。
- 递归终止条件:链表为空或只有一个节点时直接返回。
- 寻找中间节点:使用快慢指针法找到链表中点,将链表分为左右两部分。
- 递归排序:分别对左右子链表进行递归排序。
- 合并有序链表:将两个已排序的子链表合并成一个有序链表。
代码实现(Java):
class Solution {public ListNode sortList(ListNode head) {if (head == null || head.next == null) {return head;}// 使用快慢指针找到中间节点ListNode slow = head, fast = head, prev = null;while (fast != null && fast.next != null) {prev = slow;slow = slow.next;fast = fast.next.next;}prev.next = null; // 将链表断开为两部分// 递归排序左右两部分ListNode left = sortList(head);ListNode right = sortList(slow);// 合并有序链表return merge(left, right);}// 合并两个有序链表private ListNode merge(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);ListNode curr = dummy;while (l1 != null && l2 != null) {if (l1.val < l2.val) {curr.next = l1;l1 = l1.next;} else {curr.next = l2;l2 = l2.next;}curr = curr.next;}curr.next = (l1 != null) ? l1 : l2;return dummy.next;}
}
复杂度分析
- 时间复杂度:
O(n log n)
,归并排序的典型时间复杂度。 - 空间复杂度:
O(log n)
,递归调用栈的深度。
方法二:归并排序(迭代)
通过迭代方式实现归并排序,避免递归带来的栈空间开销。每次将链表分成固定大小的块,逐步合并相邻块,直到整个链表有序。
- 计算链表长度:确定需要合并的次数。
- 分块合并:每次将链表分割成大小为
step
的块,合并相邻块。 - 调整步长:每次合并后步长翻倍,直到步长超过链表长度。
代码实现(Java):
class Solution {public ListNode sortList(ListNode head) {if (head == null || head.next == null) {return head;}// 计算链表长度int len = 0;ListNode curr = head;while (curr != null) {len++;curr = curr.next;}ListNode dummy = new ListNode(0);dummy.next = head;// 迭代合并,步长从1开始倍增for (int step = 1; step < len; step *= 2) {ListNode tail = dummy;ListNode left = dummy.next;while (left != null) {ListNode right = split(left, step);ListNode next = split(right, step);tail = merge(left, right, tail);left = next;}}return dummy.next;}// 分割链表,返回分割后的右半部分头节点private ListNode split(ListNode head, int step) {if (head == null) return null;for (int i = 1; head.next != null && i < step; i++) {head = head.next;}ListNode right = head.next;head.next = null;return right;}// 合并两个链表,并连接到tail后面,返回合并后的新tailprivate ListNode merge(ListNode l1, ListNode l2, ListNode tail) {ListNode curr = tail;while (l1 != null && l2 != null) {if (l1.val < l2.val) {curr.next = l1;l1 = l1.next;} else {curr.next = l2;l2 = l2.next;}curr = curr.next;}curr.next = (l1 != null) ? l1 : l2;while (curr.next != null) curr = curr.next;return curr;}
}
复杂度分析
- 时间复杂度:
O(n log n)
,与递归方法相同。 - 空间复杂度:
O(1)
,仅使用常量额外空间。
对比总结
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
递归归并 | 代码简洁,逻辑清晰 | 栈空间O(log n) | 常规场景,链表较短 |
迭代归并 | 无栈溢出风险,空间更优 | 实现较复杂,指针操作多 | 链表较长或空间敏感场景 |
152. 乘积最大子数组
给你一个整数数组 nums
,请你找出数组中乘积最大的非空连续 子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
示例 1:
输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
提示:
1 <= nums.length <= 2*10^4
-10 <= nums[i] <= 10
nums 的任何子数组的乘积都 保证 是一个 32-位 整数
方法:动态规划
使用动态规划维护两个变量 currentMax
和 currentMin
,分别表示以当前元素结尾的子数组的最大和最小乘积。通过遍历数组,每一步利用前一步的状态更新当前值,并记录全局最大值。
-
初始化
currentMax
和currentMin
初始化为第一个元素的值。globalMax
记录全局最大乘积,初始值也为第一个元素。
-
遍历数组
- 对于每个元素
num
,计算三种可能的乘积:- 当前元素本身(可能作为新子数组的起点)。
- 当前元素乘以前一个最大乘积
currentMax * num
。 - 当前元素乘以前一个最小乘积
currentMin * num
(负数可能反转结果)。
- 更新
currentMax
和currentMin
为三种情况中的最大值和最小值。
- 对于每个元素
-
更新全局最大值
- 每次迭代后比较
globalMax
和当前currentMax
,确保记录全局最优解。
- 每次迭代后比较
代码实现(Java):
class Solution {public int maxProduct(int[] nums) {if (nums.length == 0) return 0;int currentMax = nums[0];int currentMin = nums[0];int globalMax = nums[0];for (int i = 1; i < nums.length; i++) {int num = nums[i];// 保存临时值,防止覆盖导致的计算错误int tempMax = Math.max(num, Math.max(currentMax * num, currentMin * num));int tempMin = Math.min(num, Math.min(currentMax * num, currentMin * num));currentMax = tempMax;currentMin = tempMin;globalMax = Math.max(globalMax, currentMax);}return globalMax;}
}
复杂度分析
- 时间复杂度:
O(n)
,只需一次遍历数组。 - 空间复杂度:
O(1)
,仅用常数空间存储状态变量。
153. 寻找旋转排序数组中的最小值
已知一个长度为 n
的数组,预先按照升序排列,经由 1
到 n
次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7]
在变化后可能得到:
若旋转 4
次,则可以得到 [4,5,6,7,0,1,2]
若旋转 7
次,则可以得到 [0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]]
旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]
。
给你一个元素值 互不相同 的数组 nums
,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须设计一个时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
示例 2:
输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。
示例 3:
输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。
提示:
n == nums.length
1 <= n <= 5000
-5000 <= nums[i] <= 5000
nums 中的所有整数互不相同
nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转
方法:二分查找法
通过比较中间元素与右边界元素,判断最小值所在区间,逐步缩小搜索范围。
- 旋转特性分析:旋转后的数组由两个递增子数组组成,最小值位于第二个子数组的首位;
- 关键比较逻辑:
- 当
nums[mid] > nums[right]
时,说明右半部分存在更小元素,收缩左边界; - 当
nums[mid] <= nums[right]
时,说明右半部分有序,最小值可能在左侧或当前mid位置;
- 当
- 终止条件:当左右指针相遇时,指向的位置即为最小值所在。
代码实现(Java):
class Solution {public int findMin(int[] nums) {int left = 0;int right = nums.length - 1;while (left < right) {int mid = left + (right - left) / 2;if (nums[mid] > nums[right]) {// 最小值在右半部分left = mid + 1;} else {// 最小值在左半部分(含mid)right = mid;}}return nums[left];}
}
复杂度分析
- 时间复杂度
O(log n)
,每次循环将搜索范围减半。 - 空间复杂度
O(1)
,仅需常数级额外空间。
155. 最小栈
设计一个支持 push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack
类:
MinStack();
// 初始化堆栈对象。
void push(int val);
// 将元素val推入堆栈。
void pop();
// 删除堆栈顶部的元素。
int top();
// 获取堆栈顶部的元素。
int getMin();
// 获取堆栈中的最小元素。
示例 1:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
提示:
-2^31 <= val <= 2^31 - 1
pop、top 和 getMin 操作总是在 非空栈 上调用
push, pop, top, and getMin最多被调用 3*10^4 次
方法:双栈同步维护法
使用主栈存储元素,辅助栈同步存储对应状态下的最小值,实现所有操作O(1)
时间复杂度。
-
双栈同步机制:
- 主栈
dataStack
正常执行栈操作; - 辅助栈
minStack
存储对应主栈状态时的最小值; - 每次push操作同步更新两个栈,保证栈高度一致。
- 主栈
-
最小值维护策略:
- 当新元素入栈时,
minStack
压入min(新元素, 当前minStack栈顶)
; - 当元素出栈时,双栈同步弹出保持状态一致。
- 当新元素入栈时,
代码实现(Java):
class MinStack {private Deque<Integer> dataStack; // 主栈存储元素private Deque<Integer> minStack; // 辅助栈存储最小值public MinStack() {dataStack = new ArrayDeque<>();minStack = new ArrayDeque<>();}public void push(int val) {dataStack.push(val);// 维护最小值:如果辅助栈为空直接压入,否则取当前值与栈顶较小值if (minStack.isEmpty()) {minStack.push(val);} else {minStack.push(Math.min(val, minStack.peek()));}}public void pop() {dataStack.pop();minStack.pop();}public int top() {return dataStack.peek();}public int getMin() {return minStack.peek();}
}
复杂度分析
- 时间复杂度:
push
、pop
、top
、getMin
操作均为O(1)
。 - 空间复杂度:
O(n)
,每个元素对应存储一个最小值记录。
169. 多数元素
给定一个大小为 n
的数组 nums
,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋
的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入:nums = [3,2,3]
输出:3
示例 2:
输入:nums = [2,2,1,1,1,2,2]
输出:2
提示:
n == nums.length
1 <= n <= 5*10^4
-10^9 <= nums[i] <= 10^9
进阶:
尝试设计时间复杂度为 O(n)
、空间复杂度为 O(1)
的算法解决此问题。
188. 买卖股票的最佳时机 IV
给定一个数组,它的第 i
个元素是一支给定的股票在第 i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k
笔交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [2,4,1], k = 2
输出: 2
解释: 在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
示例 2:
输入: [3,2,6,5,0,3], k = 2
输出: 7
解释: 在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。
方法一:动态规划(通用解法)
使用三维状态压缩的动态规划,将状态定义为 dp[j][0]
和 dp[j][1]
,分别表示完成 j
次交易后不持有股票和持有股票的最大利润。通过逆序处理交易次数避免状态覆盖问题,并增加无效状态判断。
代码实现(Java):
public class Solution {public int maxProfit(int k, int[] prices) {int n = prices.length;if (k == 0 || n < 2) return 0;// 处理 k 很大的情况,退化为贪心算法if (k >= n / 2) {int maxProfit = 0;for (int i = 1; i < n; i++) {if (prices[i] > prices[i - 1]) {maxProfit += prices[i] - prices[i - 1];}}return maxProfit;}// 动态规划初始化int[][] prevDp = new int[k + 1][2];for (int j = 0; j <= k; j++) {Arrays.fill(prevDp[j], Integer.MIN_VALUE);}prevDp[0][0] = 0; // 0次交易,不持有股票prevDp[0][1] = -prices[0]; // 0次交易,持有股票for (int i = 1; i < n; i++) {int[][] currDp = new int[k + 1][2];for (int j = 0; j <= k; j++) {currDp[j][0] = prevDp[j][0];currDp[j][1] = prevDp[j][1];}int price = prices[i];// 逆序处理交易次数,避免状态覆盖for (int j = k; j >= 1; j--) {// 不持有股票:卖出操作if (prevDp[j][1] != Integer.MIN_VALUE) {currDp[j][0] = Math.max(currDp[j][0], prevDp[j][1] + price);}// 持有股票:买入操作(需要前一次交易完成)if (prevDp[j - 1][0] != Integer.MIN_VALUE) {currDp[j][1] = Math.max(currDp[j][1], prevDp[j - 1][0] - price);}}// 处理 j=0 的持有状态(允许第一次买入)currDp[0][1] = Math.max(prevDp[0][1], -price);prevDp = currDp;}// 寻找最大利润(所有交易次数中的最大不持有状态)int maxProfit = 0;for (int j = 0; j <= k; j++) {maxProfit = Math.max(maxProfit, prevDp[j][0]);}return maxProfit;}
}
复杂度分析
- 时间复杂度:
O(nk)
,每个价格处理k
次交易状态。 - 空间复杂度:
O(k)
,使用两个数组交替保存状态。
方法二:状态压缩优化
进一步压缩动态规划空间,使用一维数组代替二维数组。通过逆序遍历交易次数减少空间使用。
代码实现(Java):
public class Solution {public int maxProfit(int k, int[] prices) {int n = prices.length;if (k == 0 || n < 2) return 0;if (k >= n / 2) {int profit = 0;for (int i = 1; i < n; i++) {if (prices[i] > prices[i - 1]) {profit += prices[i] - prices[i - 1];}}return profit;}int[] buy = new int[k + 1];int[] sell = new int[k + 1];Arrays.fill(buy, Integer.MIN_VALUE);for (int price : prices) {for (int j = k; j >= 1; j--) {buy[j] = Math.max(buy[j], sell[j - 1] - price);sell[j] = Math.max(sell[j], buy[j] + price);}}return sell[k];}
}
复杂度分析
- 时间复杂度:
O(nk)
,每个价格处理k
次交易。 - 空间复杂度:
O(k)
,仅用两个一维数组。
189. 轮转数组
给定一个整数数组 nums
,将数组中的元素向右轮转 k
个位置,其中 k
是非负数。
示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
示例 2:
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]
提示:
1 <= nums.length <= 10^5
-2^31 <= nums[i] <= 2^31 - 1
0 <= k <= 10^5
方法一:使用额外数组
- 计算有效k:通过取模运算将k限制在数组长度范围内。
- 创建新数组:遍历原数组,将每个元素按轮转后的位置放入新数组。
- 复制回原数组:将新数组内容覆盖到原数组,完成原地修改。
代码实现(Java):
public class Solution {public void rotate(int[] nums, int k) {int n = nums.length;if (n == 0) return;k = k % n;if (k == 0) return;int[] newArr = new int[n];for (int j = 0; j < n; j++) {int originalIndex = (j - k + n) % n;newArr[j] = nums[originalIndex];}System.arraycopy(newArr, 0, nums, 0, n);}
}
复杂度分析
- 时间复杂度:O(n),遍历数组两次(生成新数组和覆盖原数组)。
- 空间复杂度:O(n),需要额外数组空间。
方法二:三次反转法
- 反转整个数组:将数组元素顺序完全颠倒。
- 反转前k部分:恢复前k个元素的原始顺序。
- 反转剩余部分:恢复剩余元素的原始顺序,最终得到轮转结果。
代码实现(Java):
public class Solution {public void rotate(int[] nums, int k) {int n = nums.length;if (n == 0) return;k = k % n;if (k == 0) return;reverse(nums, 0, n - 1); // 反转整个数组reverse(nums, 0, k - 1); // 反转前k个元素reverse(nums, k, n - 1); // 反转剩余元素}private void reverse(int[] nums, int start, int end) {while (start < end) {int temp = nums[start];nums[start] = nums[end];nums[end] = temp;start++;end--;}}
}
复杂度分析
- 时间复杂度:
O(n)
,三次反转操作各遍历一次数组。 - 空间复杂度:
O(1)
,原地操作,无需额外空间。
方法三:环状替换
- 环状替换:将数组视为环形,每个元素直接移动到最终位置。
- 处理多个环:通过计数确保所有元素都被处理,避免遗漏或重复。
代码实现(Java):
public class Solution {public void rotate(int[] nums, int k) {int n = nums.length;if (n == 0) return;k = k % n;if (k == 0) return;int count = 0;for (int start = 0; count < n; start++) {int current = start;int prev = nums[start];do {int next = (current + k) % n;int temp = nums[next];nums[next] = prev;prev = temp;current = next;count++;} while (current != start);}}
}
复杂度分析
- 时间复杂度:
O(n)
,每个元素被移动一次。 - 空间复杂度:
O(1)
,仅用常数变量。
198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 400
方法一:动态规划(空间优化)
使用动态规划,由于每个状态只依赖于前两个状态的值,可以用两个变量保存前两个状态,优化空间复杂度到 O(1)。
- 处理边界情况(数组长度为0或1)。
- 初始化前两个状态。
- 遍历数组,逐个计算当前最大值并更新状态。
代码实现(Java):
public class Solution {public int rob(int[] nums) {int n = nums.length;if (n == 0) return 0;if (n == 1) return nums[0];int prevPrev = nums[0];int prev = Math.max(nums[0], nums[1]);for (int i = 2; i < n; i++) {int current = Math.max(prev, prevPrev + nums[i]);prevPrev = prev;prev = current;}return prev;}
}
方法二:动态规划(数组存储)
思路:
定义一个数组 dp
,其中 dp[i]
表示偷到第 i
个房屋时的最大金额。状态转移方程为:dp[i] = max(dp[i-1], dp[i-2] + nums[i])
。
步骤:
- 处理边界情况。
- 初始化
dp
数组的前两个元素。 - 遍历数组,填充
dp
数组。
代码实现(Java):
public class Solution {public int rob(int[] nums) {if (nums == null || nums.length == 0) return 0;int n = nums.length;if (n == 1) return nums[0];int[] dp = new int[n];dp[0] = nums[0];dp[1] = Math.max(nums[0], nums[1]);for (int i = 2; i < n; i++) {dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);}return dp[n - 1];}
}
方法三:记忆化递归
思路:
自顶向下递归,使用记忆化存储已计算的结果,避免重复计算。递归函数返回从当前位置开始能偷到的最大金额。
步骤:
- 初始化记忆数组。
- 递归计算每个位置的最大值,保存结果。
代码实现(Java):
public class Solution {public int rob(int[] nums) {int[] memo = new int[nums.length];Arrays.fill(memo, -1);return robHelper(nums, 0, memo);}private int robHelper(int[] nums, int start, int[] memo) {if (start >= nums.length) return 0;if (memo[start] != -1) return memo[start];int res = Math.max(robHelper(nums, start + 1, memo), nums[start] + robHelper(nums, start + 2, memo));memo[start] = res;return res;}
}
复杂度分析
- 动态规划(空间优化): 时间复杂度
O(n)
,空间复杂度O(1)
。通过变量滚动更新,节省空间。 - 动态规划(数组存储): 时间复杂度
O(n)
,空间复杂度O(n)
。直观记录每个位置的最高金额。 - 记忆化递归: 时间复杂度
O(n)
,空间复杂度O(n)
。递归树深度为n
,记忆化避免重复计算。
199. 二叉树的右视图
给定一个二叉树的 根节点 root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
示例 1:
输入:root = [1,2,3,null,5,null,4]
输出:[1,3,4]
示例 2:
输入:root = [1,2,3,4,null,null,null,5]
输出:[1,3,4,5]
示例 3:
输入:root = [1,null,3]
输出:[1,3]
示例 4:
输入:root = []
输出:[]
提示:
二叉树的节点个数的范围是 [0,100]
-100 <= Node.val <= 100
方法一:BFS层次遍历法
- 核心思想:进行标准层次遍历,每层遍历结束后,最后一个出队的节点即为该层最右可见节点。
- 实现要点:使用队列进行广度优先遍历,维护当前层节点数,收集每层末尾节点。
代码实现(Java):
class Solution {public List<Integer> rightSideView(TreeNode root) {List<Integer> result = new ArrayList<>();if (root == null) return result;Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {int levelSize = queue.size();for (int i = 0; i < levelSize; i++) {TreeNode node = queue.poll();// 每层的最后一个节点加入结果集if (i == levelSize - 1) result.add(node.val);if (node.left != null) queue.offer(node.left);if (node.right != null) queue.offer(node.right);}}return result;}
}
方法二:DFS右优先递归法
- 核心思想:按照「根→右→左」的顺序遍历,每个深度首次访问的节点即为该层最右节点。
- 实现要点:递归时优先处理右子树,利用结果列表长度与深度的关系判断是否首次访问该层。
代码实现(Java):
class Solution {public List<Integer> rightSideView(TreeNode root) {List<Integer> result = new ArrayList<>();dfs(root, 0, result);return result;}private void dfs(TreeNode node, int depth, List<Integer> result) {if (node == null) return;// 当前深度首次访问的节点即为右视图节点if (depth == result.size()) result.add(node.val);dfs(node.right, depth + 1, result); // 先右后左确保右优先dfs(node.left, depth + 1, result);}
}
复杂度分析
-
BFS层次遍历法:
- 时间复杂度:
O(n)
,每个节点入队出队一次; - 空间复杂度:
O(n)
,队列存储最多n
节点。
- 时间复杂度:
-
DFS右优先递归法:
- 时间复杂度:
O(n)
,每个节点访问一次; - 空间复杂度:
O(h)
,递归栈深度为树高h
。
- 时间复杂度:
对比总结
- 遍历顺序:BFS按层扫描天然适合收集层信息,DFS通过调整访问顺序模拟右视图。
- 空间效率:BFS空间复杂度为最宽层的节点数,DFS为树的高度,对非平衡树更高效。
200. 岛屿数量
给你一个由 '1'(陆地)
和 '0'(水)
组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]
]
输出:1
示例 2:
输入:grid = [["1","1","0","0","0"],["1","1","0","0","0"],["0","0","1","0","0"],["0","0","0","1","1"]
]
输出:3
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j] 的值为 '0' 或 '1'
方法一:深度优先搜索(DFS)
- 核心思想:从起点出发,尽可能深地探索相邻节点,遇到边界或水域时回溯。
- 实现方式:递归遍历上下左右四个方向,将访问过的陆地标记为水域
('0')
。
代码实现(Java):
class Solution {public int numIslands(char[][] grid) {int count = 0;for (int i = 0; i < grid.length; i++) {for (int j = 0; j < grid[0].length; j++) {if (grid[i][j] == '1') {dfs(grid, i, j);count++;}}}return count;}private void dfs(char[][] grid, int i, int j) {if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] != '1') {return;}grid[i][j] = '0'; // 标记为已访问dfs(grid, i - 1, j); // 上dfs(grid, i + 1, j); // 下dfs(grid, i, j - 1); // 左dfs(grid, i, j + 1); // 右}
}
方法二:广度优先搜索(BFS)
- 核心思想:从起点出发,逐层扩展探索相邻节点。
- 实现方式:使用队列存储待处理节点,每次处理一层节点,确保按层序标记所有相连陆地。
代码实现(Java):
class Solution {public int numIslands(char[][] grid) {int count = 0;int m = grid.length;int n = grid[0].length;for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (grid[i][j] == '1') {bfs(grid, i, j);count++;}}}return count;}private void bfs(char[][] grid, int i, int j) {Queue<int[]> queue = new LinkedList<>();queue.offer(new int[]{i, j});grid[i][j] = '0'; // 标记起点为已访问int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 方向数组while (!queue.isEmpty()) {int[] curr = queue.poll();for (int[] dir : dirs) {int x = curr[0] + dir[0];int y = curr[1] + dir[1];// 检查边界和是否未访问if (x >= 0 && x < grid.length && y >= 0 && y < grid[0].length && grid[x][y] == '1') {grid[x][y] = '0'; // 标记为已访问queue.offer(new int[]{x, y});}}}}
}
复杂度分析
-
DFS(深度优先搜索):
- 时间复杂度:
O(m×n)
,每个节点仅访问一次。 - 空间复杂度:最坏情况下为
O(m×n)
(递归栈深度)。
- 时间复杂度:
-
BFS(广度优先搜索):
- 时间复杂度:
O(m×n)
,每个节点处理一次。 - 空间复杂度:
O(min(m, n))
,队列最坏情况下存储对角线节点。
- 时间复杂度:
对比总结
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
DFS | 代码简洁,实现直观 | 可能栈溢出(深递归时) | 小规模网格或树形结构 |
BFS | 无栈溢出风险 | 需要额外队列空间 | 大规模网格或要求稳定性 |
(持续更新,未完待续)
相关文章:
算法刷题记录——LeetCode篇(2) [第101~200题](持续更新)
(优先整理热门100及面试150,不定期持续更新,欢迎关注) 101. 对称二叉树 给你一个二叉树的根节点 root , 检查它是否轴对称。 示例 1: 输入:root [1,2,2,3,4,4,3] 输出:true示例 2: 输入&am…...
ruoyi 小程序使用笔记
1.上传图片 页面 <uni-forms-item label"退休证明(退休证)" name"retire"><uni-file-picker ref"imageUploadRetire" :limit"1" :auto-upload"false" select"upload"/> </uni-forms-item>js …...
计算机网络——总结
01. 网络的发展及体系结构 网络演进历程 从1969年ARPANET的4个节点发展到如今覆盖全球的互联网,网络技术经历了电路交换到分组交换、有线连接到无线覆盖的革命性变革。5G时代的到来使得网络传输速度突破10Gbps,物联网设备数量突破百亿级别。 网络体系…...
Stable Diffusion lora训练(一)
一、不同维度的LoRA训练步数建议 2D风格训练 数据规模:建议20-50张高质量图片(分辨率≥10241024),覆盖多角度、多表情的平面风格。步数范围:总步数控制在1000-2000步,公式为 总步数 Repeat Image Epoch …...
再学:ERC20-Permit2、SafeERC20方法 详解ERC721,如何铸造一个NFT以及IPFS的作用
目录 1.Permit2 2.尽量使用Safe方法 3.ERC721 4. 铸造NFT 1.Permit2 针对没有permit的代币实现的线下签名方式 通过approve方法,让Permit2拿到无限次数的授权。拿到授权之后,Permit2会生成签名。 当用户需要转账的时候,Permit2会发送签…...
Docker 存储
目录挂载 在执行run时设置参数-v即可实现目录映射, 实现原理会在宿主机器创建一个空文件夹 # 挂载宿主机的 /data 目录到容器的 /app 目录 docker run -d -v /data:/app --name my-app my-image # 挂载 docker 内的 /usr/share/nginx/html 目录到本地机的 /app/nghtml docker…...
详细介绍VUE,带你了解VUE!!!
Vue.js 是一个渐进式的 JavaScript 框架,用于构建用户界面。它的核心库专注于视图层,易于与其它库或现有项目进行集成,同时也可以与现代工具链和支持库结合使用,成为一个完整的开发框架。 以下是关于 Vue.js 的详细资料ÿ…...
C++语法之命名空间二
A.h头文件中代码: namespace a {void 输出(); }; A.cpp源文件中代码: #include <iostream> #include "A.h" void a::输出() {std::cout << "A.h里的输出函数" << std::endl; } B.h头文件中代码: …...
【STM32】I²CC通信外设硬件I²CC读写MPU6050(学习笔记)
相关文章笔记: 【STM32】I2C通信协议&MPU6050芯片-学习笔记-CSDN博客 【江协科技STM32】软件I2C协议层读写MPU6050驱动层-CSDN博客 IC通信外设 IC外设简介 STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答…...
Android Room 框架公共模块源码深度剖析(四)
一、引言 在 Android 开发中,数据持久化是一个常见的需求。Android Room 框架作为 Android Jetpack 组件的一部分,为开发者提供了一个抽象层,使得在 SQLite 数据库上进行数据操作变得更加简单和高效。Room 框架包含多个模块,其中…...
NumPy系列 - 创建矩阵
目录 前传直接创建数组就只是创建数组1. np.array()2. np.arange()3. np.ones()4. numpy.ones_like()5. np.zeros()6. numpy.zeros_like() 定义数据类型 参考资料 前传 由于,某人在上智能相关课程的时候,总想着一大堆的事情,统计股市涨跌&am…...
能“嘎嘎提升”提升用户居住体验的智能家居物联网框架推荐!
智能家居在日常生活中给我们的带来了更多的便利,更让有些用户切实地体会到了科技的魅力,对于想要打造属于自己的智能家居氛围感的用户们,以下是一些能够帮助提升居住体验的智能家居物联网框架及应用: 1. 涂鸦智能(Tuy…...
python-leetcode 60.分割回文串
题目: 给定一个字符串S,请将S分割成一些子串,使每个子串都是回文串,返回S所有可能的分割方案 方法一:回溯深度优先搜索 1. 主要思想 使用 深度优先搜索(DFS) 遍历 s 的所有可能划分方式。使用 回溯&…...
Android Jetpack Compose介绍
Android Jetpack Compose Android Jetpack Compose 是 Google 推出的现代 UI 工具包,用于以声明式的方式构建 Android 应用的 UI。它摒弃了传统的 XML 布局方式,完全基于 Kotlin 编写,提供了更简洁、更强大的 UI 开发体验。以下是 Compose 的…...
文档搜索引擎项目测试
目录 设计测试用例 功能测试 文档搜索功能 具体的逻辑 文档显示搜索结果功能 自动化测试 功能类介绍 最终测试套件的测试结果 性能测试 查看聚合报告 每秒处理事务数/吞吐量(TPS) 响应时间 生成测试报告 界面测试 初始页面 搜索页面 总结 后续改进: 设计测试…...
Vue3项目技术点记录
vue3项目技术点笔记 1 环境变量 - 不同环境用不同的配置 环境变量命名:.env.produciton .env.development Vite.config.ts 配置 envDir:‘’ 链接: VUE3+Vite 环境变量配置 2 axios的封装 http请求拦截,响应拦截。 src下建立公共文件夹Utils下建立request.ts import axios…...
一和零 (leetcode 474
leetcode系列 文章目录 一、核心操作二、外层配合操作三、核心模式代码总结 本题是一个01背包问题,只是背包是一个二维数组的背包,分别为0的个数不能超过m,1的个数不能超过n,而物品就是题目中的字符串,其容量为0和1的…...
Linux vim mode | raw / cooked
注:机翻,未校。 vim terminal “raw” mode Vim 终端 “raw” 模式 1. 原始模式与已处理模式的区别 We know vim puts the terminal in “raw” mode where it receives keystrokes as they are typed, opposed to “cooked” mode where the command…...
利用Linux的I2C子系统和i2c-tools工具集写出的对I2C设备AP3216C读写的应用程序
前言 由于NXP官方提供的BSP里已经包含了其片上I2C控制器的驱动并接入到了Linux的I2C子系统,所以我们可以直接去写与I2C有关的应用程序了。 在本篇博文中我们用两种方式对I2C设备AP3216C进行读写操作。 第一种:直接利用Linux的I2C子系统对I2C设备AP3216…...
springCloud集成tdengine(原生和mapper方式) 其二 原生篇
mapper篇请看另一篇文章 一、引入pom文件 <!-- TDengine 连接器--><dependency><groupId>com.taosdata.jdbc</groupId><artifactId>taos-jdbcdriver</artifactId><version>3.5.3</version></dependency>二、在nacos中填…...
gralloc usage flags
下面这些示例主要说明了 gralloc usage flags 在图像处理和多媒体应用中如何影响性能和正确性。让我们逐个详细分析每个问题的 根因 和 修复方案,并深入解析 gralloc 标志对 缓存管理 和 数据流 的影响。 ✅ Example 1: 长曝光快照耗时异常 📌 问题描述…...
RIP路由欺骗攻击与防御实验详解
一、基础网络配置 1. 路由器R1配置 interface GigabitEthernet0/0/0ip address 192.1.2.254 255.255.255.0 ! interface GigabitEthernet0/0/1ip address 192.1.3.254 255.255.255.0 ! router rip 1version 2network 192.1.2.0network 192.1.3.0 2. 路由器R2配置 interface…...
在 Linux下使用 Python 3.11 和 FastAPI 搭建带免费证书的 HTTPS 服务器
在当今数字化时代,保障网站数据传输的安全性至关重要。HTTPS 协议通过使用 SSL/TLS 加密技术,能够有效防止数据在传输过程中被窃取或篡改。本教程将详细介绍如何在 Ubuntu 22.04 系统上,使用 Python 3.11 和 FastAPI 框架搭建一个带有免费 SS…...
[QMT量化交易小白入门]-三十五、如何将聚宽策略信号转为QMT实盘交易
本专栏主要是介绍QMT的基础用法,常见函数,写策略的方法,也会分享一些量化交易的思路,大概会写100篇左右。 QMT的相关资料较少,在使用过程中不断的摸索,遇到了一些问题,记录下来和大家一起沟通,共同进步。 文章目录 相关阅读一、聚宽模拟运行:构建交易策略的基础在聚宽…...
国思RDIF低代码快速开发框架 v6.2版本发布
1、平台介绍 国思RDIF企业级低代码开发平台,给用户和开发者最佳的框架平台方案,为企业快速构建跨平台、企业级的应用提供强大支持。致力于解决企业信息化项目交付难、实施效率低、开发成本高的问题。能帮助企业快速构建美观易用、架构专业、安全可控的企…...
学习笔记 ASP.NET Core Web API 8.0部署到iis
一.修改配置文件 修改Program.cs配置文件将 if (app.Environment.IsDevelopment()) {app.UseSwagger();app.UseSwaggerUI(); }修改为 app.UseSwagger(); app.UseSwaggerUI(); 二.安装ASP.NET Core Runtime 8.0.14 文件位置https://dotnet.microsoft.com/en-us/download/do…...
【Linux】信号:产生信号
🔥个人主页:Quitecoder 🔥专栏:linux笔记仓 目录 01.信号信号处理简单理解信号的发送和保存由软件产生的信号由硬件(异常)产生的信号 01.信号 进程信号是操作系统提供的一种异步通知机制,用于…...
单片机自学总结
自从工作以来,一直努力耕耘单片机,至今,颇有收获。从51单片机,PIC单片机,直到STM32,以及RTOS和Linux,几乎天天在搞:51单片机,STM8S207单片机,PY32F003单片机,…...
Idea集成docker通过ca加密实现镜像打包
Idea集成docker实现镜像打包_ideadocker镜像打包-CSDN博客 之前通过这种方式虽然可以实现idea通过maven打jar包的同时把docker镜像也进行打包,但是这种方式存在很大漏洞,就是服务器的2375端口大开,任何人拿着idea通过这种方式都可以连…...
【Prometheus】prometheus标签替换label_replace,动态修改生成标签,增强查询的灵活性和表达能力
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…...
蓝桥杯 之 暴力回溯
文章目录 数字接龙小u的最大连续移动次数问题 在蓝桥杯中,十分喜欢考察对于网格的回溯的问题,对于这类的问题,常常会使用到这个DFS和BFS进行考察,不过无论怎么考察,都只是会在最基础的模本的基础上,根据这个…...
在K8S中挂载 Secret 到 Pod
在 Kubernetes 里,把 Secret 挂载到 Pod 中有两种主要方式:作为卷挂载和作为环境变量挂载。下面为你提供相应的代码示例。 作为卷挂载 Secret 将 Secret 作为卷挂载到 Pod 时,Secret 的每个键会成为挂载目录下的一个文件,文件内…...
LINUX网络编程API原型详细解析
1. 网络体系 1.1. 简介 网络采用分而治之的方法设计,将网络的功能划分为不同的模块,以分层的形式有机组合在一起。 每层实现不同的功能,其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务,同时使用下层提供…...
Android 13 Launcher3最近任务列表“全部清除“按钮位置优化实战
一、问题背景与实现难点 在Android 13横屏设备开发中,系统默认将最近任务列表的"全部清除"按钮布局在屏幕左侧,这与用户习惯的底部布局存在明显差异。相较于Android 8.1时代SystemUI模块的实现,Android 13将相关逻辑迁移至Launche…...
docker最新源,及遇到问题+处理
目前国内可用Docker镜像源汇总(截至2025年3月) - CoderJia 遇到问题: Error response from daemon: Get "https://registry-1.docker.io/v2/": dial tcp: lookup registry-1.docker.io on [::1]:53: read udp [::1]:13287->[:…...
Python简单爬虫实践案例
学习目标 能够知道Web开发流程 能够掌握FastAPI实现访问多个指定网页 知道通过requests模块爬取图片 知道通过requests模块爬取GDP数据 能够用pyecharts实现饼图 能够知道logging日志的使用 一、基于FastAPI之Web站点开发 1、基于FastAPI搭建Web服务器 # 导入FastAPI模…...
统信UOS中使用Vscode编程
写在前面:统信UOS其实就是套壳的Linux系统,所以有问题如果搜不到解决方法,可以参考Linux下的解决方法。 1.环境配置 Vscode : 1.85.0 Vscode就直接下载安装就行,然后安装插件:Volar、中文汉化包 node:18…...
C#从入门到精通(1)
目录 第一章 C#与VS介绍 第二章 第一个C#程序 (1)C#程序基本组成 1.命名空间 2.类 3.Main方法 4.注释 5.语句 6.标识符及关键字 (2)程序编写规范 1.代码编写规则 2.程序命名方法 3.元素命名规范 第三章 变量 &…...
openharmony中hilog实证记录说明(3.1和5.0版本)
每次用这个工具hilog都有一些小用法记不清,需要花一些时间去查去分析使用方法,为了给丰富多彩的生活留出更多的时间,所以汇总整理共享来了,它来了它来了~~~~~~~~~ 开始是想通过3.1来汇总的,但实际测试发现openharmony…...
飞书 设计智能字段:通过“字段类型”添加AI功能列
在飞书多维表格中,通过“字段类型”添加AI功能列的核心逻辑是将AI模型能力与结构化数据结合,实现自动化内容生成与信息处理。以下是具体操作步骤及关键要点,结合实际应用场景说明: 一、基础操作步骤 创建多维表格 登录飞书&#x…...
Cannot find module @rollup/rollup-win32-x64-msvc
方法1 在package.json中添加postinstall: "scripts": {"postinstall": "node -e \"const { platform } process; if (platform win32) { require(child_process).execSync(npm install rollup/rollup-win32-x64-msvc, { stdio: inherit });…...
Docker和Dify学习笔记
文章目录 1 docker学习1.1 基本命令使用1.1.1 docker ps查看当前正在运行的镜像1.1.2 docker stop停止容器1.1.3 docker compose容器编排1.1.4 docker网络[1] 进入到容器里面敲命令[2] docker network ls[3] brige网络模式下容器访问宿主机的方式 2 Dify的安装和基础使用2.1 下…...
【AIGC】Win10系统极速部署Docker+Ragflow+Dify
【AIGC】WIN10仅3步部署DockerRagflowDify 一、 Docker快速部署1.F2进入bios界面,按F7设置开启VMX虚拟化技术。保存并退出。2.打开控制面板配置开启服务3.到官网下载docker安装包,一键安装(全部默认勾选) 二、 RagFlow快速部署1.确…...
Oracle ASM 磁盘组冗余策略
Oracle ASM 磁盘组冗余策略 1. 外部冗余(External Redundancy)2. 普通冗余(Normal Redundancy)3. 高冗余(High Redundancy)关键注意事项如何选择合适的策略? Oracle ASM(Automatic S…...
C++ 数据结构
C++ 数据结构 概述 C++作为一种强大的编程语言,在软件开发领域有着广泛的应用。数据结构作为C++编程中不可或缺的一部分,它决定了程序的性能和效率。本文将详细介绍C++中的常见数据结构,包括其定义、特点以及在实际应用中的使用方法。 常见数据结构 1. 数组 数组是一种…...
Unity NodeCanvas AI使用笔记
扩展: 1. 输入输出参数限制,增加描述,根据接口判断类型限制 2.选择节点,遍历节点,行为节点 3.行为节点 行为执行的时候有互斥关系,加入一个queue,最后执行 4.NodeCanvas的参数传参可以由上个节点传到下个节…...
(* IOB = “FORCE“ *) 的使用分享
在Xilinx FPGA设计中,IOBFORCE是一个与输入输出块(IOB)相关的属性设置。这个设置主要用于控制逻辑是否被推入到IOB(Input/Output Block)中,即FPGA芯片边缘的I/O引脚附近的专用硬件资源。使用IOB属性可以帮助…...
【大语言模型_7】利用ragas框架评测rag系统指标
一、介绍 ragas是一个用来评估RAG系统的框架,允许不在依赖人工注释的情况下,通过一套指标评估检索模块和生成模块的性能及其质量。 二、准备 数据准备:需要准备评估数据集,数据集格式如下 [{"question": "安全智…...
adb常用的命令
1. 查看adb版本 adb version 2. 将apk安装包安装到手机/模拟器上 adb install apk路径 3. 获取apk包名和界面名 包名(package):决定程序的唯一性 界面名(activity):一个界面界面名,对应一个界面…...
手动集成sqlite的方法
注意到sqlite有backup方法(https://www.sqlite.org/backup.html)。 也注意到android中sysroot下,没有sqlite3的库,也没有相关头文件。 如果要使用 sqlite 的backup,那么就需要手动集成sqlite代码到项目中。可以如下操…...