【小航的算法日记】字符串算法(三) - 字符串分割

目录

  • 一、概念
  • 二、模板
  • 三、例题
    • 题:58. 最后一个单词的长度
    • 解:
    • 题:434. 字符串中的单词数
    • 解:
    • 题:2042. 检查句子中的数字是否递增
    • 解:
    • 题:2047. 句子中的有效单词数
    • 解:
    • 题:1816. 截断句子
    • 解:
    • 题:1784. 检查二进制字符串字段
    • 解:
    • 题:468. 验证IP地址
    • 解:

一、概念

Java方法:

split(Sting regex,int limit)

limit 参数控制模式应用的次数,因此影响所得数组的长度。如果该限制 n 大于 0,则模式将被最多应用 n - 1 次,数组的长度将不会大于 n ,而且数组的最后一项将包含所有超出最后匹配的定界符的输入。如果 n 为非正,那么模式将被应用尽可能多的次数,而且数组可以是任何长度。如果 n 为 0,那么模式将被应用尽可能多的次数,数组可以是任何长度,并且结尾空字符串将被丢弃

“boo:and:foo”字符串为例:

Regex Limit Result
: 2 “boo”, “and:foo”
: 5 “boo”, “and”, “foo”
: -2 “boo”, “and”, “foo”
o 5 “b”, “”, “:and:f”, “”, “”
o -2 “b”, “”, “:and:f”, “”, “”
o 0 “b”, “”, “:and:f”

split源码:

public String[] split(String regex, int limit) {
      /* fastpath if the regex is a
       (1)one-char String and this character is not one of the
          RegEx's meta characters ".$|()[{^?*+\\", or
       (2)two-char String and the first char is the backslash and
          the second is not the ascii digit or ascii letter.
       */
      char ch = 0;
      //这里是一堆的正则校验,大致是,传入的分割符是单符号位的,才进行下面的分割,否则,return Pattern.compile(regex).split(this, limit)调用另一个分割方法进行字符串分割位分割,文末会PO出此方法
      if (((regex.value.length == 1 &&
           ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
           (regex.length() == 2 &&
            regex.charAt(0) == '\\' &&
            (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
            ((ch-'a')|('z'-ch)) < 0 &&
            ((ch-'A')|('Z'-ch)) < 0)) &&
          (ch < Character.MIN_HIGH_SURROGATE ||
           ch > Character.MAX_LOW_SURROGATE))
      {
          int off = 0;
          int next = 0;
          //从这里开始,进行limit值的入参及split逻辑
          //当传进来的值是正数的时候,limit > 0 == true
          boolean limited = limit > 0;
          //声明一个list集合对返回值结果进行存储,用于最后给String[]赋值
          ArrayList<String> list = new ArrayList<>();
          //当没有按照指定的字符分割到最后一位的时候,执行while循环进行判断,然后使用substring(off, next)方法进行分割
          while ((next = indexOf(ch, off)) != -1) {
          //判断limited 为FALSE,即limit<0,或者,list.size() < limit - 1是否成立
              if (!limited || list.size() < limit - 1) {
                  //若成立则使用substring(off, next)方法进行分割,并且加入到list中
                  list.add(substring(off, next));
                  //此时的初始标识符off为next+1
                  off = next + 1;
              } else {    // last one
                  //assert (list.size() == limit - 1);
                  //不成立的话调用substring(off, value.length),此时value.length值为1
                  list.add(substring(off, value.length));
                  off = value.length;
                  break;
              }
          }
          // 如果不符合,则返回 this
          if (off == 0)
              return new String[]{this};

          // Add remaining segment
          if (!limited || list.size() < limit)
              list.add(substring(off, value.length));

          // Construct result
          int resultSize = list.size();
          if (limit == 0) {
              while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
                  resultSize--;
              }
          }
          //将所得到的list集合进行截取,使用toArray()方法赋值到String[] result中,所以这么看来,split方法的效率,是略差的
          String[] result = new String[resultSize];
          return list.subList(0, resultSize).toArray(result);
      }
      return Pattern.compile(regex).split(this, limit);
  } 

二、模板

三、例题

题:58. 最后一个单词的长度

给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中最后一个单词的长度。

单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。

示例 1:

输入:s = "Hello World"
输出:5

示例 2:

输入:s = "   fly me   to   the moon  "
输出:4

示例 3:

输入:s = "luffy is still joyboy"
输出:6

提示:

1 <= s.length <= 104
s 仅有英文字母和空格 ' ' 组成
s 中至少存在一个单词

解:

解题思路:逆向遍历

AC代码:

class Solution {
    public int lengthOfLastWord(String s) {
        // 逆向遍历
        int len = s.length();
        int i = len - 1;
        while(i >= 0 && s.charAt(i) == ' ') i --;
        if(i < 0) return 0;
        int j = i;
        while(j >= 0 && s.charAt(j) != ' ') j --;
        return i - j;
    }
}

题:434. 字符串中的单词数

统计字符串中的单词个数,这里的单词指的是连续的不是空格的字符。

请注意,你可以假定字符串里不包括任何不可打印的字符。

示例:

输入: "Hello, my name is John"
输出: 5
解释: 这里的单词是指连续的不是空格的字符,所以 "Hello," 算作 1 个单词。

解:

解题思路:统计每一个单词的最后一个下标

AC代码:

class Solution {
    public int countSegments(String s) {
        s += " ";
        int ans = 0;
        for(int i = 1; i < s.length(); i ++) {
            if(s.charAt(i) == ' ' && s.charAt(i-1) != ' ')
                ans ++;
        }
        return ans;
    }
}

题:2042. 检查句子中的数字是否递增

句子是由若干 token 组成的一个列表,token 间用 单个 空格分隔,句子没有前导或尾随空格。每个 token 要么是一个由数字 0-9 组成的不含前导零的 正整数 ,要么是一个由小写英文字母组成的 单词 。

  • 示例,“a puppy has 2 eyes 4 legs” 是一个由 7 个 token 组成的句子:“2” 和 “4” 是数字,其他像 “puppy” 这样的 tokens 属于单词。

给你一个表示句子的字符串 s ,你需要检查 s 中的 全部 数字是否从左到右严格递增(即,除了最后一个数字,s 中的 每个 数字都严格小于它 右侧 的数字)。

如果满足题目要求,返回 true ,否则,返回 false 。

示例 1:

输入:s = "1 box has 3 blue 4 red 6 green and 12 yellow marbles"
输出:true
解释:句子中的数字是:1, 3, 4, 6, 12 。
这些数字是按从左到右严格递增的 1 < 3 < 4 < 6 < 12 。

示例 2:

输入:s = "hello world 5 x 5"
输出:false
解释:句子中的数字是:5, 5 。这些数字不是严格递增的。

示例 3:

输入:s = "sunset is at 7 51 pm overnight lows will be in the low 50 and 60 s"
输出:false
解释:s 中的数字是:7, 51, 50, 60 。这些数字不是严格递增的。

示例 4:

输入:s = "4 5 11 26"
输出:true
解释:s 中的数字是:4, 5, 11, 26 。
这些数字是按从左到右严格递增的:4 < 5 < 11 < 26 。

提示:

3 <= s.length <= 200
s 由小写英文字母、空格和数字 0 到 9 组成(包含 0 和 9)
s 中数字 token 的数目在 2 和 100 之间(包含 2 和 100)
s 中的 token 之间由单个空格分隔
s 中至少有 两个 数字
s 中的每个数字都是一个 小于 100 的 正 数,且不含前导零
s 不含前导或尾随空格

解:

解题思路:遍历+找到单词比大小

AC代码:

class Solution {
    public boolean areNumbersAscending(String s) {
        int x = 0;
        for(int i = 0; i < s.length(); i ++) {
            if(s.charAt(i) == ' ' || s.charAt(i) > '9' || s.charAt(i) < '0') continue;
            int num = 0;
            while(i < s.length() && s.charAt(i) <= '9' && s.charAt(i) >= '0') {
                num = num * 10 + (int)s.charAt(i);
                i ++;
            }
            if(num <= x) return false;
            x = num;
        }
        return true;
    }
}

题:2047. 句子中的有效单词数

句子仅由小写字母(‘a’ 到 ‘z’)、数字(‘0’ 到 ‘9’)、连字符(’-’)、标点符号(’!’、’.’ 和 ‘,’)以及空格(’ ')组成。每个句子可以根据空格分解成 一个或者多个 token ,这些 token 之间由一个或者多个空格 ’ ’ 分隔。

如果一个 token 同时满足下述条件,则认为这个 token 是一个有效单词:

  • 仅由小写字母、连字符和/或标点(不含数字)组成。
  • 至多一个 连字符 ‘-’ 。如果存在,连字符两侧应当都存在小写字母(“a-b” 是一个有效单词,但 “-ab” 和 “ab-” 不是有效单词)。
  • 至多一个 标点符号。如果存在,标点符号应当位于 token 的 末尾 。

这里给出几个有效单词的例子:“a-b.”、“afad”、“ba-c”、“a!” 和 “!” 。

给你一个字符串 sentence ,请你找出并返回 sentence 中 有效单词的数目 。

示例 1:

输入:sentence = "cat and  dog"
输出:3
解释:句子中的有效单词是 "cat"、"and" 和 "dog"

示例 2:

输入:sentence = "!this  1-s b8d!"
输出:0
解释:句子中没有有效单词
"!this" 不是有效单词,因为它以一个标点开头
"1-s" 和 "b8d" 也不是有效单词,因为它们都包含数字

示例 3:

输入:sentence = "alice and  bob are playing stone-game10"
输出:5
解释:句子中的有效单词是 "alice"、"and"、"bob"、"are" 和 "playing"
"stone-game10" 不是有效单词,因为它含有数字

示例 4:

输入:sentence = "he bought 2 pencils, 3 erasers, and 1  pencil-sharpener."
输出:6
解释:句子中的有效单词是 "he"、"bought"、"pencils,"、"erasers,"、"and" 和 "pencil-sharpener."

提示:

1 <= sentence.length <= 1000
sentence 由小写英文字母、数字(0-9)、以及字符(' '、'-'、'!'、'.' 和 ',')组成
句子中至少有 1 个 token

解:

解题思路:双指针

AC代码:

class Solution {
    public int countValidWords(String s) {
        int len = s.length();
        int res = 0;
        for(int i = 0; i < len;) {
            if(s.charAt(i) == ' ' && ++ i >= 0) continue;
            int j = i + 1;
            while(j < len && s.charAt(j) != ' ') j ++;
            if(isValid(s.substring(i, j))) res ++;
            i = j + 1;
        }
        return res;
    }
    boolean isValid(String str) {
        int len = str.length();
        // hyphen -个数   dot .个数
        for(int i = 0, hyphen = 0, dot = 0; i < len; i ++) {
            char c = str.charAt(i);
            if(Character.isDigit(c)) return false; // 不存在数字
            if(c == '-' && ++ hyphen >= 0) {
                if(hyphen > 1 || i == 0 || i == len - 1) return false;
                if(!Character.isLetter(str.charAt(i-1)) || !Character.isLetter(str.charAt(i+1))) return false;
            }
            // 标点符号位于末尾
            if((c == '!' || c == '.' || c == ',' ) && ++ dot >= 0) {
                if(dot > 1 || i != len - 1) return false;
            }        
        }
        return true;
    }
}

题:1816. 截断句子

句子 是一个单词列表,列表中的单词之间用单个空格隔开,且不存在前导或尾随空格。每个单词仅由大小写英文字母组成(不含标点符号)。

  • 例如,“Hello World”、“HELLO” 和 “hello world hello world” 都是句子。

给你一个句子 s​​​​​​ 和一个整数 k​​​​​​ ,请你将 s​​ 截断 ​,​​​使截断后的句子仅含 前 k​​​​​​ 个单词。返回 截断 s​​​​​​ 后得到的句子。

示例 1:

输入:s = "Hello how are you Contestant", k = 4
输出:"Hello how are you"
解释:
s 中的单词为 ["Hello", "how" "are", "you", "Contestant"]
前 4 个单词为 ["Hello", "how", "are", "you"]
因此,应当返回 "Hello how are you"

示例 2:

输入:s = "What is the solution to this problem", k = 4
输出:"What is the solution"
解释:
s 中的单词为 ["What", "is" "the", "solution", "to", "this", "problem"]
前 4 个单词为 ["What", "is", "the", "solution"]
因此,应当返回 "What is the solution"

示例 3:

输入:s = "chopper is not a tanuki", k = 5
输出:"chopper is not a tanuki"

提示:

1 <= s.length <= 500
k 的取值范围是 [1,  s 中单词的数目]
s 仅由大小写英文字母和空格组成
s 中的单词之间由单个空格隔开
不存在前导或尾随空格

解:

解题思路:空格计数

AC代码:

class Solution {
    public String truncateSentence(String s, int k) {
        int n = s.length(), idx = 0;
        while(idx < n) {
            if(s.charAt(idx) == ' ' && -- k == 0) break;
            ++ idx;
        }
        return s.substring(0, idx);
    }
}

题:1784. 检查二进制字符串字段

给你一个二进制字符串 s ,该字符串 不含前导零 。

如果 s 包含 零个或一个由连续的 ‘1’ 组成的字段 ,返回 true​​​ 。否则,返回 false 。

示例 1:

输入:s = "1001"
输出:false
解释:字符串中的 1 没有形成一个连续字段。

示例 2:

输入:s = "110"
输出:true

提示:

1 <= s.length <= 100
s[i]​​​​ 为 '0' 或 '1'
s[0] 为 '1'

解:

解题思路:题有毛病

AC代码:

class Solution {
    public boolean checkOnesSegment(String s) {
        return !s.contains("01");
    }
}

题:468. 验证IP地址

编写一个函数来验证输入的字符串是否是有效的 IPv4 或 IPv6 地址。

  • 如果是有效的 IPv4 地址,返回 “IPv4” ;
  • 如果是有效的 IPv6 地址,返回 “IPv6” ;
  • 如果不是上述类型的 IP 地址,返回 “Neither” 。

IPv4 地址由十进制数和点来表示,每个地址包含 4 个十进制数,其范围为 0 - 255, 用(".")分割。比如,172.16.254.1

同时,IPv4 地址内的数不会以 0 开头。比如,地址 172.16.254.01 是不合法的。

IPv6 地址由 8 组 16 进制的数字来表示,每组表示 16 比特。这些组数字通过 (":")分割。比如, 2001:0db8:85a3:0000:0000:8a2e:0370:7334是一个有效的地址。而且,我们可以加入一些以 0 开头的数字,字母可以使用大写,也可以是小写。所以, 2001:db8:85a3:0:0:8A2E:0370:7334 也是一个有效的 IPv6 address地址 (即,忽略 0 开头,忽略大小写)。

然而,我们不能因为某个组的值为 0,而使用一个空的组,以至于出现 (: 的情况。 比如, 2001:0db8:85a3::8A2E:0370:7334 是无效的 IPv6 地址。

同时,在 IPv6 地址中,多余的 0 也是不被允许的。比如, 02001:0db8:85a3:0000:0000:8a2e:0370:7334 是无效的。

示例 1:

输入:IP = "172.16.254.1"
输出:"IPv4"
解释:有效的 IPv4 地址,返回 "IPv4"

示例 2:

输入:IP = "2001:0db8:85a3:0:0:8A2E:0370:7334"
输出:"IPv6"
解释:有效的 IPv6 地址,返回 "IPv6"

示例 3:

输入:IP = "256.256.256.256"
输出:"Neither"
解释:既不是 IPv4 地址,又不是 IPv6 地址

示例 4:

输入:IP = "2001:0db8:85a3:0:0:8A2E:0370:7334:"
输出:"Neither"

示例 5:

输入:IP = "1e1.4.5.6"
输出:"Neither"

提示:

IP 仅由英文字母,数字,字符 '.' 和 ':' 组成。

解:

解题思路:模拟

AC代码:

class Solution {
    public String validIPAddress(String queryIP) {
        if(queryIP.chars().filter(ch -> 
            ch == '.'
        ).count() == 3) return validIPV4(queryIP);
        if(queryIP.chars().filter(ch -> 
            ch == ':'
        ).count() == 7) return validIPV6(queryIP);
        return "Neither";
    }
    public String validIPV4(String queryIP) {
        String[] nums =  queryIP.split("\\.", -1); // 不丢弃结尾空字符串
        for(String num : nums) {
            // 判断长度
            if(num.length() <= 0 || num.length() > 3) return "Neither";
            // 没有前导0
            if(num.charAt(0) == '0' && num.length() > 1) return "Neither";
            // 判断是否都为数字
            for(Character c : num.toCharArray()) {
                if(!Character.isDigit(c)) return "Neither";
            }
            // 判断数字大小
            if(Integer.parseInt(num) > 255) return "Neither";
        }
        return "IPv4";
    }
    public String validIPV6(String queryIP) {
        String[] nums =  queryIP.split(":", -1); // 不丢弃结尾空字符串
        String hexdigits = "0123456789ABCDEFabcdef";
        for(String num : nums) {
            // 判断长度
            if(num.length() <= 0 || num.length() > 4) return "Neither";
            for(Character c : num.toCharArray()) {
                if(hexdigits.indexOf(c) == -1) return "Neither";
            }         
        }
        return "IPv6";
    }
}

你可能感兴趣的:(#,小航的算法日记,算法,leetcode,职场和发展)