当前位置 博文首页 > 许诗宇的博客:leetcode-字符串总结

    许诗宇的博客:leetcode-字符串总结

    作者:[db:作者] 时间:2021-09-04 15:31

    leetcode-344-反转字符串(reverse string)-java
    字符串是一个整体,如果对字符串内部进行操作,可以用stringbuilder,也可以将string转为char数组,这个方法更为灵活
    反转的方式可以使用双指针,在前后两头逐个交换,交换后begin++,end–
    交换的时候可以使用O(1)空间存储,也可以像这样使用亦或交换

    leetcode-7-反转整数(reverse integer)-java
    判断一个数是不是超出自己类型的范围,比如int,long等,可以用integer.maxvalue等,也可以对它进行运算,如果不正常就是溢出了
    也可以在每次ans * 10 + pop之前,校验大小
    当出现 ans > MAX_VALUE / 10 且 还有pop需要添加 时,则一定溢出
    当出现 ans =MAX_VALUE / 10 且 pop > 7 时,则一定溢出,7是2^31 - 1的个位数
    当出现 ans < MIN_VALUE / 10 且 还有pop需要添加 时,则一定溢出
    当出现 ans =MAX_VALUE / 10 且 pop < -8 时,则一定溢出,8是-2^31的个位数

    对数字类型颠倒,可以用乘除10,结果每次乘10+数字mod10

    leetcode-387-字符串中的第一个唯一字符(first unique character in a string)-java
    找重复的东西可以用hashmap,如果对查找顺序有要求可以用linkedhashmap,如果对象是固定的比如全是数字,字母,可以用int数组

    有两种模式,
    第一种key为字母,value为字母出现的次数,
    这种适用于找到每个字母出现几次的情况
    如果要找到只出现n次的字母,遍历map找到value=n既可,
    如果要找到第n个出现k次的字母,遍历字符串,遍历的时候查找对应的value=k既可。

    第二种key为字母,value为字母出现的index
    这种情况一般适用于区分字母出现0次,1次,>1次的情况,
    如果要找到出现第n个出现1次或者>1次的字母,遍历map既可

    leetcode-242-有效的字母异位词(valid anagram)-java
    注意:一定要先检查一个情况:两者的length是否相同,不然时间会变长!
    典型的重复检测的图,由于限定26位小位数字母,直接用int【26】,否则用hashmap
    s每有一个字母对应的int[i]加一个,同时t对应的减少一个
    最后查是否全为0

    leetcode-125-验证回文串(valid palindrome)-java
    使用双指针,从前端和后端,挨个找到符合要求的字母
    判断是否是数字和英文字符 . Character.isLetterOrDigit
    字母的小写化 Characte.toLowerCase
    (注意:这个方法A会变成a,其他的a,0,的放进去,不变)

    leetcode-8-字符串转整数 (atoi)(String to Integer)-java
    如果从字符串转为数字,由于不知道长度,可能会超出范围,
    第一种方法,new BigInteger(str),再用这个biginteger与数字的范围比较

    BigInteger resultlong=new BigInteger(result);
    if(resultlong.compareTo(new BigInteger(String.valueOf(Integer.MIN_VALUE)))==-1){
    		return Integer.MIN_VALUE;
    	}
    

    第二种方法,比如int,先检测length是否大于9,否则转为long,因为此时必定在long范围内,再与integer.max_value比较,即可
    第三种方法,也可以在每次ans * 10 + pop之前,校验大小
    当出现 ans > MAX_VALUE / 10 且 还有pop需要添加 时,则一定溢出
    当出现 ans =MAX_VALUE / 10 且 pop > 7 时,则一定溢出,7是2^31 - 1的个位数
    当出现 ans < MIN_VALUE / 10 且 还有pop需要添加 时,则一定溢出
    当出现 ans =MAX_VALUE / 10 且 pop < -8 时,则一定溢出,8是-2^31的个位数

    leetcode-28- 实现strStr()(implement strstr)-java
    查询字符串中另一个字符串的位置时可以说使用kmp算法,原来速度o(nm),现在o(n+m)

    leetcode-38-报数 (count and say)-java
    循环n次,每次查询下一个数,由于数可能很大,必须用string字符串作为参数和结果
    在查询下一个数中,循环这个数的长度次,每次与上一个字符判断是否相同,相同,则num++,否则结果插入num和prev,为几个谁,然后prev和num重置。最后,i==length,则由于没有下一位进行比较,直接插入num,prev

    leetcode-14-最长公共前缀(longest common prefix)-java
    具体方法是先算出所有字符串的最小长度,在最小长度内循环
    每次取第一个字符串的对应字符作基准,如果后面的都与他相同,则小循环后,最后一个对应的字符的index++,返回时直接substring即可

    leetcode-49-字母异位词分组(group anagrams)-java
    算法的关键是:如何将多个字母异位词定位到一个分类,即类似与hash(a)=hash(b)
    这里的方法是,将字符串转为char数组,根据字母大小排序,再转为字符串,得到它的hashcode
    建立一个hashmap<integer,integer>,key为字符串对应的hashcode,value为result里字符串对应的index
    字符串最后加入index对应的那个list中去

    一种是,char数组排序后,生成的字符串作为hash的结果(不对这个生成的字符串hashcode)
    另一种是,用int[26]统计字符串的字母的个数,用字符串 1#2#3… 作为hash的结果,其中的1就代表a的个数,其他类似

    leetcode-3-无重复字符的最长子串(longest substring without repeating characters)-java
    滑动窗口法类似于双指针,两个指针一个对应窗口的开始,一个对应结束。
    每次移动窗口到j(结束),根据j的内容,将i指针(开始)移动到对应位置。
    一般是一步步移动i指针,但如果在map中存放了value为对应index的数据,可能可以直接将i指针移动到确定位置

    优化的滑动窗口
    它可以被进一步优化为仅需要 n 个步骤。我们可以定义字符到索引的映射,而不是使用集合来判断一个字符是否存在。 当我们找到重复的字符时,我们可以立即跳过该窗口。
    也就是说,如果 s[j]在 [i,j) 范围内有与 j′ 重复的字符,我们不需要逐渐增加 i。 我们可以直接跳过 [i,j′] 范围内的所有元素,并将 i变为 j′+1。

    leetcode-5-最长回文子串(longest palindromic substring)-java
    使用manacher算法
    https://blog.csdn.net/xushiyu1996818/article/details/100533618

    leetcode-76-最小覆盖子串-java
    明显是要使用滑动窗口,比较复杂的一点是,滑动窗口的长度是可变的。
    所以区别于固定滑动窗口的达到窗口长度后随着右指针向右移动一位左指针也要移动一位
    滑动窗口更改长度的原因是在滑动窗口中去掉不需要的字符
    使用数组hash存储需要的字符串,根据字符串t,里面hash[ tt[i] -‘0’]++,需要的字符为正数,不需要的为0或者负数。
    使用count记录匹配的字符数,一开始为t.length,如果为0,代表完全匹配,刷新minLength和results
    right每次向右移动一位,hash[ ss[right] -‘0’]- - ,表示需要的字符数减少,如果hash[ ss[right] -‘0’]>=0 代表right是需要的字符。
    然后不断让left向右移hash[ ss[left] -‘0’]<0 ,代表left是不需要的字符,自然可以向右移。
    最后判断count是否为0

    leetcode-647-回文子串-java
    解法1(成功,3ms,极快)
    这是一个比较巧妙的方法,实质的思路和动态规划的思路类似。
    比如对一个字符串 ababa,选择最中间的 a 作为中心点,往两边扩散,第一次扩散发现 left 指向的是 b,right 指向的也是 b,所以是回文串,继续扩散,同理 ababa 也是回文串。
    这个是确定了一个中心点后的寻找的路径,然后我们只要寻找到所有的中心点,问题就解决了。

    中心点一共有多少个呢?看起来像是和字符串长度相等,但你会发现,如果是这样,上面的例子永远也搜不到 abab,想象一下单个字符的哪个中心点扩展可以得到这个子串?似乎不可能。所以中心点不能只有单个字符构成,还要包括两个字符,比如上面这个子串 abab,就可以有中心点 ba 扩展一次得到,所以最终的中心点由 2 * len - 1 个,分别是 len 个单字符和 len - 1 个双字符。

    如果上面看不太懂的话,还可以看看下面几个问题:
    为什么有 2 * len - 1 个中心点?
    aba 有5个中心点,分别是 a、b、c、ab、ba
    abba 有7个中心点,分别是 a、b、b、a、ab、bb、ba

    什么是中心点?
    中心点即 left 指针和 right 指针初始化指向的地方,可能是一个也可能是两个
    为什么不可能是三个或者更多?
    因为 3 个可以由 1 个扩展一次得到,4 个可以由两个扩展一次得到

    解法2(别人的)
    Manacher 算法是在线性时间内求解最长回文子串的算法。在本题中,我们要求解回文串的个数,为什么也能使用 Manacher 算法呢?这里我们就需要理解一下 Manacher 的基本原理。
    Manacher 算法也会面临「方法一」中的奇数长度和偶数长度的问题,它的处理方式是在所有的相邻字符中间插入 #
    比如 abaa 会被处理成 #a#b#a#a#,这样可以保证所有找到的回文串都是奇数长度的,以任意一个字符为回文中心,既可以包含原来的奇数长度的情况,也可以包含原来偶数长度的情况。假设原字符串为 S,经过这个处理之后的字符串为 s。

    我们用 f(i) 来表示以 s 的第 i 位为回文中心,可以拓展出的最大回文半径,那么 f(i)?1 就是以 i 为中心的最大回文串长度 (想一想为什么)。
    Manacher 算法依旧需要枚举 s 的每一个位置并先假设它是回文中心,但是它会利用已经计算出来的状态来更新 f(i),而不是向「中心拓展」一样盲目地拓展。具体地说,假设我们已经计算好了 [1,i?1] 区间内所有点的 f(即我们知道 [1,i?1] 这些点作为回文中心时候的最大半径), 那么我们也就知道了 [1,i?1] 拓展出的回文达到最大半径时的回文右端点。例如 i=4 的时候 f(i)=5f(i) = 5f(i)=5,说明以第 4 个元素为回文中心,最大能拓展到的回文半径是 5,此时右端点为 4+5?1=84 + 5 - 1 = 84+5?1=8。所以当我们知道一个 i 对应的 f(i) 的时候,我们就可以很容易得到它的右端点为 i+f(i)?1。

    Manacher 算法如何通过已经计算出的状态来更新 f(i) 呢?Manacher 算法要求我们维护「当前最大的回文的右端点 r(max)以及这个回文右端点对应的回文中心 i(max)。我们需要顺序遍历 s,假设当前遍历的下标为 i。我们知道在求解 f(i) 之前我们应当已经得到了从 [1,i?1] 所有的 f,并且当前已经有了一个最大回文右端点 r(max)? 以及它对应的回文中心 i(max)。

    初始化 f(i)
    如果 i≤r(max),说明 i 被包含在当前最大回文子串内,假设 j 是 i 关于这个最大回文的回文中心 i(max) 的对称位置(即 j+i=2×i(max)),我们可以得到 f(i) 至少等于 min?{f(j),r(max)}。这里将 f(j) 和 r(max)?i+1 取小,是先要保证这个回文串在当前最大回文串内。(思考:为什么 f(j) 有可能大于 r(max)?i+1?)

    如果 i>r(max),那就先初始化 f(i)=1。
    中心拓展
    做完初始化之后,我们可以保证此时的 s[i+f(i)?1]=s[i?f(i)+1],要继续拓展这个区间,我们就要继续判断 s[i+f(i)] 和 s[i?f(i)] 是否相等,如果相等将 f(i) 自增;这样循环直到 s[i+f(i)]≠s[i?f(i)],以此类推。我们可以看出循环每次结束时都能保证 s[i+f(i)?1]=s[i?f(i)+1],而循环继续(即可拓展的条件)一定是 s[i+f(i)]=s[i?f(i)]。 这个时候我们需要注意的是不能让下标越界,有一个很简单的办法,就是在开头加一个 $,并在结尾加一个 !,这样开头和结尾的两个字符一定不相等,循环就可以在这里终止。
    这样我们可以得到 s 所有点为中心的最大回文半径,也就能够得到 S 中所有可能的回文中心的的最大回文半径,把它们累加就可以得到答案。

    剑指offer-4-替换空格-java
    先计算input中空格的数目count,然后建立大小为length+2*count的新char数组,然后双指针遍历新旧数组,给新数组赋值,遇到旧数组的空格,就新数组设置3个值。

    剑指offer-35-第一个只出现一次的字符-java
    由于题目与字符出现的次数有关,我们可以统计每个字符在该字符串中出现的次数,要达到这个目的,我们需要一个数据容器来存放每个字符出现的次数。在这个容器中可以根据字符来查找它出现的次数,也就是说这个容器的作用就是把一个字符映射称一个数字。在常用的数据容器中,哈希表正是这个用途。

    为了解决这个问题,我们可以定义哈希表的键值(key)是字符,而值(Value)是该字符出现的次数。同时我们还需要从头开始扫描字符串两次。第一次扫描字符串时,每扫描到一个字符就在哈希表中的对应项中把次数加1.接下来第二次扫描时,每扫描到一个字符就能从哈希表中得到该字符出现的次数。这样第一个只出现一次的字符就是符合要求的输出。

    注意:由于题目只要求得到次数为1的字符,可以把value变成字符在字符串的索引,如果碰到第二个了,直接value变成Integer.Max,第二次扫描可以不用扫描字符串,而是扫描hashmap

    剑指offer-42-翻转单词顺序VS左旋转字符串-java
    题目一:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串为“I am a Student.",则输出为”Student. a am i".
    方法1:把字符串按照空格分开,变成字符串数组,建立StringBuilder,从最后一个字符串加到第一个字符串
    方法2:第一步反转句子中所有的字符。比如翻转“I am a Student"中所有的字符得到”.tneduts a ma i",此时不但翻转了句子中单词的顺序,连单词内的字符顺序也被翻转了。第二步再反转每个单词中字符的顺序,就得到了“student. a am I".这正是符合题目要求的输出。

    题目二:字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转的功能。比如输入字符串”abcdefg"和数字2,该函数将返回左旋转2位得到的结果“cdefgab":
    方法1:要找到字符串旋转时每个字符移动的规律,不是一件容易的事。那么我们是不是可以从解决第一个问题的思路中找到启发呢?在第一个问题中,如果输入的字符串之中只有两个单词,比如”hello World",那么翻转这个句子中的单词顺序就得到了“world hello",比较这两个字符串,我们是不是可以把”world hello"看成是把原始字符串“hello world"的前面若干个字符转移到后面?也就是说这两个问题非常相似,我们同样可以通过反转字符串的办法来解决第二个问题。
    以”abcdeftg"为例,我们可以把它分为两个部分,由于想把它的前两个字符一道后面,我们就把前面两个字符分到第一部分,把后面的所有字符都分到第二个部分。我们先反转这两部分,于是就得到了“bagfedc",接下来我们再翻转整个字符串,得到了”cdefgab"刚好就是把原始字符串左旋转2位的结果。

    剑指offer-49-把字符串转为整数-java
    注意输入数据的合法性,例如“1234+12”,“12@@#*24”,这样的都是不合法数值,要返回0的。但是如果第一个字符时+或-时是可以的,且影响最后输出的符号,最后还要判断下数字是否超出了范围。
    计算时,先把数字变成负数,int范围是[-2147483648,2147483647],可以计算到最小的负数。

    cs
    下一篇:没有了