LeetCode初级算法-字符串篇

时间过得真快,距离18年的1024已经一年了,一年前,刚开始在csdn上写博客,那时候对1024没有太上心,觉得自己不是一个程序员。经过了1年的学习,心态有了很大的变化,把写代码变得有仪式感,1024,不只是成长,更是挑战。

LeetCode的初级题或者说是简单题,跟智力和脑子没关系。锻炼的还是编程能力。就是说,这些题基本上看到就能有想法,它们锻炼的是把脑子中的想法快速转换为代码的能力。算是入门级别。

反转相关

1. 反转字符串

这个就是个憨批题:

  public void reverseString(char[] s) {
        char temp;
        int length = s.length;
        for (int i = 0; i < length / 2; i ++){
            temp = s[i];
            s[i] = s[length - 1 - i];
            s[length - 1 - i] = temp;
        }
    }
2. 整数反转

由于这是字符串这一章节的问题,所以我很自然的就把它当成了字符串来做,开局换上字符数组直接开始怼,具体思路是:

  • 即从前往后拿数据

    1 2 3 4 5

    21 3 4 5

    public int reverse(int x) {
        	// 全转为正数
            char[] numChar = Integer.valueOf(x > 0 ? x : -x).toString().toCharArray();
            int reValue = 0;
            int multi = 1;
            int temp;
            int billion = 1000000000;
            for (char c : numChar) {
                temp = (c - 48) * multi;
                if (multi == billion) {
                    // 如果最后一位数比2大或者(等于2,但是后面的数比Max_Value大)
                    if (c > 50 || (reValue > Integer.MAX_VALUE % billion && c == 50)) {
                        return 0;
                    }
                }
                reValue += temp;
                multi *= 10;
            }
            return x > 0 ? reValue : -reValue;
        }
    
  • 另外一种思路是从后往前拿数据,巧妙地运用了%/的作用。算是用了栈把

    1 2 3 4 5

    54 1 2 3

    public int reverse(int x) {
            int rev = 0;
            while (x != 0) {
                int pop = x % 10;
                x /= 10;
                if (rev > Integer.MAX_VALUE/10) return 0;
                if (rev < Integer.MIN_VALUE/10) return 0;
                rev = rev * 10 + pop;
            }
            return rev;
    }
    

重复相关

3. 字符串中第一个唯一的数组

这个题不能像只出现一个的数字一样,即不能使用异或来解决,因为这个题中可能会出现多个唯一的数组,然后我们需要拿到第一个唯一的数组。

首先想到的就是使map,value值随着key的重复而++

public int firstUniqChar(String str) {
      
        Map<Character, Integer> map = new HashMap<>(15);
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            map.put(c, map.getOrDefault(c, 0) + 1);
        }
        for (int i = 0; i < str.length(); i++) {
            if (map.get(str.charAt(i)) == 1) {
                return i;
            }
        }
        return -1;
    }
4. 有效的字母异位词

这个题和之前的两个数组的交集比较像

  • 先排序,然后比较,非常方便,但是感觉有点慢

    public boolean isAnagram(String s, String t) {
            if(s.length() != t.length()){
                return false;
            }
            char[] cs = s.toCharArray();
            char[] ct = t.toCharArray();
            Arrays.sort(cs);
            Arrays.sort(ct);
            
            for(int i = 0; i < s.length(); i ++){
                if(cs[i] != ct[i]) {
                    return false;
                }
            }
            return true;
        }
    
  • 同时,还可以利用哈希表来观察这两个字符串中字符的出现次数

    public boolean isAnagram(String s, String t) {
            if(s.length() != t.length()){
                return false;
            }
            int[] counter = new int[26];
            for(int i = 0; i < s.length(); i ++) {
                // -97是因为前面的数组长度只有26
                counter[s.charAt(i) - 97] ++;
            }
            
            for(int i = 0; i < t.length(); i ++) {
                if(-- counter[t.charAt(i) - 97] < 0){
                    return false;
                }
            }
            return true;
        }
    
9. 最长公共前缀

主要是字符串数组比较特别,确定字符串数组中的字符需要两重循环;str[i].charAt(j)

  • 方法一是暴力

    两重循环,第一个是数组的循环,第二个是字符串中字符的循环。用力扣管方的说法就是水平扫描

    public static String longestCommonPrefix(String[] strs) {
            if (strs.length == 0) return "";
            if (strs.length == 1) return strs[0];
            int flag = 0;
            for (int i = 1; i < strs.length; ) {
                // 防止charAt(i) 越界
                if (flag > strs[i].length() - 1 || flag > strs[i - 1].length() - 1) break;
                if (strs[i].charAt(flag) == strs[i - 1].charAt(flag)) {
                    // 如果数组循环一遍,则回到第一个数组元素。同时把索引加一
                    if (i == strs.length - 1) {
                        flag ++;
                        i = 1;
                        continue;
                    }
                } else break;
                i ++;
            }
            return strs[0].substring(0, flag);
        }
    
  • 方法二:分治递归

    public String longestCommonPrefix(String[] strs) {
        if (strs == null || strs.length == 0) return "";    
        return longestCommonPrefix(strs, 0 , strs.length - 1);
    }
    
    private String longestCommonPrefix(String[] strs, int l, int r) {
        if (l == r) {
            return strs[l];
        }
        else {
            int mid = (l + r)/2;
            String lcpLeft =   longestCommonPrefix(strs, l , mid);
            String lcpRight =  longestCommonPrefix(strs, mid + 1,r);
            return commonPrefix(lcpLeft, lcpRight);
       }
    }
    
    private String commonPrefix(String left,String right) {
        int min = Math.min(left.length(), right.length());       
        for (int i = 0; i < min; i++) {
            if ( left.charAt(i) != right.charAt(i) )
                return left.substring(0, i);
        }
        return left.substring(0, min);
    }
    
  • 方法三:二分法

坑爹的字符相关

主要是使用一些字符串相关API,同时还有字符和int类型的比较

5. 验证回文字符串

这个题当理解了回文串的性质之后,无非就是比较前面和后面的值。没有什么难度,主要是CharacterAPI 的熟悉

    public static boolean isPalindrome(String s) {
        boolean isNumOrLetterI;
        boolean isNumOrLetterJ;
        for (int i = 0, j = s.length() - 1; i <= j; i ++, j --){
            isNumOrLetterI = Character.isLetterOrDigit(s.charAt(i));
            isNumOrLetterJ = Character.isLetterOrDigit(s.charAt(j));
            if (isNumOrLetterI && isNumOrLetterJ) {
                if (Character.toLowerCase(s.charAt(i)) == Character.toLowerCase(s.charAt(j))) {
                    continue;
                }
                return false;
            } else {
                if (!isNumOrLetterI && !isNumOrLetterJ) {
                    continue;
                }
                if (!isNumOrLetterI) {
                    j ++;
                }
                if (!isNumOrLetterJ) {
                    i --;
                }
            }
        }
        return true;
    }
6. 字符串转整数

这个题字字符的处理上和第五题比较像,在int类型越界上和第二题比较像

  • 第一步时处理字符,直至找到数字。有一下情况:

    • 如果是空格,则跳过continue
    • 如果是- or + 则需要记录它的正负
    • 如果不是符号,也不是空格,更不是数字,则 return false
    • 如果检测到数字,则到下一步
  • 第二步是计算,并防止越界

    if (!mark && (-num < Integer.MIN_VALUE/10 || (-num == Integer.MIN_VALUE/10 && str.charAt(index) > '8'))) return Integer.MIN_VALUE;
    if (mark && (num > Integer.MAX_VALUE/10 || (num == Integer.MAX_VALUE/10 && str.charAt(index) > '7'))) return Integer.MAX_VALUE;
    
public static int myAtoi(String str) {
        boolean mark = true;
        int num = 0;
        // 字符串索引
        int index = 0;
        char temp;
        // 第一个循环,找到数字
        while(index < str.length()) {
            temp = str.charAt(index);
            // 前面没有数字且是负号
            if (temp == '-' || temp == '+') {
                // -为最后一个字符或-后面不为数字
                if (index == str.length() - 1 || !isNum(str.charAt(index + 1))) return 0;
                else mark = temp != '-';
            } else if (temp == ' ') {
                index ++;
                continue;
            } else if (!isNum(temp)) return 0;
            else break;
            index ++;
        }
        // 找到数字以后
        for(; index < str.length() && isNum(str.charAt(index)); index ++) {
            if (!mark && (-num < Integer.MIN_VALUE/10 || (-num == Integer.MIN_VALUE/10 && str.charAt(index) > '8'))) return Integer.MIN_VALUE;
            if (mark && (num > Integer.MAX_VALUE/10 || (num == Integer.MAX_VALUE/10 && str.charAt(index) > '7'))) return Integer.MAX_VALUE;
            num = (str.charAt(index) - '0') + num * 10;
        }
        return mark ? num : -num;
    }
    private static boolean isNum(char c) {
        return c >= '0' && c <= '9';
    }
7. 实现strStr()

这个类似于Java的indexOf的函数

  • 思路一,暴力双指针

    这个题也是用了双指针,数组模块经常用到双指针,如第一题和第六题都是这样。

    其实全一点的话是4个指针:

    • i 指向长字符串的索引
    • j指向短字符串的索引
    • beginI指向长字符串开始匹配的索引
    • 0是短字符串开始的索引
     	public static int strStr(String haystack, String needle) {
            if ("".equals(needle)) return 0;
            int beginI = 0;
            // j != 0 说明已经开始比较了
            for (int i = 0, j = 0; i < haystack.length() && j < needle.length(); i ++) {
                if (haystack.charAt(i) == needle.charAt(j)) {
                    beginI = j == 0 ? i : beginI;
                    if (j ++ == needle.length() - 1) {
                        return beginI;
                    }
                } else if (j != 0) {
                    j = 0;
                    i = beginI;
                }
            }
            return -1;
        }
    
  • 思路二,KMP算法:

    KMP算法在面试中常问到,基本是字符串比较中最优的算法了。这个算法是用空间换时间,时间复杂度是o(1)

    • KMP算法详解-Java
    1. KMP不同于上一个暴力算法,KMP 算法的主串下标永不后退,而暴力算法一旦出错,则回退至匹配起始的下一个下标重头开始
    2. 为什么说是用空间换时间呢?因为这个算法需要构建一个有限状态机,通过每次比对有限状态机来实现线性耗时
    3. 第一个for循环先构建有限状态机。构建有限状态机是一个难点
    4. 第二个for循环和有限状态机核查,然后成功后返回index
    public int strStr(String haystack, String needle) {
        int strLen = haystack.length(), subLen = needle.length();
        if (subLen == 0) return 0;
        if (strLen == 0) return -1;
        // 构建状态机
        int[][] FSM = new int[subLen][256];
        int X = 0, match = 0;
        for (int i = 0; i < subLen; i++) {
            match = (int) needle.charAt(i);
            for (int j = 0; j < 256; j++) {
                // 当前状态 + 匹配失败字符 = 孪生词缀状态 + 匹配字符
                FSM[i][j] = FSM[X][j]; 
            }
            FSM[i][match] = i + 1;
            if (i > 0) {
                // 下一孪生前缀状态 = X + match
                X = FSM[X][match];
            }
        }
        // 匹配子串
        int state = 0;
        for (int i = 0; i < strLen; i++) {
            state = FSM[state][haystack.charAt(i)];
            if (state == subLen) {
                return i - subLen + 1;
            }
        }
        return -1;
    }
    
  • 思路三,Sunday算法:

    这个还没看,等到面试之前再刷一哈

8.报数

题意真是SB,是我脑子不够用了吗?

看了评论,明白这题说的是什么意思了,这题也不算难,非常简单,基本看一下就能出来结果。只要把脑子里的想法写出来就行。

  1. 先维护一个计数器 counter
  2. str.charAt(i) == str.charAt(i + 1),如果为真,counter++;如果为假,则把i的值和counter增加到stringBuffer中,之后把counter变为1,。
public String countAndSay(int n) {
        String str = "1";
        for (int i = 1; i < n; i ++) {
            str = nextStr(str);
        }
        return str;
    }
    private String nextStr(String str) {
        StringBuilder sb = new StringBuilder("");
        int counter = 1;
        for (int i = 0; i < str.length(); i ++) {
            if (i < str.length() - 1 && str.charAt(i) == str.charAt(i + 1)) {
                counter ++;
            } else {
                // 如果是最后一个且前面没有重复
                if (i == str.length() - 1 && str.length() > 1 && str.charAt(i) != str.charAt(i - 1)) {
                    counter = 1;
                }
                sb.append(counter).append(str.charAt(i));
                counter = 1;
            }
        }
        return sb.toString();
    }

你可能感兴趣的:(算法和数据结构)