面试经典150题0425

本文介绍了LeetCode中的几道经典面试题,包括双指针法解决接雨水问题,罗马数字转换,整数转罗马数字,字符串操作如最后一个单词长度、最长公共前缀和单词反转,以及字符串处理中的Z字形变换和KMP算法的应用。
摘要由CSDN通过智能技术生成

面试经典150题0425

Leetcode042 接雨水

image-20240425132307820

双指针

设置左右指针,分别指向左边界和右边界操作的列。

设置变量leftMaxrightMax代表左边遍历过的最大高度和右边遍历过的最大高度。

另外,最左边和最右边的列不能存水。

如果leftMax < rightMax,说明当前左指针的右边至少有一个板子,其高度大于左指针及遍历过的左边所有位置;根据木桶效应,左指针当前列的储水量的决定权在左边,即leftMax - height[left]

如果leftMax >= rightMax,那么右指针当前列的储水量的决定权在右边,即rightMax - height[right]

public static int trap(int[] height){
    int n = height.length;
    int res = 0;
    // 定义左右指针
    int left = 0, right = n - 1;
    int leftMax = height[left];
    int rightMax = height[right];
    // 最左边和最右边的列不能储水
    left++;
    right--;
    while (left <= right){
        leftMax = Math.max(leftMax, height[left]);
        rightMax = Math.max(rightMax, height[right]);
        if(leftMax < rightMax){
            res += leftMax - height[left];
            left++;
        }
        else {
            res += rightMax - height[right];
            right--;
        }
    }
    return res;
}
Leetcode013 罗马数字转整数
public static int romanToInt(String s){
    Map<Character, Integer> map = new HashMap<>();
    map.put('I', 1);
    map.put('V', 5);
    map.put('X', 10);
    map.put('L', 50);
    map.put('C', 100);
    map.put('D', 500);
    map.put('M', 1000);
    int ptr = 0;
    int res = 0;
    while (ptr < s.length()){
        char tmp = s.charAt(ptr);
        // V L D M 放在前面不涉及特例
        if(tmp == 'V' || tmp == 'L' || tmp == 'D' || tmp == 'M'){
            res += map.get(tmp);
            ptr++;
        }
        else {
            // 尝试读取下一个字符
            ptr++;
            int a = map.get(tmp);
            if(ptr >= s.length()){
                // 不存在下一个字符,直接将当前字符数值添加并结束循环
                res += map.get(tmp);
                break;
            } else if (map.get(s.charAt(ptr)) / a != 5 && map.get(s.charAt(ptr)) / a != 10) {
                // 根据题目要求,当后一个字符表示数值/当前字符表示数值 != 5 && != 10,不属于特殊情况
                res += a;
            }
            else {
                int b = map.get(s.charAt(ptr));
                // 特殊情况,后面字符数值 - 当前字符数值
                // 另外,指针需要前进两步
                res += b - a;
                ptr++;
            }
        }
    }
    return res;
}
Leetcode012 整数转罗马数字

使用贪心算法,从大到小处理。(类似找零钱)

public String intToRoman(int num) {
    // 把阿拉伯数字与罗马数字可能出现的所有情况和对应关系,放在两个数组中
    // 并且按照阿拉伯数字的大小降序排列
    int[] nums = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
    String[] romans = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
    int index = 0;
    StringBuilder result = new StringBuilder();
    while (index < 13) {
        if (num >= nums[index]) {
            result.append(romans[index]);
            num -= nums[index];
        } else {
            index ++;
        }
    }
    return result.toString();
}
Leetcode058 最后一个单词的长度

从后面遍历,找到第一个非空格元素,记录该位置;然后再继续向左遍历,找到第一个空格元素或者首位置为止。

public static int lengthOfLastWord(String s){
    int right = s.length() - 1;
    while (right >= 0 && s.charAt(right) == ' '){
        right--;
    }
    if(right < 0){
        return 0;
    }
    int left = right - 1;
    while (left >= 0 && s.charAt(left) != ' '){
        left--;
    }
    return right - left;
}
Leetcode014 最长公共前缀

先求出所有所有字符串的最小长度minLen,最长公共前缀长度一定小于等于minLen

纵向比较每个字符串,比较第j个字符串和j-1个字符串在第i个位置的字符是否相同。相同则继续,否则跳出循环,返回结果。

public static String longestCommonPrefix(String[] strs){
    int len = strs.length;
    if(len <= 1)
        return strs[0];
    int minLen = Integer.MAX_VALUE;
    for(String s : strs){
        minLen = Math.min(minLen, s.length());
    }
    StringBuilder sb = new StringBuilder();
    for(int i = 0; i < minLen; i++){
        boolean flag = true;
        for(int j = 1; j < len; j++){
            flag = strs[j-1].charAt(i) == strs[j].charAt(i);
            if(!flag){
                break;
            }
        }
        if(!flag){
            break;
        }
        sb.append(strs[0].charAt(i));
    }
    return sb.toString();
}
Leetcode151 反转字符串中的单词

整体反转+单词反转

注意空格处理

public static String reverseWords(String s) {
    // 整体反转,去掉首尾空格,按照空格进行分割
    String[] strs = new StringBuilder(s).reverse().toString().trim().split(" ");
    StringBuilder res = new StringBuilder();
    for(String str : strs){
        // 中间多个连续空格会造成出现空字符串
        if(str.isEmpty()) {
            continue;
        }
        res.append(new StringBuilder(str).reverse()).append(" ");
    }
    // 返回字符串去除首尾空格
    return res.toString().trim();
}
Leetcode006 Z字形变换
  • numRows==0时,在第一行上每一步需要加1;
  • numRows!=0时,第一行和最后一行每一步需要跳sumJump = 2 * (numRows - 1),对于中间的行,偶数次跳为even = 2 * i,奇数次跳为odd = sumJump - even
  • 然后依次遍历[0, numRows - 1],对获得的字符进行拼接。
public static String convert(String s, int numRows){
    StringBuilder sb = new StringBuilder();
    for(int i = 0; i < numRows; i++){
        // 总跳数
        int sumJump = numRows == 1 ? 1 : 2 * (numRows - 1);
        // 偶次跳数
        int even = 2 * i;
        // 奇次跳数
        int odd = sumJump - even;
        // 以此拼接第i行
        int start = i;
        if(i == 0 || i == numRows - 1){
            while (start < s.length()){
                sb.append(s.charAt(start));
                start += sumJump;
            }
        }
        int cnt = 1;
        while (start < s.length()){
            sb.append(s.charAt(start));
            if(cnt % 2 == 1){
                start += odd;
            }
            else {
                start += even;
            }
            cnt++;
        }
    }
    return sb.toString();
}
Leetcode028 找出字符串中第一个匹配项的下标

KMP算法:能够快速在原字符串中找到匹配字符串

**KMP能够在O(m+n)**复杂度内完成查找,是因为其能在非完全匹配的过程中提取到有效信息进行复用,以减少重复匹配的时间消耗。

两个概念:

  • 前缀:对于字符串abcxxxefg,其中abcabcxxxefg的某个前缀
  • 后缀:对于字符串abcxxxefg,其中efgabcxxxefg的某个后缀

设原串为abeababeabf,匹配串为abeabf

KMP匹配过程:

首先匹配串会检查之前已经匹配成功的部分中是否存在相同的前缀后缀,如果存在,则跳转到前缀的下一位置继续往下匹配。

image-20240425183304005

image-20240425183127130

跳转到下一匹配位置后,尝试匹配ae,发现匹配不上,并且已经匹配上的字符串ab不存在相同的前缀后缀,此时只能回到匹配串的起始位置重新开始匹配。

image-20240425183649910

  • KMP利用已经匹配部分中相同的前缀和后缀来加速下一次的匹配
  • KMP的原串指针不会进行回溯。

分析实现:

扫描完整原串操作是不可避免的,只能去优化**[检查已经匹配部分的相同前缀和后缀]**这一过程。

检查前缀和后缀的目的是为了确定匹配串中的下一段开始匹配的位置

另外,对于匹配串的任意一个位置而言,由该位置发起的下一个匹配点位置其实与原串无关。

例如,对于匹配串abcabd的字符d来说,由它发起的下一个匹配点跳转必然是字符c的位置。因为字符d位置的相同前缀和后缀字符ab的下一位置字符为c

可以看出匹配串某个位置跳转到下一个匹配位置这一过程是与原串无关的,将这一过程称为寻找next

显然我们可以预处理next数组,数组中的每个位置的值是该下标应该跳转的目标位置(next点)

next数组的构建

如下是一个字串的next数组的值,可以看到这个字串的对称度很高,所以next的值都很大。

位置i

位置i0123456789101112131415
前缀next[i]0000123123456740
字串agctagcagctagctg

**对称度:**这里的对称度指的是abcabc

**前缀:**指出了最后一个字符以外,一个字符串的全部头部组合;

**后缀:**指除了第一个字符以外,一个字符串的全部尾部组合;

next的值就是前缀和后缀的最长的共有元素的长度。

ABCDABD为例:

  • "A"的前缀和后缀都为空集,共有元素的长度为0;
  • "AB"的前缀为A,后缀为B,共有元素的长度为0;
  • "ABC"的前缀为A,AB,后缀为BC,C,共有元素的长度为0;
  • "ABCD"的前缀为A, AB, ABC,后缀为BCD, CD, D,共有元素长度为0;
  • "ABCDA"的前缀为A, AB, ABC, ABCD,后缀为BCDA, CDA, DA, A,共有元素长度为1;
  • …………

编程推理过程:

  • 当前字符的前一个字符的对称度为0的时候,只要将当前字符与字串第一个字符进行比较。因为前面都是0,说明都不对称,如果多加一个字符,只能和第一个字符形成对称。例如agcta,其中tnext为0,后面字符a只能和第一个字符对称。

  • 如果前面一个字符的next为1,那么就可以把当前字符与子串第二个字符进行比较,因为前面字符的next为1,说明已经和第一个字符相等了。例如agctag

  • 按照上面推理,如果一直相等,就可以一直累加。

  • 如果遇到不相等的,那么说明不能继承前面的对称性了。需要回头重新找对称性:例如[agctagc][agctagc]t,其中t之前的对称度分别为1,2,3,4,5,6,7;到最后一个字符t没有继承前一个字符的next值,所以这个t的对称性必须重新求。

    • 要找更小的对称,必然在对称内部还存在子对称,而且这个t必须紧接着在子对称之后。

    image-20240425223012845

    参考链接:https://cloud.tencent.com/developer/article/1706743

public static int strStr(String haystack, String needle){
    int[] next = new int[needle.length()];
    nextArray(needle, next);
    for(int i = 0, j = 0; i < haystack.length() && j < needle.length(); i++){
        while (j > 0 && haystack.charAt(i) != needle.charAt(j)){
            // pattern回溯
            // 下一轮使用原串的下一个字符和模式串的next[j-1]位置的字符比较
            j = next[j-1];
        }
        if(haystack.charAt(i) == needle.charAt(j)){
            j++;
        }
        if(j == needle.length()){
            // 找到返回匹配位置
            return i - needle.length() + 1;
        }
    }
    return -1;
}

public static void nextArray(String pattern, int[] next){
    for(int i = 1, j = 0; i < pattern.length(); i++){
        while (j > 0 && pattern.charAt(i) != pattern.charAt(j)){
            // 不相等时,寻找子对称
            // 参考最后一张图
            j = next[j - 1];
        }
        if(pattern.charAt(i) == pattern.charAt(j)){
            j++;
        }
        next[i] = j;
    }
}
  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值