leetcode刷题记录day009:28和8

28、难度简单:通过率40%

说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。

题给施舍:haystackneedle 仅由小写英文字符组成

方法一:KMP算法

使用枚举的朴素解法,不考虑剪枝的话复杂度是 O(m * n) 的,而 KMP 算法的复杂度为 O(m + n)
KMP 之所以能够在 O(m + n) 复杂度内完成查找,是因为其能在「非完全匹配」的过程中提取到有效信息进行复用,以减少「重复匹配」的消耗。
KMP原理在这里不进行描述,可以在b站搜索相关解释视频。
或者点击下方链接看代码实现过程:
作者:AC_OIer
链接:https://leetcode-cn.com/problems/implement-strstr/solution/shua-chuan-lc-shuang-bai-po-su-jie-fa-km-tb86/

class Solution {
    // KMP 算法
    // ss: 原串(string)  pp: 匹配串(pattern)
    public int strStr(String ss, String pp) {
        if (pp.isEmpty()) return 0;
        
        // 分别读取原串和匹配串的长度
        int n = ss.length(), m = pp.length();
        // 原串和匹配串前面都加空格,使其下标从 1 开始
        ss = " " + ss;
        pp = " " + pp;

        char[] s = ss.toCharArray();
        char[] p = pp.toCharArray();

        // 构建 next 数组,数组长度为匹配串的长度(next 数组是和匹配串相关的)
        int[] next = new int[m + 1];
        // 构造过程 i = 2,j = 0 开始,i 小于等于匹配串长度 【构造 i 从 2 开始】
        for (int i = 2, j = 0; i <= m; i++) {
            // 匹配不成功的话,j = next(j)
            while (j > 0 && p[i] != p[j + 1]) j = next[j];
            // 匹配成功的话,先让 j++
            if (p[i] == p[j + 1]) j++;
            // 更新 next[i],结束本次循环,i++
            next[i] = j;
        }

        // 匹配过程,i = 1,j = 0 开始,i 小于等于原串长度 【匹配 i 从 1 开始】
        for (int i = 1, j = 0; i <= n; i++) {
            // 匹配不成功 j = next(j)
            while (j > 0 && s[i] != p[j + 1]) j = next[j];
            // 匹配成功的话,先让 j++,结束本次循环后 i++
            if (s[i] == p[j + 1]) j++;
            // 整一段匹配成功,直接返回下标
            if (j == m) return i - m;
        }

        return -1;
    }
}
方法二:Sunday算法

其核心思想是:在匹配过程中,模式串并不被要求一定要按从左向右进行比较还是从右向左进行比较,它在发现不匹配时,算法能跳过尽可能多的字符以进行下一步的匹配,从而提高了匹配效率。

记模式串为S,子串为T,长度分别为N,M。
对于T,我们做一个简单而巧妙的预处理:记录T中每一种字符最后出现的位置,将其存入一个数组中。
Sunday算法思想跟BM算法很相似,在匹配失败时关注的是文本串中参加匹配的最末位字符的下一位字符。如果该字符没有在匹配串中出现则直接跳过,即移动步长= 匹配串长度+1
(从原先的匹配位置 + 匹配串长度到达了“最末位字符的下一位字符”,但其不在匹配串中,所以再+1);
(因为当前位置开始匹配会匹配失败,假设我们从当前位置的下一个位置开始匹配就恰好能匹配成功,那么参加本次失败匹配的最末位字符的下一位字符肯定会出现在模式串中,若不存在那假设不成立,所以我们直接 + 1,从当前位置 + 匹配串长度 + 1 位置开始从头匹配)
如果该字符在匹配串中出现,同BM算法一样其移动步长=匹配串中最右端的该字符到末尾的距离+1
(文本串中与匹配串的“第一个比较位置”的移动位数 = 模式串长度 - 该字符最右出现的位置(以0开始) = 模式串中该字符最右出现的位置到尾部的距离 + 1)
(这一块难以理解的话就看下面的例子)

算法举例:图片来源于https://blog.csdn.net/u013001763/article/details/69397504
A是文本串,B是匹配串。开始时指针分别指向各自首位
在这里插入图片描述

第一个元素就互不相同且 t 不在B中,当前匹配的最末位字符的下一位字符是 i ,i 在B中所以下一个“第一个比较位置”是在元素 t 位置后“模式串长度 - 该字符最右出现的位置”=4-1=3距离,也就是红指针从 t 元素位置移动 3 距离到达 s 处(这样恰好能让A的元素 i 和B的元素 i 对应)如下图
在这里插入图片描述

此时s和c 不相同,当前匹配的最末位字符的下一位字符是 b ,b不在B中所以直接跳到b的下一位元素 i 作为“第一个比较位置“开始比较(也就是从 s 位置增加距离 模式串长度+1 = 4+1=5 到 i 位置)。如下图
在这里插入图片描述

仍然不同,当前匹配的最末位字符的下一位字符是 t ,t 在B中所以下一个“第一个比较位置”是在元素 i 位置后“模式串长度 - 该字符最右出现的位置”=4-2=2距离,也就是红指针从 i 元素位置移动 2 距离到达 c 处。如下图
在这里插入图片描述

class Solution {
       public int strStr(String haystack, String needle) {
        int n = haystack.length(), m = needle.length();
        if (m == 0) return 0;
        if (m > n) return -1;
        char[] haystacks = haystack.toCharArray();
        char[] needles = needle.toCharArray();
        Map<Character, Integer> shiftTable = new HashMap<>();
        for (int i = 0; i < m; i++) {
            //这里使用的是:模式串中该字符最右出现的位置到尾部的距离 + 1:用这个作为 偏移表[c]
            shiftTable.put(needles[i], m - i);
        }

        int i = 0;
        //循环结束条件 idx + len(pattern) > len(String) 
        while (i + m <= n) {
            int k = 0;
            //开始对比字符是否相同
            while (k < m && needles[k] == haystacks[i + k]) {
                k++;
            }
            //若一路顺利 k 等于 m 全部字符匹配相同就直接返回正确下标
            if (k == m) return i;
            else {
                //若出现了不同字符:如果i + m < n那还符合匹配的基本条件,若不满足就直接返回-1无正确结果
                //符合条件情况下,若shiftTable.get(haystacks[i + m]) == null,也就是当前匹配的最末位字符的下一位字符
                //不在模式串中,那当前字符位置 i 就移动距离m + 1(模式串长度+1)
                //若在模式串中,那就...
                if (i + m < n) i += shiftTable.get(haystacks[i + m]) == null ? m + 1 : shiftTable.get(haystacks[i + m]);
                else return -1;
            }
        }

        return -1;
    }  
}

上述代码运算部分的逻辑为:
每次匹配都会从 目标字符串中 提取 待匹配字符串与 模式串 进行匹配:
若匹配,则返回当前 idx (当前字符位置)
不匹配,则查看 待匹配字符串 的后一位字符 c:
若c存在于Pattern中,则 idx = idx + 偏移表[c]
否则,idx = idx + len(pattern)
Repeat Loop 直到 idx + len(pattern) > len(String) (当前字符位置 + 匹配串长度 > 文本串)

方法三:horspool算法,详细自己可以去了解。和sunday在算法上差不多

class Solution {
public static int strStr(String haystack, String needle) {
        if (needle.equals("")) return 0;
        if (needle.length() > haystack.length()) return -1;
        Map<Character, Integer> shiftTable = new HashMap<>();
        char[] haystacks = haystack.toCharArray();
        char[] needles = needle.toCharArray();
        int n = haystacks.length, m = needles.length;
	    //从第一个元素开始遍历,记录T中每一种字符最后出现的位置,将其存入一个哈希表中。
        for (int i = 0; i < needles.length - 1; i++) {
            //第一个参数是字符,第二个参数是该字符的出现位置
            //假设在1位置记录了字符a,但是在3位置又出现了a,此时会刷新位置记录,这样就达到了最右位置的记录
            //这里使用的是:模式串中该字符最右出现的位置到尾部的距离 + 1:用这个作为 偏移表[c]
            shiftTable.put(needles[i], needles.length - 1 - i);
        }
		//开始时i指向匹配串的末尾(从0开始)
        int i = m - 1; 
        while (i < n) { 
            int k = 0; 
            while (k < m && needles[m - k - 1] == haystacks[i - k]) {
                k++;
            }
            if (k == m) return i - m + 1;
            else i += shiftTable.get(haystacks[i]) == null ? m : shiftTable.get(haystacks[i]);
        }

        return -1;
    }
}

8、难度中等:本题通过率只有20%左右

本题要求:如果难以理解,可以直接看题目给的示例,把所有情况都涵盖到了。
1、读入字符串并丢弃无用的前导空格
2、检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
3、读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
(例:①“123word”在读入到第一个非数字字符w后,就把w及往后的部分忽略掉。②“wer 123”中非数字字符在最前面,所以直接忽略往后所有,等于没有读入任何数字,直接判定为0)
4、将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
5、如果整数数超过 32 位有符号整数范围 [−2^31, 2^31 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −2^31 的整数应该被固定为 −2^31 ,大于 2^31 − 1 的整数应该被固定为 2^31 − 1 。
返回整数作为最终结果。

注意:
本题中的空白字符只包括空格字符 ' '
除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符
0 <= s.length <= 200
s 由英文字母(大写和小写)、数字(0-9)、' ''+''-''.' 组成
题目要求将字符串转化为整数,所以不需要小数

代码和解析参考如下链接:

作者:liweiwei1419
链接:https://leetcode-cn.com/problems/string-to-integer-atoi/solution/jin-liang-bu-shi-yong-ku-han-shu-nai-xin-diao-shi-/

//时间复杂度:O(N) 空间复杂度:O(1)
public class Solution {

    public int myAtoi(String str) {
        int len = str.length();
        // str.charAt(i) 方法回去检查下标的合法性,一般先转换成字符数组(.toCharArray()方法)
        char[] charArray = str.toCharArray();

        // 1、去除前导空格
        int index = 0;
        
        //只要index不到len就一直遍历,除非当前字符不是空格。这样可以为下面"    "多个空格情况做出判断
        while (index < len && charArray[index] == ' ') { 
            index++;
        }

        // 2、如果已经遍历完成(针对极端用例 "      ")
        if (index == len) {
            return 0;
        }

        // 3、如果出现符号字符,仅第 1 个有效,并记录正负
        int sign = 1;
        char firstChar = charArray[index];
        if (firstChar == '+') {
            index++;
        } else if (firstChar == '-') {
            index++;
            sign = -1;
        }

        // 4、将后续出现的数字字符进行转换
        // 不能使用 long 类型,这是题目说的
        int res = 0;
        while (index < len) {
            char currChar = charArray[index];
            // 4.1 先判断不合法的情况
            if (currChar > '9' || currChar < '0') {
                break;
            }

            // 题目中说:环境只能存储 32 位大小的有符号整数,因此,需要提前判:断乘以 10 以后是否越界
            //Integer.MAX_VALUE是指极限数字除以10的结果,这个数字*10后与极限数字间就相差了Integer.MAX_VALUE % 10
            //Integer.MAX_VALUE % 10 = 7,因为%10意味着舍弃个位数以上的所有数
            if (res > Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE / 10 && (currChar - '0') > Integer.MAX_VALUE % 10)) {
                return Integer.MAX_VALUE;
            }
            if (res < Integer.MIN_VALUE / 10 || (res == Integer.MIN_VALUE / 10 && (currChar - '0') > -(Integer.MIN_VALUE % 10))) {
                return Integer.MIN_VALUE;
            }

            // 4.2 合法的情况下,才考虑转换,每一步都把符号位乘进去
            res = res * 10 + sign * (currChar - '0');
            index++;
        }
        return res;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        String str = "2147483646";
        int res = solution.myAtoi(str);
        System.out.println(res);

        System.out.println(Integer.MAX_VALUE);
        System.out.println(Integer.MIN_VALUE);
    }
}

这个问题其实没有考察算法的知识,模拟的是日常开发中对于原始数据的处理(例如「参数校验」等场景)
其实很多时候,业务需求就是类似这样的问题,工作中如果遇到:
1、有现成的工具和类库需尽量使用,因为它们是性能更优,且经过更严格测试,是相对可靠的;
2、能抽取成工具类、工具方法的尽量抽取,以突出主干逻辑、方便以后代码复用;
3、不得不写得比较繁琐、冗长的时候,需要写清楚注释、体现逻辑层次,以便上线以后排查问题和后续维护。

几个要点:
1、根据示例 1,需要去掉前导空格;
2、根据示例 2,需要判断第 1 个字符为 + 和 - 的情况,因此,可以设计一个变量 sign,初始化的时候为 1,如果遇到 - ,将 sign 修正为 -1;
3、判断是否是数字,可以使用字符的 ASCII 码数值进行比较,即 0 <= c <= ‘9’;
4、根据示例 3 和示例 4 ,在遇到第 1 个不是数字的字符的情况下,转换停止,退出循环;
5、根据示例 5,如果转换以后的数字超过了 int 类型的范围,需要截取。这里不能将结果 res 变量设计为 long 类型,

注意:由于输入的字符串转换以后也有可能超过 long 类型,因此需要在循环内部就判断是否越界,

只要越界就退出循环,这样也可以减少不必要的计算;
6、由于涉及下标访问,因此全程需要考虑数组下标是否越界的情况。
7、由于题目中说「环境只能保存 32 位整数」,因此这里在每一轮循环之前先要检查乘以 1010 以后是否溢出,具体细节请见编码。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeYello

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值