力扣刷题(复习版2)

题目: 回文子串

原题链接: 回文子串
在这里插入图片描述

题解

方法1:暴力(通过了)

  1. 枚举子串

    • 外层循环 i 作为子串的起点,从 0s.length() - 1
    • 内层循环 j 作为子串的终点,从 is.length() - 1,逐步扩展子串。
    • 通过 StringBuilder 逐个字符拼接生成子串 sb
  2. 判断回文

    • 复制 sb 并翻转(temp.reverse())。
    • 判断 sb.toString() 是否等于翻转后的字符串,若相等则 res + 1

时间复杂度: O(n³)

  • O(n²) 遍历所有子串

  • O(n) 检查回文(reverse() 需要 O(n))

    public int countSubstrings(String s) {
        int res = 0;
        for (int i = 0; i < s.length(); i++) {
            StringBuilder sb = new StringBuilder();
            for (int j = i; j < s.length(); j++) {
                sb.append(s.charAt(j));
                StringBuilder temp = new StringBuilder(sb); // 因为reverse是原地修改的,所以需要创建一个副本
                if (sb.toString().equals(temp.reverse().toString())) {
                    res += 1;
                }
            }
        }
        return res;
    }

方法2:中心扩展法
优化效果:

  • 时间复杂度降至 O(n²)

  • 空间复杂度 O(1),不需要额外存储子串

public int countSubstrings(String s) {
    int res = 0;
    for (int i = 0; i < s.length(); i++) {
        // 以 i 为中心,检查奇数长度的回文
        res += expandAroundCenter(s, i, i);
        // 以 i 和 i+1 为中心,检查偶数长度的回文
        if (i + 1 < s.length()) {
            res += expandAroundCenter(s, i, i + 1);
        }
    }
    return res;
}

// 从中心向两边扩展,统计回文子串数量
private int expandAroundCenter(String s, int left, int right) {
    int count = 0;
    while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
        count++; // 每次扩展成功,找到一个回文子串
        left--;
        right++;
    }
    return count;
}

题目: 最长回文子串

原题链接: 最长回文子串
在这里插入图片描述

题解

回文子串的暴力方法改一下,就会超时。

方法1:中心扩展法

    public String longestPalindrome(String s) {
        if (s == null || s.length() == 0) return "";

        int start = 0;
        int maxLength = 1;

        for (int i = 0; i < s.length(); i++) {
            int len1 = expandAroundCenter(s, i, i);
            int len2 = expandAroundCenter(s, i, i + 1);
            int len = Math.max(len1, len2);

            if (len > maxLength) {
                maxLength = len;
                start = i - (len - 1) / 2; // 更新起点
            }
        }
        return s.substring(start, start + maxLength);
    }

    private int expandAroundCenter(String s, int left, int right) {
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            left--;
            right++;
        }
        return right - left - 1;
    }

题目: 计数质数

原题链接: 计数质数
在这里插入图片描述

题解

 public int countPrimes(int n) {
     if (n <= 2) return 0;
     // boolean 数组,初始时假设所有数都是质数
     boolean[] isPrime = new boolean[n];
     Arrays.fill(isPrime, true); // 全部初始化为true

     // 从 2 开始筛选
     for (int i = 2; i * i < n; i++) {
         if (isPrime[i]) {
             // 将 i 的倍数标记为非质数
             for (int j = i * i; j < n; j += i) {
                 isPrime[j] = false;
             }
         }
     }
     // 统计质数的数量
     int count = 0;
     for (int i = 2; i < n; i++) {
         if (isPrime[i]) {
             count++;
         }
     }
     return count;
 }

题目: 基本计算器 II

原题链接: 基本计算器 II
在这里插入图片描述

题解

方法:栈
具体来说,遍历字符串 s,并用变量 preSign 记录每个数字之前的运算符,对于第一个数字,其之前的运算符视为加号。每次遍历到数字末尾时,根据 preSign 来决定计算方式:

  • 加号:将数字压入栈;
  • 减号:将数字的相反数压入栈;
  • 乘除号:计算数字与栈顶元素,并将栈顶元素替换为计算结果。
    public static int calculate(String s) {
        LinkedList<Integer> stack = new LinkedList<>();
        char preSign = '+';// 用于存储前一个操作符,初始为 '+'    
        int num = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (Character.isDigit(c)) {
                num = num * 10 + (c - '0');
            }
            if (!Character.isDigit(c) && s.charAt(i) != ' ' || i == s.length() - 1) {
                if (preSign == '+') {
                    stack.push(num);
                } else if (preSign == '-') {
                    stack.push(-num);
                } else if (preSign == '*') {
                    stack.push(stack.pop() * num);
                } else if (preSign == '/') {
                    stack.push(stack.pop() / num);
                }
                preSign = c;
                num = 0;
            }
        }
        int res = 0;
        for (Integer i : stack) {
            res += i;
        }
        return res;
    }

题目: 基本计算器

原题链接: 基本计算器
在这里插入图片描述

题解

方法:

    public static int calculate(String s) {
        // ops 存储符号状态 (1 表示正号,-1 表示负号)
        Deque<Integer> ops = new LinkedList<Integer>();
        ops.push(1); // 初始符号为 1(正号)
        
        int sign = 1; // sign 表示当前计算的符号状态
        
        int ret = 0;
        int n = s.length();
        int i = 0;

        // 循环遍历整个表达式字符串
        while (i < n) {
            // 跳过空格
            if (s.charAt(i) == ' ') {
                i++;
            } else if (s.charAt(i) == '+') {
                sign = ops.peek();
                i++;
            } else if (s.charAt(i) == '-') {
                sign = -ops.peek();
                i++;
            } else if (s.charAt(i) == '(') {
                ops.push(sign);
                i++;
            } else if (s.charAt(i) == ')') {
                ops.pop();
                i++;
            } else {
                // 如果遇到数字,解析整个数字并将其加到结果 ret 中
                long num = 0;
                while (i < n && Character.isDigit(s.charAt(i))) {
                    num = num * 10 + s.charAt(i) - '0';
                    i++;
                }
                ret += sign * num;
            }
        }
        return ret;
    }
  • 当遇到左括号 ( 时,把当前的符号状态 sign 压入栈中。这样在括号内的表达式会继承括号外的符号状态。
  • 当遇到右括号 ) 时,弹出栈顶符号,恢复括号外的符号状态。

题目-和为 K 的子数组

在这里插入图片描述

题解

解法1:两层for循环

public class T560 {
    public static int subarraySum(int[] nums, int k) {
        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            int tempSum = 0;
            for (int j = i; j < nums.length; j++) {
                tempSum += nums[j];
                if (tempSum == k) {
                    res++;
                }
            }
        }
        return res;
    }

    public static void main(String[] args) {
        int[] nums = {1, -1, 0};
        int k = 0;
        System.out.println(subarraySum(nums, k));
    }
}

解法2:前缀和+哈希

  • 前缀和:前缀和数组 sum[i] 表示从数组起点到位置 i 的元素之和。
  • 哈希表:使用哈希表记录前缀和出现的次数,这样在遍历数组时可以快速找到符合条件的子数组。
public class T560 {
    public static int subarraySum(int[] nums, int k) {
        int res = 0;
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        // 
        int currentSum = 0;
        for (int i = 0; i < nums.length; i++) {
            //sum[i...j]=sum[j]-sum[i-1]
            currentSum += nums[i];
            if (map.containsKey(currentSum - k)) {
                res += map.get(currentSum - k);
            }
            map.put(currentSum, map.getOrDefault(currentSum, 0) + 1);
        }
        return res;
    }

    public static void main(String[] args) {
        int[] nums = {1, -1, 0};
        int k = 0;
        System.out.println(subarraySum(nums, k));
    }
}


详细解释 currentSum - k
设 currentSum 表示当前的前缀和,也就是从数组起点到当前位置的元素之和。我们希望找到一个子数组,使得这个子数组的和等于 k。假设我们当前遍历到数组位置 j,当前的前缀和为 currentSum。

如果存在某个位置 i 使得从 i 到 j 的子数组和等于 k,根据前缀和的定义,有:sum[j]-sum[i-1]=k 其中sum[j]就是currentSum

变形一下:sum[j]-sum[i-1]=k ➡️sum[j]-k=sum[i-1]➡️currentSum-k=sum[i-1]。(因为已知k,不知道sum[i-1] ,所以把k移到等号左边,看map中是否包含sum[i-1],包含则说明ij的和为k


为什么是res += map.get(currentSum - k);而不是res += 1;因为可能存在多个前缀和为currentSum - k。搞懂map存的什么

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值