LeetCode初级算法训练-字符串

简介

上一篇 : LeetCode初级算法训练-数组

下一篇 : LeetCode初级算法训练-链表

本来想重初中级和企业面试算法开始的,但是最后还是选择从基础的开始,因为我们并不是为了刷题而刷题,而是在刷题过程中锻炼一种算法思维,在大量的训练之后形成一种对算法的独特见解,培养那种对算法的的敏感度,看到题目,大脑中可以浮现一个解题蓝图,而且从初级开始慢慢建立信心,而且这也是在为后边复杂算法的解题思路打基础。

LeetCode初级算法简介

如果你也想训练自己的算法思维,也可以加入我,从初级算法开始,开启你的算法之旅:初级算法。

自己的一些思考:不要在看完题目后直接就看答案,然后去背题,这样行成的算法记忆是不牢固的,一定要有自己的思考;而且不要一开始就在IDEA上边去写,一定试着自己在leetCode提供的白板上边写一遍最后在放到IDEA上边去执行看有什么问题,以便巩固你的基础API的使用和熟练度;还有一点就是大胆些,不是面试我们试错成本低,尽可能把我们的想法融入到代码中

因篇幅问题,博客中只列出示例和自己的解题答案,详细可以直接点击题目查看。

反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 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 {
    public void reverseString(char[] s) {
        for(int i = 0;i < s.length / 2;i++){
            char temp = s[i];
            s[i] = s[s.length - 1 - i];
            s[s.length - 1 -i] = temp;    
        }
    }
}

执行用时:1 ms
内存消耗:46.5 MB

这个没什么好说的之前我们翻转int数组也是用的这种方法。

整数反转

给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例 1:
输入: 123
输出: 321

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

示例 3:
输入: 120
输出: 21
注意:
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为
[ − 2 31 , 2 31 − 1 ] [−2^{31}, 2^{31} − 1] [231,2311]。请根据这个假设,如果反转后整数溢出那么就返回 0。

第一种方法自己弄得效率很低

class Solution {
    public int reverse(int x) {
      boolean isMinusNum = x < 0;
        String s = String.valueOf(Math.abs(x));
        StringBuilder sb = new StringBuilder(s);
        sb.reverse();
         Long aLong;
        try{
             aLong = isMinusNum ? -Long.valueOf(sb.toString()) : Long.valueOf(sb.toString());
        } catch(Exception e){
            return 0;
        }
       
        if (aLong > Integer.MAX_VALUE || aLong < Integer.MIN_VALUE) return 0;
        return aLong.intValue();
    }
}

执行用时:4 ms
内存消耗:37.2 MB

第二种方法看的答案巧妙地用取余和除一次一次取得余数乘10累加,中间直接体检判断rev大小。

class Solution {
    public int reverse(int x) {

        int rev = 0;
        while (x != 0) {
            int tail = x % 10;
            x /= 10;

            if (rev < Integer.MIN_VALUE / 10 || (rev == Integer.MIN_VALUE / 10 && tail < Integer.MIN_VALUE % 10)) {
                return 0;
            }

            if (rev > Integer.MAX_VALUE / 10 || (rev == Integer.MAX_VALUE / 10 && tail > Integer.MAX_VALUE % 10)) {
                return 0;
            }

            rev = rev * 10 + tail;
        }
        return rev;
    
    }
}

执行用时:1 ms
内存消耗:37.2 MB

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

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

s = “leetcode”
返回 0

s = “loveleetcode”
返回 2
提示:你可以假定该字符串只包含小写字母。

class Solution {
    public int firstUniqChar(String s) {
        int[] locates = new int[26];
        
        for(int i = 0;i

执行用时:7 ms
内存消耗:40.3 MB
第一想法就是这个,定义一个26大小的int数组,先遍历一遍字符串,过程中计算字母相比于’a’的距离也就是在26大小数组中的位置,然后遍历到一个相应位置的数字+1。
在遍历一遍,判断只要在26大小的int数组中出现一次就返回。

还有一种巧妙地方法执行更快的方法。

遍历26个英文小写字母,利用字符串的indexOf和lastIndexOf判断字符第一次和最后一次出现的所以是否一致一致说明只出现一次,然后找出这些索引中最小的一个。

class Solution {
    public int firstUniqChar(String s) {
       int firstUniqIndex = s.length();
       for(char i = 'a';i<='z';i++) {
           int index = s.indexOf(i);
           if(index != -1 && index == s.lastIndexOf(i)){
               firstUniqIndex = Math.min(firstUniqIndex,index);
           }
       }
        return firstUniqIndex == s.length() ? -1 : firstUniqIndex;
    }
}

执行用时:2 ms
内存消耗:40.2 MB

有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例 1:
输入: s = “anagram”, t = “nagaram”
输出: true

示例 2:
输入: s = “rat”, t = “car”
输出: false
说明:
你可以假设字符串只包含小写字母。
进阶:
如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

class Solution {
    public boolean isAnagram(String s, String t) {
       if (s == null || t == null) return false;
        if (s.length() != t.length()) return false;
        if (s.length() == 0 && t.length() == 0) return true;
        char[] chars = s.toCharArray();
        char[] chars1 = t.toCharArray();
        Arrays.sort(chars);
        Arrays.sort(chars1);
        //这里刚开始用的是Arrays.toString转为string然后在eqauls 但是那样执行有 8 -11ms 然后发现这里直接一个循环就搞定了,Arrays.equals也是用的循环比对。
        for (int i = 0; i < chars.length; i++) {
            if (chars[i] != chars1[i]) {
                return false;
            }
        }
        return true;
    }
}

执行用时:3 ms
内存消耗:39.8 MB

还有一种方式是使用 字符串中的第一个唯一字符 中的26个int数组方式,如果两个字符串调整位置后是相同的,那必定每个字母都是 x%2==0的所以每个字符串位置+1另一个字符串-1最后每个位置必定是0。所以如果出现了一个不为零的肯定是一个比另一个的字符多。

class Solution {
    public boolean isAnagram(String s, String t) {
       if (s == null || t == null) return false;
        if (s.length() != t.length()) return false;
        if (s.length() == 0 && t.length() == 0) return true;
    
        int[] nums = new int[26];
        
        for(int i = 0;i < s.length();i++) {
            nums[s.charAt(i) - 'a'] ++;
            nums[t.charAt(i) - 'a'] --;
        }
        
        for(int i = 0;i < nums.length;i++) {
            if(nums[i] != 0){
                return false;
            }
        }
        return true;
    }
}
验证回文字符串

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
示例 1:
输入: “A man, a plan, a canal: Panama”
输出: true

示例 2:
输入: “race a car”
输出: false

使用指针方法去对比。

class Solution {
    public boolean isPalindrome(String s) {
        if(s == null) return false;
        s = s.toLowerCase();
        //之前还加了个计算数字和字母的数量来判断下边循环结束,答案中可以使用i < j来做条件
        int i = 0,j = s.length() - 1;
        while(i < j) {
            char left = s.charAt(i);
            char right = s.charAt(j);
            if(!((left >= 'a' && left <= 'z') ||(left >= '0' && left <= '9') ) ){
                i++;
                continue;
            }

            if(!((right >= 'a' && right <= 'z') ||(right >= '0' && right <= '9'))) {
                j--;
                continue;
            }
            if(left != right) {
                return false;
            }
           
            i++;
            j--;
        }
        return true;
    }
}

执行用时:3 ms
内存消耗:39.7 MB

字符串转换整数 (atoi)

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

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。接下来的转化规则如下:

如果第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字字符组合起来,形成一个有符号整数。
假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成一个整数。
该字符串在有效的整数部分之后也可能会存在多余的字符,那么这些字符可以被忽略,它们对函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换,即无法进行有效转换。

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

提示:

本题中的空白字符只包括空格字符 ’ ’ 。
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 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) {
        if (str == null || str.length() == 0) return 0;
        boolean haveFirstValidChar = false;
        boolean haveValidNumAfterZero = false;
        boolean isMinus = false;

        int firstCharType = -1; //0 zero 1 symbol 2 validnum

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (!haveFirstValidChar) {
                if (!isValidChar(c)) {
                    return 0;
                } else if (!isSpace(c)) {
                    haveFirstValidChar = true;
                    if (isZero(c)) {
                        firstCharType = 0;
                        if (i == str.length() - 1 || (i < str.length() - 1 && !isNum(str.charAt(i + 1)))) {
                            return 0;
                        }
                    }

                    if (isValidNum(c)) {
                        firstCharType = 2;
                    }

                    if (isValidSymbol(c)) {
                        firstCharType = 1;
                        if (i == str.length() - 1 || (i < str.length() - 1 && !isNum(str.charAt(i + 1)))) {
                            return 0;
                        }
                    }

                    isMinus = (c == '-');

                    if (firstCharType != 0) {
                        sb.append(c);
                    }
                }
            } else if (firstCharType == 0 && isNum(c)) {
                if (haveValidNumAfterZero) {
                    sb.append(c);
                } else if (!isZero(c)) {
                    haveValidNumAfterZero = true;
                    sb.append(c);
                }
            } else if (firstCharType == 1 && isNum(c)) {
                if (haveValidNumAfterZero) {
                    sb.append(c);
                } else if (!isZero(c)) {
                    haveValidNumAfterZero = true;
                    sb.append(c);
                }
            } else if (firstCharType == 2 && isNum(c)) {
                sb.append(c);
            } else {
                break;
            }
        }

        if (!haveFirstValidChar) {
            return 0;
        }

        if ((firstCharType == 0 || firstCharType == 1) && !haveValidNumAfterZero) {
            return 0;
        }

        String result = sb.toString();

        if (result.charAt(0) == '+') {
            result = result.substring(1);
        }

        if (isMinus) {
            if (result.length() < 11) {
                return Integer.valueOf(result);
            } else if (result.length() > 11) {
                return Integer.MIN_VALUE;
            } else {
                int tenInt = Integer.valueOf(result.substring(0, 10));
                int lastInt = Integer.valueOf(result.substring(10, 11));
                return tenInt < Integer.MIN_VALUE / 10 || (tenInt == Integer.MIN_VALUE / 10 && lastInt > 8) ? Integer.MIN_VALUE : Integer.valueOf(result);
            }
        } else if (result.length() < 10) {
            return Integer.valueOf(result);
        } else if (result.length() > 10) {
            return Integer.MAX_VALUE;
        } else {
            int tenInt = Integer.valueOf(result.substring(0, 9));
            int lastInt = Integer.valueOf(result.substring(9, 10));
            return tenInt > Integer.MAX_VALUE / 10 || (tenInt == Integer.MAX_VALUE / 10 && lastInt > 7) ? Integer.MAX_VALUE : Integer.valueOf(result);
        }
    }
    
    private boolean isValidChar(char c){
        return isValidSymbol(c) || isNum(c) || isSpace(c);
    }
    
    private boolean isValidSymbol(char c){
        return '-' == c || '+' == c;
    }
    
    private boolean isNum(char c){
        return c <= '9' && c >= '0';
    }
    
    private boolean isValidNum(char c){
        return c <= '9' && c > '0';
    }
    
    private boolean isSpace(char c){
        return ' ' == c;
    }
    
    private boolean isZero(char c){
        return '0' == c;
    }
}

执行用时:4 ms
内存消耗:39.7 MB
这是我自己的做法时间上有待优化。研究一下别人的算法。差距产生在我考虑的太复杂,没仔细做分析分类。

class Solution {
    public int myAtoi(String str) {
       int res = 0;
        int max = Integer.MAX_VALUE / 10;
        boolean hasStarted = false, isNegative = false;
        for (char ch : str.toCharArray()) {
            if (ch == ' ' && !hasStarted)
                continue;
            else if (ch == '+' && !hasStarted)
                hasStarted = true;
            else if (ch == '-' && !hasStarted) {
                hasStarted = true;
                isNegative = true;
            } else if (ch >= '0' && ch <= '9') {
                hasStarted = true;
                int val = ch - '0';
                if (res > max || (res == max && val > 7))
                    return isNegative ? Integer.MIN_VALUE : Integer.MAX_VALUE;
                else
                    res = res * 10 + val;
            } else
                break;
        }

        return isNegative ? -res : res;
    }

}
实现 strStr()

实现 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() 定义相符。

class Solution {
    public int strStr(String haystack, String needle) {
        if(haystack == null || needle == null) return -1;
    
       if(haystack.contains(needle)) {
          return haystack.indexOf(needle);
       } else {
           return -1;
       }
        
    }
}

执行用时:0 ms
内存消耗:38.3 MB

这个比较简单都是现成的,但是建议大家去看下contains和indexOf的实现。

外观数列

给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。
注意:整数序列中的每一项将表示为一个字符串。
「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下:

  1. 1
    
  2. 1 1
    
  3. 2 1
    
  4. 1 2 1 1
    
  5. 1 1 1 2 2 1
    

第一项是数字 1
描述前一项,这个数是 1 即 “一个 1 ”,记作 11
描述前一项,这个数是 11 即 “两个 1 ” ,记作 21
描述前一项,这个数是 21 即 “一个 2 一个 1 ” ,记作 1211
描述前一项,这个数是 1211 即 “一个 1 一个 2 两个 1 ” ,记作 111221

示例 1:

输入: 1
输出: “1”
解释:这是一个基本样例。

示例 2:
输入: 4
输出: “1211”
解释:当 n = 3 时,序列是 “21”,其中我们有 “2” 和 “1” 两组,“2” 可以读作 “12”,也就是出现频次 = 1 而 值 = 2;类似 “1” 可以读作 “11”。所以答案是 “12” 和 “11” 组合在一起,也就是 “1211”。

class Solution {
    public String countAndSay(int n) {
         if (n < 1 && n > 30) throw new IllegalArgumentException("no serialize");
        if (n == 1) return "1";
        StringBuilder sb = new StringBuilder();

        int count = 1;
        String seed = "1";
        for (int i = 1; i < n; i++) {
            count = 1;
            sb.delete(0,sb.length());
            for (int j = 0; j < seed.length(); j++) {
                if (j + 1 < seed.length()) {
                    if (seed.charAt(j) == seed.charAt(j + 1)) {
                        count++;
                    } else {
                        sb.append(count);
                        sb.append(seed.charAt(j));
                        count = 1;
                    }
                } else {
                    sb.append(count);
                    sb.append(seed.charAt(j));
                }
            }
            seed = sb.toString();
        }

        return sb.toString();
    }
}

执行用时:4 ms
内存消耗:37.1 MB

这个其实两层循环,每次内层循环的结果是下次循环的种子。

最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
示例 1:
输入: [“flower”,“flow”,“flight”]
输出: “fl”

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

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

class Solution {
    public String longestCommonPrefix(String[] strs) {
          if (strs == null || strs.length == 0) return "";

        if(strs.length == 1 && strs[0] != null) return strs[0];
        
        String pre = "";
        boolean havePre = true;
        for (int i = 0; i < strs[0].length(); i++) {
            pre = strs[0].substring(0, i + 1);
            for (int j = 0; j < strs.length; j++) {
                if (!strs[j].startsWith(pre) || strs[j].length() < pre.length()) {
                    if(i == 0) {
                        return "";
                    } else {
                        return pre.substring(0, i);
                    }
                }
            }
        }
        return pre;
    }
}

执行用时:5 ms
内存消耗:37.9 MB

这个自己写的方法可能不使用startwith的话用字符去判断可能时间更快些。

看一下最快的方法:

class Solution {
    public String longestCommonPrefix(String[] strs) {
         if (strs == null || strs.length == 0) {
            return "";
        }
        String pre = strs[0];
        for(String str: strs){
            while(str.indexOf(pre) != 0){
                pre = pre.substring(0,pre.length() - 1);
            }
        }
        return pre;
    }
}

118 / 118 个通过测试用例
状态:通过
执行用时:0 ms
内存消耗:37.8 MB
这个属于从后往前找,str.indexOf(pre) == 0就跳过找下一个,如果不为则缩短字符串继续找,重复上边的操作。

你可能感兴趣的:(数据结构与算法,LeetCode训练)