• 怒刷LeetCode的第4天(Java版)


    #【中秋征文】程序人生,中秋共享#

    目录

    第一题

    题目来源

    题目内容

    解决方法

    方法一:遍历字符串

    方法二:有限状态机(Finite State Machine)

    方法三:正则表达式

    第二题

    题目来源

    题目内容

    解决方法

    方法一:反转数字比较

    第三题

    题目来源

    题目内容

    解决方法

    方法一:动态规划

    方法二:递归回溯


    第一题

    题目来源

    8. 字符串转换整数 (atoi) - 力扣(LeetCode)

    题目内容

    解决方法

    方法一:遍历字符串

    题目要求实现一个字符串转换整数的函数,即将给定字符串转换成一个32位有符号整数。

    解题思路如下:

    1. 去除字符串开头的空格。

    2. 判断接下来的字符是否为正号或者负号,如果是则记录符号并向后移动一位。

    3. 遍历剩余的字符串字符,直到遇到非数字字符为止。将遇到的数字字符转换成整数,并累加到结果中。

    4. 根据符号和结果判断最终返回的整数值。

    5. 注意处理越界情况,如果超出32位有符号整数的取值范围,则返回边界值。

    1. class Solution {
    2. public int myAtoi(String s) {
    3. // 去除前导空格
    4. s = s.trim();
    5. // 处理空字符串和只有正负号的情况
    6. if (s.length() == 0 || (s.length() == 1 && (s.charAt(0) == '+' || s.charAt(0) == '-'))) {
    7. return 0;
    8. }
    9. // 判断第一个非空字符是否为正负号
    10. boolean positive = true;
    11. int i = 0;
    12. if (s.charAt(0) == '+' || s.charAt(0) == '-') {
    13. if (s.charAt(0) == '-') {
    14. positive = false;
    15. }
    16. i++;
    17. }
    18. // 提取连续的数字字符并转换为整数
    19. long result = 0; // 使用长整型,防止溢出
    20. while (i < s.length() && Character.isDigit(s.charAt(i))) {
    21. int digit = s.charAt(i) - '0';
    22. result = result * 10 + digit;
    23. // 判断是否超出范围
    24. if (positive && result > Integer.MAX_VALUE) {
    25. return Integer.MAX_VALUE;
    26. } else if (!positive && result * -1 < Integer.MIN_VALUE) {
    27. return Integer.MIN_VALUE;
    28. }
    29. i++;
    30. }
    31. // 根据正负号返回最终结果
    32. return positive ? (int)result : (int)(result * -1);
    33. }
    34. }

    这个函数首先去除字符串的前导空格,然后判断字符串是否为空或只有正负号的情况,如果是,则返回0。接下来,判断第一个非空字符是正号还是负号,并将其处理为正负标志。然后,遍历字符串的剩余部分,将连续的数字字符转换为整数,并判断是否超出范围。最后,根据正负标志返回最终结果。 

    下面是该解决方案的复杂度分析:

    • 时间复杂度:O(n),其中 n 是字符串的长度。在算法中,我们只遍历字符串一次。
    • 空间复杂度:O(1)。除了存储结果之外,算法使用的额外空间是固定大小的变量,并不随输入的规模而改变。

    需要注意的是,在判断是否超出范围时,我们使用了长整型变量 result,而不是直接使用整型变量。这是因为如果 result 溢出 32 位有符号整数的范围,将无法正确判断溢出。所以我们将连续数字字符转换为长整型,然后再判断是否超出范围,并根据正负号返回最终结果。

    LeetCode运行结果:

    方法二:有限状态机(Finite State Machine)

    还有其他方法可以解决这个问题。一种常见的方法是使用有限状态机(Finite State Machine)。有限状态机的基本思想是根据输入的字符和当前状态进行状态转移,最终得到结果。

    1. class Solution {
    2. public int myAtoi(String s) {
    3. Automaton automaton = new Automaton();
    4. for (char c : s.toCharArray()) {
    5. if (!automaton.process(c)) {
    6. break;
    7. }
    8. }
    9. return (int) (automaton.sign * automaton.result);
    10. }
    11. class Automaton {
    12. public int sign = 1; // 符号,默认为正号
    13. public long result = 0; // 结果,默认为0
    14. private String state = "start"; // 初始状态
    15. private Map transitionTable = new HashMap<>(){{
    16. put("start", new String[]{"start", "signed", "inNumber", "end"});
    17. put("signed", new String[]{"end", "end", "inNumber", "end"});
    18. put("inNumber", new String[]{"end", "end", "inNumber", "end"});
    19. put("end", new String[]{"end", "end", "end", "end"});
    20. }};
    21. public boolean process(char c) {
    22. state = transitionTable.get(state)[getStateIndex(c)];
    23. if ("inNumber".equals(state)) {
    24. result = result * 10 + (c - '0');
    25. result = sign == 1 ? Math.min(result, Integer.MAX_VALUE) : Math.min(result, -1L * Integer.MIN_VALUE);
    26. } else if ("signed".equals(state)) {
    27. sign = c == '+' ? 1 : -1;
    28. } else if ("end".equals(state)) {
    29. return false;
    30. }
    31. return true;
    32. }
    33. private int getStateIndex(char c) {
    34. if (c == ' ') {
    35. return 0;
    36. }
    37. if (c == '+' || c == '-') {
    38. return 1;
    39. }
    40. if (Character.isDigit(c)) {
    41. return 2;
    42. }
    43. return 3;
    44. }
    45. }
    46. }

    在这个解决方案中,我们使用了一个包含四个状态的有限状态机。根据当前状态和输入字符的类型,我们进行状态转移,并更新符号和结果。具体的状态转移规则可以在 transitionTable 中定义。

    这个解决方案的时间复杂度为 O(n),空间复杂度为 O(1)。

    LeetCode运行结果:

    方法三:正则表达式

    正则表达式是一种强大的模式匹配工具,可以用来匹配和提取字符串中的特定模式。

    1. import java.util.regex.Pattern;
    2. import java.util.regex.Matcher;
    3. class Solution {
    4. public int myAtoi(String s) {
    5. String pattern = "^\\s*([+-]?\\d+)";
    6. Pattern regex = Pattern.compile(pattern);
    7. Matcher matcher = regex.matcher(s);
    8. if(matcher.find()) {
    9. try {
    10. long num = Long.parseLong(matcher.group(1));
    11. if (num > Integer.MAX_VALUE) {
    12. return Integer.MAX_VALUE;
    13. } else if (num < Integer.MIN_VALUE) {
    14. return Integer.MIN_VALUE;
    15. } else {
    16. return (int) num;
    17. }
    18. } catch (NumberFormatException e) {
    19. // 处理超出整数范围的情况
    20. if (matcher.group(1).charAt(0) == '-') {
    21. return Integer.MIN_VALUE;
    22. } else {
    23. return Integer.MAX_VALUE;
    24. }
    25. }
    26. }
    27. return 0; // 如果无法匹配数字,则返回0
    28. }
    29. }

    在这个解决方案中,我们使用了正则表达式 ^\\s*([+-]?\\d+) 来匹配字符串中的数字。首先,使用 Pattern 类编译正则表达式,并使用 Matcher 类对输入字符串进行匹配。如果在字符串中找到一个或多个数字序列,我们尝试将其转换为长整型 num。然后,我们检查 num 是否超出了整数的范围,并根据结果返回相应的值。

    这个解决方案的时间复杂度取决于正则表达式的匹配速度,通常为 O(n)。空间复杂度为 O(1),因为只使用了有限的变量来存储结果。

    LeetCode运行结果:

    第二题

    题目来源

    9. 回文数 - 力扣(LeetCode)

    题目内容

    解决方法

    方法一:反转数字比较

    1. class Solution {
    2. public boolean isPalindrome(int x) {
    3. // 处理特殊情况,负数和以0结尾的非零数不可能是回文数
    4. if (x < 0 || (x % 10 == 0 && x != 0)) {
    5. return false;
    6. }
    7. int reverse = 0;
    8. int original = x;
    9. while (x > 0) {
    10. int digit = x % 10;
    11. reverse = reverse * 10 + digit;
    12. x /= 10;
    13. }
    14. return original == reverse;
    15. }
    16. }

    在这个解决方案中,我们首先处理了一些特殊情况:负数和以0结尾的非零数不可能是回文数。然后,我们使用一个循环,将输入的数字逐位反转并保存到变量 reverse 中。最后,我们检查反转后的数字是否等于原始数字 x。

    这个方法的时间复杂度是 O(log n),其中 n 是 x 的位数。空间复杂度是 O(1)。

    LeetCode运行结果:

    第三题

    题目来源

    10. 正则表达式匹配 - 力扣(LeetCode)

    题目内容

    解决方法

    方法一:动态规划

    这是一个经典的正则表达式匹配问题,可以使用动态规划来解决。具体的思路如下:

    1. 定义一个二维布尔数组 dp,其中 dp[i][j] 表示 s 的前 i 个字符是否与 p 的前 j 个字符匹配。
    2. 初始化 dp[0][0] 为 true,表示空字符可以匹配。
    3. 对于第一行 dp[0][j]:如果 p[j-1] 是 '',则可以把 p[j-2] 和 '' 一起消去,即 dp[0][j] = dp[0][j-2];否则为 false。
    4. 对于其他行 dp[i][j]:
      • 如果 p[j-1] 是字母或者 '.',则判断当前字符能否匹配,即 dp[i][j] = dp[i-1][j-1];
      • 如果 p[j-1] 是 '*',有两种情况:
        • "表示匹配零次前面的元素,即将 p[j-2] 和 "一起消去,即 dp[i][j] = dp[i][j-2];
        • '*' 表示匹配一次或多次前面的元素,即当前字符与 p[j-2] 匹配且前一个字符匹配(dp[i-1][j])。

    最后,返回 dp[s.length()][p.length()]。

    1. class Solution {
    2. public boolean isMatch(String s, String p) {
    3. int m = s.length();
    4. int n = p.length();
    5. // 创建并初始化 dp 数组
    6. boolean[][] dp = new boolean[m + 1][n + 1];
    7. dp[0][0] = true; // 空字符串和空模式匹配
    8. // 处理边界情况,s 为空字符串时的匹配
    9. for (int j = 1; j <= n; j++) {
    10. if (p.charAt(j - 1) == '*') {
    11. dp[0][j] = dp[0][j - 2]; // '*' 可以将前面的字符匹配 0 次
    12. }
    13. }
    14. // 动态规划求解
    15. for (int i = 1; i <= m; i++) {
    16. for (int j = 1; j <= n; j++) {
    17. // 如果当前字符匹配,则和上一个字符的匹配结果一致
    18. if (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '.') {
    19. dp[i][j] = dp[i - 1][j - 1];
    20. } else if (p.charAt(j - 1) == '*') {
    21. // 如果遇到 '*' 字符,有两种情况:
    22. // 1. '*' 匹配了前一个字符 0 次,则和 p 的前两个字符匹配结果一致
    23. // 2. '*' 匹配了前一个字符多次,则当前字符和前面的模式字符串匹配,
    24. // 并且和上一个字符的匹配结果一致
    25. dp[i][j] = dp[i][j - 2]; // 匹配 0 次的情况
    26. if (p.charAt(j - 2) == '.' || p.charAt(j - 2) == s.charAt(i - 1)) {
    27. dp[i][j] = dp[i][j] || dp[i - 1][j];
    28. }
    29. }
    30. }
    31. }
    32. return dp[m][n];
    33. }
    34. }

    这个算法的时间复杂度是 O(m * n),其中 m 是字符串 s 的长度,n 是模式 p 的长度。空间复杂度是 O(m * n)。 

    LeetCode运行结果:

    方法二:递归回溯

    除了动态规划,还可以使用递归回溯的方法来解决。具体的思路是从左到右依次匹配字符,对于每个字符,有如下几种情况:

    1. 如果当前字符和模式字符相同,或者模式字符是 '.',则继续匹配下一个字符。
    2. 如果模式字符是 '*',有两种情况:
      • 模式字符和前一个字符不匹配,并且当前字符也和模式字符的前一个字符不匹配,则只能将 '*' 视为匹配 0 次前面的字符,继续匹配后面的字符。
      • 其他情况下,可以将 '*' 视为匹配 0 次、1 次或多次前面的字符,然后继续匹配下一个字符。

    如果能够成功匹配到最后一个字符,则字符串和字符规律匹配成功。

    1. class Solution {
    2. public boolean isMatch(String s, String p) {
    3. if (p.isEmpty()) {
    4. return s.isEmpty();
    5. }
    6. boolean firstMatch = !s.isEmpty() && (s.charAt(0) == p.charAt(0) || p.charAt(0) == '.');
    7. if (p.length() >= 2 && p.charAt(1) == '*') {
    8. return isMatch(s, p.substring(2)) || (firstMatch && isMatch(s.substring(1), p));
    9. } else {
    10. return firstMatch && isMatch(s.substring(1), p.substring(1));
    11. }
    12. }
    13. }

    复杂度分析:

    • 时间复杂度:最坏情况下,对于每个字符都存在两种选择(匹配 0 次或多次),因此时间复杂度是指数级的,即 O(2^(m + n)),其中 m 和 n 分别是字符串 s 和模式 p 的长度。
    • 空间复杂度:递归过程中需要消耗额外的栈空间,最坏情况下递归深度是字符串 s 和模式 p 的长度的和,因此空间复杂度为 O(m + n)。

    综上所述,动态规划方法在时间和空间复杂度上都优于递归回溯方法。在实际应用中,建议使用动态规划方法来解决这个问题,尤其是在输入规模较大时。

    LeetCode运行结果:

  • 相关阅读:
    frontend webstorm plugin:插件推荐
    代数与逻辑:作业三 贝叶斯决策
    安杰思医学冲刺科创板:​年营收3亿 拟募资7.7亿
    C# OpenCvSharp 去除字母后面的杂线
    C++中构造函数什么时候会被调用(从本质上理解)
    Linux运维实战:CentOS7.6操作系统从入门到精通(16-18)
    深入Java微服务之网关系列1:什么是网关
    ethercat EOE arp
    “它经济”盛行,宠物食品行业如何做好口碑营销
    【附源码】计算机毕业设计JAVA校园绿化管理系统
  • 原文地址:https://blog.csdn.net/m0_74293254/article/details/132940844