文章目录
三、字符串专题
1、剑指Offer_05:替换空格
题目:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
public class Solution {
public String replaceSpace(StringBuffer str) {
/**
* 1、创建一个StringBuilder
* 2、遍历字符串中的每一个字符c,如果是空格,就拼接%20
* 3、如果不是空格,就拼接c
*/
String s = str.toString();
StringBuilder sb = new StringBuilder();
for(char c:s.toCharArray()){
if(c==' '){
sb.append("%20");
}else{
sb.append(c);
}
}
return sb.toString();
}
}
2、剑指Offer_19:正则表达式匹配
题目:给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
/**
* 本题考查动态规划,困难题
*
* 时间复杂度:O(m*n)
* 空间复杂度:O(m*n)
*/
public class Solution {
public boolean match(char[] str, char[] pattern) {
/**
* 动态规划:定义boolean dp[i][j]的含义就是s[0-i]与p[0-j]是都匹配
* 分情况考虑:
* 1、pattern[j]==str[i]:dp[i][j] = dp[i-1][j-1];
* 2、pattern[j]=='.':dp[i][j] = dp[i-1][j-1];
* 3、pattern[j]=='*':
* 1、如果pattern[j-1]!=str[i] :dp[i][j] = dp[i][j-2]
* s = "ba"、p = "bc*a"-->p="ba"
* 2、如果 pattern[j-1]==str[i] 或者 pattern[j-1]!='.'
* 1、如果 '.*' 匹配1个a:dp[i][j]=dp[i][j-1];
* s = "ba"、p = "b.*a" --->p="baa"
* 2、如果 '.*' 匹配多个a:dp[i][j] = dp[i-1][j];
* s = "baaa"、p = "b.*a" --->p="baaaa"
* 3、如果 '.*' 匹配0个a:dp[i][j] = dp[i][j-2];
* s = "ba"、p = "b.*a" -->p="ba"
*/
if(str==null || pattern==null){
return false;
}
boolean[][] dp = new boolean[str.length+1][pattern.length+1];
dp[0][0] = true;
/**
* 要将pattern[j]=='*' 这种情况单独拿出来处理一下,c*删除的时候,dp[0][j+1] = true
* str="aab" pattern="c*aab"-->pattern="aab"
*/
for(int j=0;j<pattern.length;j++){
if(pattern[j]=='*' && dp[0][j-1]){
dp[0][j+1] = true;
}
}
for(int i=0;i<str.length;i++){
for(int j=0;j<pattern.length;j++){
if(pattern[j]==str[i]){
dp[i+1][j+1] = dp[i][j];
}
if(pattern[j]=='.'){
dp[i+1][j+1] = dp[i][j];
}
if(pattern[j]=='*'){
if(pattern[j-1]!=str[i] && pattern[j-1]!='.'){
dp[i+1][j+1] = dp[i+1][j-1];
}else{
dp[i+1][j+1] = (dp[i+1][j]) || (dp[i][j+1]) || (dp[i+1][j-1]);
}
}
}
}
return dp[str.length][pattern.length];
}
}
3、剑指Offer_20:表示数值的字符串
题目:请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。
public class Solution {
public boolean isNumeric(char[] str) {
//将char数组转换为String对象
String s = new String(str);
//写这个条件是因为5.6f视为不是数值,但是转换的时候是数值
if(s.endsWith("f") || s.endsWith("d") || s.endsWith("F") || s.endsWith("D")){
return false;
}
try {
Double.parseDouble(s);
} catch (NumberFormatException e) {
return false;
}
return true;
}
}
4、剑指Offer_23:字符流中第一个不重复的字符
题目:请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
import java.util.LinkedList;
import java.util.Queue;
public class Solution {
//定义一个数组用来记录每个字符出现的次数
int[] arr = new int[128];
//定义一个队列用来找到第一个不重复的数字,先进先出
Queue<Character> queue = new LinkedList<>();
//Insert one char from stringstream
public void Insert(char ch) {
arr[ch]++;
if(arr[ch]>1){
return;
}
queue.add(ch);
}
//return the first appearence once char in current stringstream
public char FirstAppearingOnce() {
while (!queue.isEmpty() && arr[queue.peek()]>1){
queue.poll();
}
if(queue.isEmpty()){
return '#';
}
return queue.peek();
}
}
5、剑指Offer_38:字符串的排列
题目:输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
思路:即通过字符交换,先固定第 1位字符( n种情况)、再固定第 2位字符( n−1 种情况)、… 、最后固定第 n 位字符( 1种情况)。
aple,如何确定字符转在每一位的字符?
1、确定第1位字符:a与后面p交换确定p、a与后面l交换确定l、a与后面e交换确定e、最终为a、p、l、e
2、确定第2位字符:排除a后,p与l交换确定l、p与e交换确定e ,第二位字符为p、l、e
排除p后,a与l交换确定l、a与e交换确定e ,第二位字符为a、l、e
…以此类推
3、递归回溯到当前层的时候,为了消除当前层去递归的时候进行交换字符的影响,需要再交换为原来的位置。
比如 aple:
a与p交换,得到第一位的字符p、继续向下递归寻找第二位的字符、第三位的字符、、、、当回到第一位时需要确定第一位的下一个字符时,a需要与l交换,但是此时字符串为pale,变成了p与l交换,为了消除这种影响,需要在递归回溯到本层时再交换回来。
4、如果字符串中有重复字符,那么就会出现重复排列方案,为排除重复方案,需在固定某位字符时,保证 “每种字符只在此位固定一次” ,即遇到重复字符时不交换,直接跳过。或者直接把左右排列情况都找出来,最后统一去除重复方案。
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
public class Solution {
public ArrayList<String> Permutation(String str) {
/**
* 递归回溯算法:
* 1、通过字符串交换先固定第一位,让当前字符与后面的每一位进行交换
* aple:得出第一位的可能字符为a、p、l、e
* 2、通过递归去固定第2位、第3位、、、、
* 3、递归回溯到第一位时,需要消除交换造成的影响,将字符交换回来
* 4、递归终止的条件是固定到最后一位时
* 5、通过HashSet去重
* 6、通过Collections集合类进行排序
*/
//1、将字符串转换为字符数组
char[] strArr = str.toCharArray();
//2、定义一个集合用来存放返回结果
ArrayList<String> list = new ArrayList<>();
dfs(strArr,list,0,str.length());
//3、去重
list = new ArrayList<String>(new HashSet<String>(list));
//4、排序
Collections.sort(list);
return list;
}
/**
*
* @param strArr 传入的字符数组
* @param list 用来存放结果的集合
* @param index 固定第index位
* @param length 数组的长度
*/
public void dfs(char[] strArr,ArrayList<String> list,int index,int length){
//1、递归终止的条件,固定到最后一位时
if(index==length-1){
String res = change(strArr);
//添加排列方案
list.add(res);
return;
}
for(int i=index;i<length;i++) {
//2、固定第index位,将strArr[i]固定在第index位
char temp = strArr[i];
strArr[i] = strArr[index];
strArr[index] = temp;
//3、递归固定第index+1位
dfs(strArr, list, index + 1, length);
//4、递归回溯到本层时需要消除字符交换的影响,再将字符交换回来,让原来的字符与下一位字符交换
temp = strArr[i];
strArr[i] = strArr[index];
strArr[index] = temp;
}
}
public String change(char[] a){
StringBuilder res = new StringBuilder();
for(char value: a){
res.append(value);
}
return res.toString();
}
}
6、剑指Offer_45:把数组排成最小的数
题目:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
/**
* 在排序时传入一个自定义的Comparator实现,重新定义String类内的排序方法,
* 若拼接s1+s2 > s2+s1,那么把s2在拼接时放在前面,以此类推,将整个String列表排序后再拼接起来。
* 如:30、3--- 303<330 ---303
*/
public class Solution {
public String PrintMinNumber(int [] numbers) {
/**
* 1、将整型数组转换为字符串数组
*/
String[] str = new String[numbers.length];
for(int i=0;i<numbers.length;i++){
str[i] = String.valueOf(numbers[i]);
}
/**
* 2、自定义比较器比较拼接后字符串
* public int compareTo( NumberSubClass referenceName )
* 如果指定的数大于参数返回1: System.out.println(5.compareTo(3)); //1 排序为:3、5
*/
Arrays.sort(str, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return (o1+o2).compareTo(o2+o1);
}
});
/**
*3、将字符串数组拼接成字符串
*/
StringBuilder sb = new StringBuilder();
for(int i=0;i<str.length;i++){
sb.append(str[i]);
}
return sb.toString();
}
}
7、剑指Offer_67:把字符串转换成整数
题目:写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;
假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
public class Solution {
/**
* 1、字符串的前面出现空格,丢弃
* 2、三种情况,即 ''+'' , ''−'' , ''无符号",新建一个变量保存符号位,返回前判断正负。
* 3、遇到首个非空字符,直接返回
* 4、如果是数字字符:
* 1、将字符转换成数字:此 "数字字符的ASCII" 与 "0的ASCII相减"
* 2、将字符进行拼接res = 10*res-ascii(c)-ascii(0)
*/
public int StrToInt(String str) {
//1、使用字符串的trim()函数取出字符串两端的空格
char[] charArray = str.trim().toCharArray();
if(charArray.length==0){
return 0;
}
int i=1;
//2、定义变量保存符号位
int sign = 1;
//说明符号位为负
if(charArray[0]=='-'){
sign=-1;
}else if(charArray[0]!='+'){
i=0;
}
long res = 0;
//3、将字符串转换为整数
for(int j=i;j<charArray.length;j++) {
if (charArray[j] < '0' || charArray[j] > '9') {
break;
}
res = res * 10 + (charArray[j] - '0');
//判断res是否超出整型范围
if (res > Integer.MAX_VALUE) {
return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
}
return sign * (int)res;
}
}