Leetcode刷题:初级算法之字符串

目录

    • 题目
      • 反转字符串
      • 整数反转
      • 字符串中的第一个唯一字符
      • 字符串转换整数 (atoi)
      • 最长公共前缀
      • 报数
      • 有效的字母异位词
      • 实现strStr()
    • 总结

题目

反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

示例 1:

输入:[“h”,“e”,“l”,“l”,“o”]
输出:[“o”,“l”,“l”,“e”,“h”]
示例 2:

输入:[“H”,“a”,“n”,“n”,“a”,“h”]
输出:[“h”,“a”,“n”,“n”,“a”,“H”]

class Solution(object):
    def reverseString(self, s):
        n = len(s)
        if n <= 1:
            return
        for i in range(n // 2):
            s[i], s[n - i - 1] = s[n - i - 1], s[i]

整数反转

给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。

示例 1:

输入: 123
输出: 321
示例 2:

输入: -123
输出: -321
示例 3:

输入: 120
输出: 21
注意:

假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。

思路 这道题目最关键的一点就是要考虑整数溢出的问题,所以我用了c++编的这道题目(用python怪怪的)
比如: 12345反转,先设定一个sum = 0
12345/10:余数是5,sum = 0 + 5 = 5;(12345 - 5)/ 10 = 1234
1234/10:余数是4,sum = 5 * 10 + 4 = 54;(1234 - 4)/ 10 = 123
。。。
以此类推
踩坑: 为了让我方便想,我打算把所有的负数先弄成正数,最后乘(-1)
然而,悲催的事情发生了。要知道int的范围是[−2^31, 2^31 − 1],也就是说−2^31如果变成正数就溢出了,于是我就加了一个
if (x == 0x80000000) { return 0; }
当然在运算的时候我也加了一个判断的逻辑,如果溢出的话直接返回0;

class Solution {
public:
    int reverse(int x) {
        // int 是否溢出:if( num>0x7fffffff || num<(signed int)0x80000000)
        // 判断是否是负数
        int neg = 1;
        if (x < 0){
            if (x == 0x80000000){
                return 0;
            }
            neg = -1;
            x = -x;
        }
        // 计算反转后的整数
        long long int num = 0;
        while (x > 0){
            int cur = x % 10;
            if (num * 10 + cur > 0x7fffffff){
                return 0;
            }
            num = num * 10 + cur;
            x = (x - cur) / 10;
                
        }
		return num * neg;

但是效果不是特别好,看看官方解答是怎么做的吧?

class Solution {
public:
    int reverse(int x) {
        int rev = 0;
        while (x != 0) {
            int pop = x % 10;
            x /= 10;
            if (rev > INT_MAX/10 || (rev == INT_MAX / 10 && pop > 7)) return 0;
            if (rev < INT_MIN/10 || (rev == INT_MIN / 10 && pop < -8)) return 0;
            rev = rev * 10 + pop;
        }
        return rev;
    }
};

字符串中的第一个唯一字符

给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

案例:

s = “leetcode”
返回 0.

s = “loveleetcode”,
返回 2.

注意事项:您可以假定该字符串只包含小写字母

思路:
开一个长度为26的字母表,所有的值都是n。扫描字符串。
如果当前字母对应字母表中的值是n(说明还没有遇到过这个字母),就把字母表的这位置的值变成这个字母现在的index;
如果对应的不是n(说明已经重复了),就把字母表的这位置的值变成n+1;
这样做的好处是,最后我们的字母表的最小值就是要求的索引,因为重复的和没出现的要么是n+1要么是n~

class Solution(object):
    def firstUniqChar(self, s):
        n = len(s)
        idx_array = [n for i in range(26)]
        
        for i in range(n):
            letter = ord(s[i]) - ord('a')
            if idx_array[letter] == n:
                idx_array[letter] = i
            else:
                idx_array[letter] = n + 1
            
        min_idx = min(idx_array)
        if min_idx >= n:
            return -1
        else:
            return min_idx

注意:

  • 在c++中 char-‘a’ = 数字
  • 然而python没有这样的用法,如果要实现则需要加一个ord()函数

字符串转换整数 (atoi)

请你来实现一个 atoi 函数,使其能将字符串转换成整数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。

当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。

该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。

注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。

在任何情况下,若函数不能进行有效的转换时,请返回 0。

说明:

假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,qing返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。

示例 1:

输入: “42”
输出: 42
示例 2:

输入: " -42"
输出: -42
解释: 第一个非空白字符为 ‘-’, 它是一个负号。
我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。
示例 3:

输入: “4193 with words”
输出: 4193
解释: 转换截止于数字 ‘3’ ,因为它的下一个字符不为数字。
示例 4:

输入: “words and 987”
输出: 0
解释: 第一个非空字符是 ‘w’, 但它不是数字或正、负号。
因此无法执行有效的转换。
示例 5:

输入: “-91283472332”
输出: -2147483648
解释: 数字 “-91283472332” 超过 32 位有符号整数范围。
因此返回 INT_MIN (−231) 。

class Solution {
public:
    int myAtoi(string str) {
        int n = str.size();
        int i = 0, neg = 1;
        int sum = 0;
        
        // 去掉刚开始的空格
        while (i<n){
            if (str[i] == ' ') i++;
            else break;
        }
        // str是空的
        if (i == n) return 0;
        // 第一个非空的字符不是正负号,也不是数字,直接return
        if (str[i] != '-' && str[i] != '+' && (str[i] < '0' || str[i] > '9')) return 0;
        
        // 遇到正负号
        if (str[i] == '-' || str[i] == '+'){
            if (str[i] == '-') neg = -1;
            i++;
        }
        
        // 算一下
        for (int k=i; k<n; k++){
            if (str[k] <= '9' && str[k] >= '0'){
                int pop = str[k] - '0';
                if (sum > INT_MAX/10 || (sum == INT_MAX/10 && pop > 7)) return INT_MAX;
                if (sum < INT_MIN/10 || (sum == INT_MIN/10 && pop > 8)) return INT_MIN;
                sum = sum * 10 + neg * pop;
                
            }
            else return sum;
        }
        return sum;
        
        
    }
};

踩的坑:

  • 关于INT_MIN, INT_MAX: 这两个常量是内置的,不需要自己定义,定义完之后程序就出问题了。。。。
  • 由于pop一定是个非负值,所以远离啊的pop < -8要变成pop > 8
  • 判断遇到正负号的时候不能分开写,逻辑是不对的,比如+-1这样的例子

最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 “”。

示例 1:

输入: [“flower”,“flow”,“flight”]
输出: “fl”
示例 2:

输入: [“dog”,“racecar”,“car”]
输出: “”
解释: 输入不存在公共前缀。
说明:

所有输入只包含小写字母 a-z 。

思路:纵向比较法

Leetcode
Lead
Lets

只要每个string的字符 从idx=0的地方开始比对,如果所有的string在同一个idx的位置字符相等,就把这个字符加到前缀里,如果不等于,就可以停止比对了
那么这个idx的值有限定吗? 我在这里先加了一个计算的过程,它求得的是string的向量中最短的那个string的长度len_min,而这个前缀长度肯定是小于len_min的

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        string res = "";
        // 空的string向量
        if (strs.size() == 0) return res;
        
        // 求string数组中最短的string的长度
        vector<int> str_lens;
        for (int i=0; i<strs.size(); i++) str_lens.push_back(strs[i].size());
        int len_min = *min_element(str_lens.begin(), str_lens.end());
        
        // 从每个string的第一个字符开始检查
        bool end_flag = false;
        for (int i=0; i<len_min; i++){
            char cur_ch = strs[0][i];
            for (int j=1; j<strs.size(); j++){
                if (strs[j][i] != cur_ch){
                    end_flag = true;
                    break;
                }
            }
            if (end_flag) return res;
            else res = res + cur_ch;
        }
        return res;
        
    }
};

踩的坑:

  • vector求最小值:min_element(str_lens.begin(), str_lens.end())返回的是迭代器,前面记得加*
  • 空的vector要记得考虑

报数

报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:

  1. 1
    
  2. 11
    
  3. 21
    
  4. 1211
    
  5. 111221
    

1 被读作 “one 1” (“一个一”) , 即 11。
11 被读作 “two 1s” (“两个一”), 即 21。
21 被读作 “one 2”, “one 1” (“一个二” , “一个一”) , 即 1211。

给定一个正整数 n(1 ≤ n ≤ 30),输出报数序列的第 n 项。

注意:整数顺序将表示为一个字符串。

示例 1:

输入: 1
输出: “1”
示例 2:

输入: 4
输出: “1211”

这个题目考查的是读题能力? 什么one 1 two 1 的 我们就叫这个函数“饶舌歌手”好了!hey, bro!!!

class Solution(object):
    def countAndSay(self, n):
        s = "1"
		for i in range(1, n): # 从n=1开始报数 循环n-1次
            pre_s = s
            s = ""
            # 找相同的块
            pre_ss = pre_s[0]  # n-1时候的string
            pre_idx = 0
            idx = 1
            # 遍历
            while (idx < len(pre_s)):
                # 如果当前位置的数和前面的不同,需要更新output string
                if (pre_s[idx] != pre_ss):
                    times = idx - pre_idx
                    s += (str(times) + pre_ss)  # 让我们的饶舌歌手唱出:数字出现了几次, 数字的value
                    pre_idx = idx

                pre_ss = pre_s[idx]
                idx += 1

            s += (str(len(pre_s) - pre_idx) + pre_ss) 
        return s               

有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词。

示例 1:

输入: s = “anagram”, t = “nagaram”
输出: true
示例 2:

输入: s = “rat”, t = “car”
输出: false
说明:
你可以假设字符串只包含小写字母。

进阶:
如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

  1. 用字典
  2. 因为题目已经假设字符串中只有小写字母,所以可以开一个长度为26的数组来储存

(ps: 测试了一下第二种方法会更快一点~)

注意:一定要判断两个字符串长度是否相等!

# 第一种方法:用字典实现
class Solution(object):
    def isAnagram(self, s, t):
        if len(s) != len(t):
            return False
        
        dic = {}
        for char in s: # 记录第一个字符串里面的字符出现的次数
            if char in dic:
                dic[char] += 1
            else:
                dic[char] = 1
        for char in t: # 遍历第二个字符串
            if (char not in dic) or dic[char] == 0: 
                return False
            dic[char] -= 1
        return True
        
# 第二种方法:用数组实现
class Solution(object):
    def isAnagram(self, s, t):
        if len(s) != len(t):
            return False
        letters = [0 for i in range(26)]
        for ss in s:
            letters[ord(ss)-ord('a')] += 1
        for tt in t:
            if letters[ord(tt)-ord('a')] == 0:
                return False
            else:
                letters[ord(tt)-ord('a')] -= 1
        return True

实现strStr()

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。

示例 1:

输入: haystack = “hello”, needle = “ll”
输出: 2
示例 2:

输入: haystack = “aaaaa”, needle = “bba”
输出: -1
说明:

当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。

对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。

这是一个模式匹配的问题,典型的算法有KMF、BM、Sunday等。
自己想只能想到暴力的解法(就是两个没匹配后再重头开始匹配,这样做的缺点在于没有很好的利用已经匹配过的信息,重新匹配的时候,位置可以跳动的更大一点),还是站在巨人的肩膀上看世界会更加美丽一点~

mark一下相关的资料:

  1. 很详细的一篇介绍:https://www.cnblogs.com/ZuoAndFutureGirl/p/9028287.html
  2. KMF算法介绍与实现:https://blog.csdn.net/yutianzuijin/article/details/11954939 (这篇文章短小精悍)
  3. BM算法介绍:http://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html
  4. BM、KMF算法python实现:https://blog.csdn.net/chiang97912/article/details/83005577

我的代码如下

class Solution(object):
    def strStr(self, haystack, needle):
        """
        给定一个 haystack 字符串和一个 needle 字符串
        在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)
        """
        if needle == "": # needle是空字符串, 返回0
            return 0

        next = self.GetNext(needle)
        j = 0
        for i in range(len(haystack)):
            while (j > 0 and haystack[i] != needle[j]): # 当前j与i的字符没有得到匹配
                j = next[j]
            if haystack[i] == needle[j]: # 当前j与i的字符得到匹配
                j += 1
            if j == len(needle): # 找到匹配串
                return i-j+1

        return -1 # 不存在,返回-1

    def GetNext(self, needle):
        next = [0 for i in range(len(needle)+1)] # 初始化 next 数组,注意长度 = 字符串长度 + 1
        j = 0
        for i in range(1, len(needle)):
            """
            如果 字符串i位置 != 字符串 next【i】位置:
            递归地看
            字符串i位置 和 字符串 next【next【i】】位置 是否匹配?
            不是的话继续和next【next【next【i】】】比较....
            """
            while (j>0 and needle[i] != needle[j]):
                j = next[j]
            if (needle[i] == needle[j]):  # 如果 字符串i位置 = 字符串next【i】位置,说明最大相同前缀后缀长度 + 1
                j += 1
            next[i+1] = j
        return next

用时还是比较多的,有时间再去实现一下BM算法吧

总结

  • 字符串的问题还是很重要的,感觉这类问题非常注重对边界值、异常值的处理
  • 其次很多题目都可以用指针or双指针的方法来实现
  • 最难的还是strStr()这道题目,涉及到了经典的KMF算法,这个算法理解起来还是很有难度的,理解完之后coding部分也不简单,sad。。。。

题目来源: https://leetcode-cn.com/explore/interview/card/top-interview-questions-easy/5/strings/

你可能感兴趣的:(刷题)