马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
更新时间:2025-03-30
- 算法题解目录汇总:算法刷题记录——题解目录汇总
- 技术博客总目录:盘算机技术系列博客——目录页
1. 两数之和
给定一个整数数组 nums 和一个整数量的值 target,请你在该数组中找出 和为目的值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按恣意次序返回答案。
示例 1:
- 输入:nums = [2,7,11,15], target = 9
- 输出:[0,1]
复制代码 解释:由于 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
- 输入:nums = [3,2,4], target = 6
- 输出:[1,2]
复制代码 示例 3:
- 输入:nums = [3,3], target = 6
- 输出:[0,1]
复制代码 提示:
- 2 <= nums.length <= 10^4
- -10^9 <= nums <= 10^9
- -10^9 <= target <= 10^9
- 只会存在一个有用答案
进阶:
你可以想出一个时间复杂度小于 O(n^2) 的算法吗?
方法:哈希表
使用哈希表存储已遍历元素的值及其索引,对于每个元素 nums,盘算其补数 target - nums。若补数存在于哈希表中,则直接返回两个索引;否则将当前元素存入哈希表,继续遍历。
- 初始化哈希表,键为元素值,值为索引。
- 遍历数组,对于每个元素盘算补数。
- 检查补数是否存在哈希表中:
- 存在:返回当前索引和补数的索引。
- 不存在:将当前元素存入哈希表。
代码实现(Java):
- class Solution {
- public int[] twoSum(int[] nums, int target) {
- Map<Integer, Integer> map = new HashMap<>();
- for (int i = 0; i < nums.length; i++) {
- int complement = target - nums[i];
- if (map.containsKey(complement)) {
- return new int[] { map.get(complement), i };
- }
- map.put(nums[i], i);
- }
- throw new IllegalArgumentException("No solution found");
- }
- }
复制代码 复杂度分析
- 时间复杂度:O(n),只需一次遍历数组。
- 空间复杂度:O(n),哈希表存储最多 n 个元素。
2. 两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同情势返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
- 输入:l1 = [2,4,3], l2 = [5,6,4]
- 输出:[7,0,8]
复制代码 解释:342 + 465 = 807.
示例 2:
- 输入:l1 = [0], l2 = [0]
- 输出:[0]
复制代码 示例 3:
- 输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
- 输出:[8,9,9,9,0,0,0,1]
复制代码 提示:
- 每个链表中的节点数在范围 [1, 100] 内
- 0 <= Node.val <= 9
- 题目数据包管列表表示的数字不含前导零
方法:模仿逐位相加
模仿手工逐位相加的过程,从链表头部(最低位)开始遍历,处理每一位的和及进位。使用哑节点简化链表头部的处理,循环直到所有链表遍历完毕且无进位为止。
- 初始化:
- 创建哑节点 dummy 和当前指针 current。
- 初始化进位 carry 为 0。
- 遍历链表:
- 只要任一链表未遍历完或存在进位,继续循环。
- 盘算当前位的总和:sum = val1 + val2 + carry。
- 更新当前节点值和进位。
- 处理剩余节点和进位:
- 若链表遍历完毕,对应值取 0。
- 若终极进位非零,添加新节点。
代码实现(Java):
- class Solution {
- public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
- ListNode dummy = new ListNode(0);
- ListNode current = dummy;
- int carry = 0;
- while (l1 != null || l2 != null || carry != 0) {
- int val1 = (l1 != null) ? l1.val : 0;
- int val2 = (l2 != null) ? l2.val : 0;
- int sum = val1 + val2 + carry;
- carry = sum / 10;
- current.next = new ListNode(sum % 10);
- current = current.next;
- if (l1 != null) l1 = l1.next;
- if (l2 != null) l2 = l2.next;
- }
- return dummy.next;
- }
- }
复制代码 哑节点:简化链表头节点的处理,避免空指针题目。
循环条件:覆盖两链表长度差别及存在终极进位的环境。
进位处理:每位盘算后更新进位,并在循环结束时检查是否必要添加新节点。
复杂度分析
- 时间复杂度:O(max(m, n)),其中 m 和 n 为两链表长度。
- 空间复杂度:O(1)(不计效果链表)。
3. 无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
解释: 由于无重复字符的最长子串是 “abc”,以是其长度为 3。
示例 2:
解释: 由于无重复字符的最长子串是 “b”,以是其长度为 1。
示例 3:
解释: 由于无重复字符的最长子串是 “wke”,以是其长度为 3。请留意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
提示:
- 0 <= s.length <= 5 * 10^4
- s 由英文字母、数字、符号和空格组成
方法一:滑动窗口(哈希表记录字符末了出现的位置)
使用哈希表来记录每个字符末了一次出现的位置。当遇到重复字符时,直接将左指针移动到重复字符的下一个位置,确保窗口内无重复字符。
代码实现(Java):
- class Solution {
- public int lengthOfLongestSubstring(String s) {
- Map<Character, Integer> map = new HashMap<>();
- int maxLen = 0;
- int left = 0;
- for (int right = 0; right < s.length(); right++) {
- char c = s.charAt(right);
- if (map.containsKey(c) && map.get(c) >= left) {
- left = map.get(c) + 1;
- }
- map.put(c, right);
- maxLen = Math.max(maxLen, right - left + 1);
- }
- return maxLen;
- }
- }
复制代码 方法二:滑动窗口(哈希聚集维护当前窗口)
使用哈希聚集来维护当前窗口中的字符。当遇到重复字符时,渐渐移动左指针并移除字符,直到重复字符被移出聚集。
代码实现(Java):
- class Solution {
- public int lengthOfLongestSubstring(String s) {
- Set<Character> set = new HashSet<>();
- int maxLen = 0;
- int left = 0;
- for (int right = 0; right < s.length(); right++) {
- char c = s.charAt(right);
- while (set.contains(c)) {
- set.remove(s.charAt(left));
- left++;
- }
- set.add(c);
- maxLen = Math.max(maxLen, right - left + 1);
- }
- return maxLen;
- }
- }
复制代码 方法三:数组优化(记录字符索引)
使用数组代替哈希表来记录字符的末了出现位置,提升访问速度。假设字符集为ASCII,数组大小设为128。
代码实现(Java):
- class Solution {
- public int lengthOfLongestSubstring(String s) {
- int[] index = new int[128];
- Arrays.fill(index, -1);
- int maxLen = 0;
- int left = 0;
- for (int right = 0; right < s.length(); right++) {
- char c = s.charAt(right);
- if (index[c] >= left) {
- left = index[c] + 1;
- }
- index[c] = right;
- maxLen = Math.max(maxLen, right - left + 1);
- }
- return maxLen;
- }
- }
复制代码 对比总结
- 滑动窗口(哈希表):使用哈希表存储字符及其末了一次出现的位置。当右指针遇到重复字符时,直接调整左指针到重复字符的下一位,确保窗口内无重复。
- 滑动窗口(哈希聚集):使用聚集生存当前窗口的字符。右指针移动时,若遇到重复字符,则不断移动左指针并移除字符,直到重复字符被移出聚集。
- 数组优化:针对ASCII字符集,用数组替代哈希表,记录字符的末了出现位置。访问数组比哈希表更快,提升效率。
三种方法均包管了线性时间复杂度(O(n)),其中数组优化方法在常数时间使用上更优。
4. 寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例 1:
- 输入:nums1 = [1,3], nums2 = [2]
- 输出:2.00000
复制代码 解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
- 输入:nums1 = [1,2], nums2 = [3,4]
- 输出:2.50000
复制代码 解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
提示:
- nums1.length == m
- nums2.length == n
- 0 <= m <= 1000
- 0 <= n <= 1000
- 1 <= m + n <= 2000
- -10^6 <= nums1, nums2 <= 10^6
方法:二分查找法
将题目转化为寻找两个有序数组的第k小元素,通过递归二分扫除元素实现。
- 中位数转换:
- 将中位数题目转化为寻找第k小元素题目,奇数找中心数,偶数找中心两个数的平均值
- 递归二分扫除:
- 每次比力两个数组的第k/2个元素(现实取有用长度防止越界)
- 扫除较小值所在数组的前k/2元素(或全部剩余元素)
- 递归处理剩余部门,直到k=1时直接取较小值
- 界限处理:
- 当某个数组起始位置越界时,直接从另一数组取对应元素
- 有用步长盘算确保不会越界访问数组
代码实现(Java):
- class Solution {
- public double findMedianSortedArrays(int[] nums1, int[] nums2) {
- int total = nums1.length + nums2.length;
- if (total % 2 == 1) {
- return findKth(nums1, nums2, 0, 0, total / 2 + 1);
- } else {
- int left = findKth(nums1, nums2, 0, 0, total / 2);
- int right = findKth(nums1, nums2, 0, 0, total / 2 + 1);
- return (left + right) / 2.0;
- }
- }
-
- private int findKth(int[] A, int[] B, int aStart, int bStart, int k) {
- if (aStart >= A.length) return B[bStart + k - 1];
- if (bStart >= B.length) return A[aStart + k - 1];
- if (k == 1) return Math.min(A[aStart], B[bStart]);
-
- // 计算有效步长(防止越界)
- int aStep = Math.min(k / 2, A.length - aStart);
- int bStep = Math.min(k / 2, B.length - bStart);
- int aIndex = aStart + aStep - 1;
- int bIndex = bStart + bStep - 1;
-
- // 比较并排除较小值的区间
- if (A[aIndex] <= B[bIndex]) {
- return findKth(A, B, aStart + aStep, bStart, k - aStep);
- } else {
- return findKth(A, B, aStart, bStart + bStep, k - bStep);
- }
- }
- }
复制代码 复杂度分析
- 时间复杂度为O(log(m+n)),每次递归减少k值约一半。
5. 最长回文子串
给你一个字符串 s,找到 s 中最长的 回文 子串。
示例 1:
解释:“aba” 同样是符合题意的答案。
示例 2:
提示:
- 1 <= s.length <= 1000
- s 仅由数字和英文字母组成
方法一:动态规划
- 使用二维数组 dp[j] 记录子串 s[i...j] 是否为回文。
- 通过状态转移方程 dp[j] = (s == s[j]) && dp[i+1][j-1] 递推盘算所有子串的回文性,同时记录最长回文。
代码实现(Java):
- class Solution {
- public String longestPalindrome(String s) {
- int n = s.length();
- if (n < 2) return s;
-
- boolean[][] dp = new boolean[n][n];
- int maxLen = 1, start = 0;
-
- // 初始化所有长度为1的子串为回文
- for (int i = 0; i < n; i++) dp[i][i] = true;
-
- // 递推计算所有子串
- for (int j = 1; j < n; j++) {
- for (int i = 0; i < j; i++) {
- if (s.charAt(i) != s.charAt(j)) {
- dp[i][j] = false;
- } else {
- if (j - i < 3) { // 长度<=3时无需检查内部子串
- dp[i][j] = true;
- } else {
- dp[i][j] = dp[i+1][j-1];
- }
- }
-
- // 更新最长回文
- if (dp[i][j] && j - i + 1 > maxLen) {
- maxLen = j - i + 1;
- start = i;
- }
- }
- }
- return s.substring(start, start + maxLen);
- }
- }
复制代码 复杂度分析:
- 时间复杂度:O(n²),双重循环遍历所有子串。
- 空间复杂度:O(n²),存储动态规划表。
方法二:中心扩展法
遍历每个可能的回文中心(单个字符或相邻字符对),向两侧扩展直到不满足回文条件。记录扩展过程中的最长回文。
代码实现(Java):
- class Solution {
- public String longestPalindrome(String s) {
- if (s == null || s.length() < 1) return "";
- int start = 0, end = 0;
-
- for (int i = 0; i < s.length(); i++) {
- int len1 = expand(s, i, i); // 奇数长度
- int len2 = expand(s, i, i+1); // 偶数长度
- int maxLen = Math.max(len1, len2);
-
- if (maxLen > end - start) {
- start = i - (maxLen - 1) / 2;
- end = i + maxLen / 2;
- }
- }
- return s.substring(start, end + 1);
- }
-
- private int expand(String s, int left, int right) {
- while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
- left--;
- right++;
- }
- return right - left - 1; // 实际回文长度
- }
- }
复制代码 复杂度分析:
- 时间复杂度:O(n²),遍历 2n-1 个中心点,每个扩展最多 O(n) 次。
- 空间复杂度:O(1),无需额外存储。
方法三:BFS 层序扩展
将每个可能的回文中心(长度1或2)加入队列,逐层向两侧扩展。每次扩展后更新最长回文,使用队列避免重复处理。
代码实现(Java):
- class Solution {
- public String longestPalindrome(String s) {
- int n = s.length();
- if (n == 0) return "";
-
- Queue<int[]> queue = new LinkedList<>();
- boolean[][] visited = new boolean[n][n];
- int maxLen = 1, start = 0;
-
- // 初始化长度为1和2的回文
- for (int i = 0; i < n; i++) {
- queue.offer(new int[]{i, i});
- visited[i][i] = true;
- if (i < n-1 && s.charAt(i) == s.charAt(i+1)) {
- queue.offer(new int[]{i, i+1});
- visited[i][i+1] = true;
- maxLen = 2;
- start = i;
- }
- }
-
- // BFS扩展
- while (!queue.isEmpty()) {
- int[] pos = queue.poll();
- int l = pos[0], r = pos[1];
-
- // 向两侧扩展
- int nl = l - 1, nr = r + 1;
- if (nl >= 0 && nr < n && s.charAt(nl) == s.charAt(nr) && !visited[nl][nr]) {
- visited[nl][nr] = true;
- queue.offer(new int[]{nl, nr});
- if (nr - nl + 1 > maxLen) {
- maxLen = nr - nl + 1;
- start = nl;
- }
- }
- }
- return s.substring(start, start + maxLen);
- }
- }
复制代码 复杂度分析:
- 时间复杂度:O(n²),每个子串最多处理一次。
- 空间复杂度:O(n²),存储队列和访问标记。
方法四:Manacher 算法(最优解)
通过插入特别字符(如 #)将字符串同一为奇数长度,使用对称性快速盘算每个中心的最长回文半径,避免重复盘算。
代码实现(Java):
- class Solution {
- public String longestPalindrome(String s) {
- StringBuilder sb = new StringBuilder("^#");
- for (char c : s.toCharArray()) {
- sb.append(c).append('#');
- }
- sb.append('$');
- String T = sb.toString();
-
- int n = T.length();
- int[] P = new int[n];
- int C = 0, R = 0;
-
- for (int i = 1; i < n-1; i++) {
- int mirror = 2 * C - i;
- if (i < R) P[i] = Math.min(R - i, P[mirror]);
-
- // 扩展回文
- while (T.charAt(i + P[i] + 1) == T.charAt(i - P[i] - 1)) {
- P[i]++;
- }
-
- // 更新中心和右边界
- if (i + P[i] > R) {
- C = i;
- R = i + P[i];
- }
- }
-
- // 找出最大回文
- int maxLen = 0, center = 0;
- for (int i = 1; i < n-1; i++) {
- if (P[i] > maxLen) {
- maxLen = P[i];
- center = i;
- }
- }
- int start = (center - maxLen) / 2;
- return s.substring(start, start + maxLen);
- }
- }
复制代码 复杂度分析:
- 时间复杂度:O(n),线性时间处理。
- 空间复杂度:O(n),存储变换后的字符串和回文半径数组。
对比总结
方法优劣势动态规划逻辑直观,但空间占用较高中心扩展法空间最优,代码轻便BFS 扩展类似中心扩展,但需额外空间记录状态Manacher 算法线性时间,但实现复杂,适用于极大输入 6. Z 字形变换
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
好比输入字符串为 " AYPALISHIRING" 行数为 3 时,排列如下:
- P A H N
- A P L S I I G
- Y I R
复制代码 之后,你的输出必要从左往右逐行读取,产生出一个新的字符串,好比:" AHNAPLSIIGYIR"。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
- 输入:s = "PAYPALISHIRING", numRows = 3
- 输出:"PAHNAPLSIIGYIR"
复制代码 示例 2:
- 输入:s = "PAYPALISHIRING", numRows = 4
- 输出:"PINALSIGYAHRPI"
复制代码 解释:如下所示。
示例 3:
- 输入:s = "A", numRows = 1
- 输出:"A"
复制代码 提示:
- 1 <= s.length <= 1000
- s 由英文字母(小写和大写)、',' 和 '.' 组成
- 1 <= numRows <= 1000
方法一:数学规律法(按行访问)
通过观察Z字形排列的规律,直接盘算每一行字符在原字符串中的位置并按次序拼接。
- 周期盘算:每个Z字形周期的长度为 2 * numRows - 2。
- 按行处理:对于每一行,字符的位置分为主列(垂直列)和斜列(对角线列)。主列的位置为 j + i,斜列的位置为 j + cycle - i,其中 j 为周期的起始索引。
- 界限处理:第一行和末了一行没有斜列,中心行必要处理斜列的位置,确保不越界。
代码实现(Java):
- class Solution {
- public String convert(String s, int numRows) {
- if (numRows == 1) {
- return s;
- }
-
- StringBuilder result = new StringBuilder();
- int cycle = 2 * numRows - 2;
-
- for (int i = 0; i < numRows; i++) {
- for (int j = 0; j + i < s.length(); j += cycle) {
- result.append(s.charAt(j + i));
- if (i != 0 && i != numRows - 1) { // 中间行处理斜列
- int secondIndex = j + cycle - i;
- if (secondIndex < s.length()) {
- result.append(s.charAt(secondIndex));
- }
- }
- }
- }
- return result.toString();
- }
- }
复制代码 方法二:模仿方向法(按行添补)
通过模仿字符的排列过程,按方向逐行添补字符,末了合并所有行的字符。
- 方向模仿:使用方向变量控制字符添补的行移动方向,到达界限时反转方向。
- 逐行添补:根据当前方向将字符添加到对应行,末了合并所有行得到效果。
代码实现(Java):
- class Solution {
- public String convert(String s, int numRows) {
- if (numRows == 1) {
- return s;
- }
-
- List<StringBuilder> rows = new ArrayList<>();
- for (int i = 0; i < numRows; i++) {
- rows.add(new StringBuilder());
- }
-
- int currentRow = 0;
- int direction = 1; // 1表示向下,-1表示向上
-
- for (char c : s.toCharArray()) {
- rows.get(currentRow).append(c);
- currentRow += direction;
- // 到达边界时反转方向
- if (currentRow == 0 || currentRow == numRows - 1) {
- direction = -direction;
- }
- }
-
- StringBuilder result = new StringBuilder();
- for (StringBuilder row : rows) {
- result.append(row);
- }
- return result.toString();
- }
- }
复制代码 复杂度分析
两种方法的时间复杂度均为 O(n),其中 n 为字符串长度。数学方法通过直接盘算字符位置更高效,而模仿方法更直观易懂。
7. 整数反转
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部门反转后的效果。
假如反转后整数超过 32 位的有符号整数的范围 [−2^31, 2^31 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
示例 1:
示例 2:
示例 3:
示例 4:
提示:
方法思路
- 逐位反转:通过循环取出整数的末了一位数字,并构建反转后的数。
- 溢出检查:在每次构建新数字前,判断是否会导致溢出。使用整数除法的特性,提前预判乘10后的越界环境。
- 符号处理:使用Java负数取余的特性,直接处理负数环境,避免符号转换导致的溢出题目。
代码实现(Java):
- class Solution {
- public int reverse(int x) {
- int reversed = 0;
- while (x != 0) {
- int digit = x % 10;
- x /= 10;
-
- // 检查正数溢出
- if (reversed > Integer.MAX_VALUE / 10 || (reversed == Integer.MAX_VALUE / 10 && digit > 7)) {
- return 0;
- }
- // 检查负数溢出
- if (reversed < Integer.MIN_VALUE / 10 || (reversed == Integer.MIN_VALUE / 10 && digit < -8)) {
- return 0;
- }
-
- reversed = reversed * 10 + digit;
- }
- return reversed;
- }
- }
复制代码 8. 字符串转换整数 (atoi)
请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数。
函数 myAtoi(string s) 的算法如下:
- 空格:读入字符串并丢弃无用的前导空格(" ")
- 符号:检查下一个字符(假设还未到字符末端)为 ‘-’ 照旧 ‘+’。假如两者都不存在,则假定效果为正。
- 转换:通过跳过前置零来读取该整数,直到遇到非数字字符或到达字符串的末端。假如没有读取数字,则效果为0。
- 舍入:假如整数数超过 32 位有符号整数范围 [−2^31, 2^31 − 1] ,必要截断这个整数,使其保持在这个范围内。具体来说,小于 −2^31 的整数应该被舍入为 −2^31 ,大于 2^31 − 1 的整数应该被舍入为 2^31 − 1 。
返回整数作为终极效果。
示例 1:
解释:
第 1 步:“42”(当前没有读入字符,由于没有前导空格)
第 2 步:“42”(当前没有读入字符,由于这里不存在 ‘-’ 大概 ‘+’)
第 3 步:“42”(读入 “42”)
示例 2:
解释:
第 1 步:" -042"(读入前导空格,但忽视掉)
第 2 步:" -042"(读入 ‘-’ 字符,以是效果应该是负数)
第 3 步:" -042"(读入 “042”,在效果中忽略前导零)
示例 3:
解释:
第 1 步:“1337c0d3”(当前没有读入字符,由于没有前导空格)
第 2 步:“1337c0d3”(当前没有读入字符,由于这里不存在 ‘-’ 大概 ‘+’)
第 3 步:“1337c0d3”(读入 “1337”;由于下一个字符不是一个数字,以是读入停止)
示例 4:
解释:
第 1 步:“0-1” (当前没有读入字符,由于没有前导空格)
第 2 步:“0-1” (当前没有读入字符,由于这里不存在 ‘-’ 大概 ‘+’)
第 3 步:“0-1” (读入 “0”;由于下一个字符不是一个数字,以是读入停止)
示例 5:
- 输入:s = "words and 987"
- 输出:0
复制代码 解释:
读取在第一个非数字字符“w”处停止。
提示:
- 0 <= s.length <= 200
- s 由英文字母(大写和小写)、数字(0-9)、' '、'+'、'-' 和 '.' 组成
方法思路
- 去除前导空格:遍历字符串,跳过所有前导空格。
- 处理符号:检查符号位,确定正负,并移动索引到符号后的位置。
- 逐位转换:读取连续的数字字符,渐渐构建整数。每次添加新数字时检查是否溢出:
- 正数溢出:效果超过Integer.MAX_VALUE时返回Integer.MAX_VALUE。
- 负数溢出:效果的绝对值超过Integer.MAX_VALUE + 1时返回Integer.MIN_VALUE。
- 返回效果:将终极效果乘以符号,确保在32位整数范围内。
代码实现(Java):
- class Solution {
- public int myAtoi(String s) {
- if (s == null || s.isEmpty()) return 0;
- int index = 0;
- int n = s.length();
- int sign = 1;
- long result = 0;
- // 跳过前导空格
- while (index < n && s.charAt(index) == ' ') {
- index++;
- }
- if (index == n) return 0;
- // 处理符号
- if (s.charAt(index) == '+' || s.charAt(index) == '-') {
- sign = s.charAt(index) == '+' ? 1 : -1;
- index++;
- }
- // 转换数字并处理溢出
- while (index < n && Character.isDigit(s.charAt(index))) {
- int digit = s.charAt(index) - '0';
- result = result * 10 + digit;
- // 检查正溢出
- if (sign == 1 && result > Integer.MAX_VALUE) {
- return Integer.MAX_VALUE;
- }
- // 检查负溢出
- if (sign == -1 && -result < Integer.MIN_VALUE) {
- return Integer.MIN_VALUE;
- }
- index++;
- }
- return (int) (sign * result);
- }
- }
复制代码 9. 回文数
给你一个整数 x ,假如 x 是一个回文整数,返回 true ;否则,返回 false 。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
比方,121 是回文,而 123 不是。
示例 1:
示例 2:
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
解释:从右向左读, 为 01 。因此它不是一个回文数。
提示:
进阶:
你能不将整数转为字符串来办理这个题目吗?
方法一:反转整个数字
- 特别环境处理:负数直接返回 false。
- 反转整个数字:通过循环取出原数字的每一位,渐渐构建反转后的数字。使用 long 范例存储反转效果以避免溢出。
- 比力效果:反转后的数字与原数字相称则为回文数。
代码实现(Java):
- public boolean isPalindrome(int x) {
- if (x < 0) return false;
- long reversed = 0;
- int original = x;
- while (x != 0) {
- reversed = reversed * 10 + x % 10;
- x /= 10;
- }
- return reversed == original;
- }
复制代码 复杂度分析:
- 时间复杂度:O(log n),其中 n 是输入整数 x 的位数。反转过程必要遍历每一位。
- 空间复杂度:O(1),仅使用常数级别的额外空间。
方法二:反转一半数字
- 特别环境处理:
- 负数直接返回 false。
- 末端为 0 的非零数不可能是回文数。
- 反转后半部门:在反转过程中,当反转数大于或等于剩余原数字时停止,此时已处理了一半位数。
- 判断回文:
- 偶数位数:反转数等于剩余原数字。
- 奇数位数:反转数除以 10 后等于剩余原数字(忽略中心位)。
代码实现(Java):
- public boolean isPalindrome(int x) {
- if (x < 0 || (x % 10 == 0 && x != 0)) return false;
- int reversed = 0;
- while (x > reversed) {
- reversed = reversed * 10 + x % 10;
- x /= 10;
- }
- return x == reversed || x == reversed / 10;
- }
复制代码 复杂度分析:
- 时间复杂度:O(log n),最多反转一半位数。
- 空间复杂度:O(1),仅使用常数级别的额外空间。
10. 正则表达式匹配
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
- '.' 匹配恣意单个字符
- '*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s 的,而不是部门字符串。
示例 1:
- 输入:s = "aa", p = "a"
- 输出:false
复制代码 解释:“a” 无法匹配 “aa” 整个字符串。
示例 2:
- 输入:s = "aa", p = "a*"
- 输出:true
复制代码 解释:由于 ‘*’ 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 ‘a’。因此,字符串 “aa” 可被视为 ‘a’ 重复了一次。
示例 3:
- 输入:s = "ab", p = ".*"
- 输出:true
复制代码 解释:"." 表示可匹配零个或多个('‘)恣意字符(’.')。
提示:
- 1 <= s.length <= 20
- 1 <= p.length <= 20
- s 只包罗从 a-z 的小写字母。
- p 只包罗从 a-z 的小写字母,以及字符 . 和 *。
- 包管每次出现字符 * 时,前面都匹配到有用的字符
方法:动态规划
- 状态界说:
- dp[j] 表示字符串 s 的前 i 个字符和模式 p 的前 j 个字符是否匹配。
- 界限条件:
- dp[0][0] = true:空字符串和空模式匹配。
- 处理模式 p 匹配空字符串的环境(如 a*b* 等布局)。
- 状态转移:
- 当模式字符是 * 时:
- 匹配0次:忽略当前 * 和前一个字符,状态继续自 dp[j-2]。
- 匹配多次:当前字符需与前一个模式字符匹配,且前序状态 dp[i-1][j] 为真。
- 平凡字符或.匹配:当前字符必须匹配,且前序状态 dp[i-1][j-1] 为真。
代码实现(Java):
- class Solution {
- public boolean isMatch(String s, String p) {
- int m = s.length(), n = p.length();
- boolean[][] dp = new boolean[m + 1][n + 1];
- dp[0][0] = true;
- // 处理模式串p前j个字符匹配空字符串的情况(如a*b*c*)
- for (int j = 1; j <= n; j++) {
- if (p.charAt(j - 1) == '*') {
- dp[0][j] = dp[0][j - 2];
- }
- }
- for (int i = 1; i <= m; i++) {
- for (int j = 1; j <= n; j++) {
- char sc = s.charAt(i - 1);
- char pc = p.charAt(j - 1);
- if (pc == '*') {
- char prev = p.charAt(j - 2);
- // 匹配0次(跳过当前*和前一个字符)或匹配多次
- boolean matchZero = dp[i][j - 2];
- boolean matchMore = (prev == '.' || prev == sc) && dp[i - 1][j];
- dp[i][j] = matchZero || matchMore;
- } else if (pc == '.' || pc == sc) {
- // 当前字符精确匹配
- dp[i][j] = dp[i - 1][j - 1];
- }
- // 否则保持dp[i][j]为false
- }
- }
- return dp[m][n];
- }
- }
复制代码 复杂度分析
- 时间复杂度:O(m*n),其中 m 和 n 分别是字符串和模式的长度。
- 空间复杂度:O(m*n),用于存储动态规划表。可优化至 O(n) 使用滚动数组。
声明
- 本文版权归 CSDN 用户 Allen Wurlitzer 所有,遵照CC-BY-SA协议发布,转载请注明出处。
- 本文题目泉源 力扣-LeetCode ,著作权归领扣网络所有。贸易转载请联系官方授权,非贸易转载请注明出处。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |