算法-数据结构-字符串

文章目录

字符串所需所需方法

方法名说明
String
String(char[] value)构造新的字符串,使用已给的字符数组
String (char[] value,int offset,int count)构造新的字符串,包含字符数组,从下标offset开始共count个字符
String replace(char oldChar,char newChar)将字符串的中oldChar替换为newChar,并返回
char[] toCharArray()将字符串转化为新的字符数组
String substring(int beginIndex,int endIndex)返回一个字符串,从源字符串下标beginInext到endIndex
String substring(int beginIndex)返回一个字符串,从源字符串下标beginIndex到字符串结尾
StringBuilder
void setCharAt(int index,char ch)使用指定索引处字符设置ch
char charAt(int index)返回指定索引处的char对象

反转字符串

需求
编写一个函数,其作用是将输入的字符串反转过来。
输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
示例

  • 输入:s = [“h”,“e”,“l”,“l”,“o”] 输出:[“o”,“l”,“l”,“e”,“h”]
  • 输入:s = [“H”,“a”,“n”,“n”,“a”,“h”] 输出:[“h”,“a”,“n”,“n”,“a”,“H”]

思路梳理之两个数交换

两数交换思路整理:num[0] = a num[1] = b
① 使用中间变量
temp = num[0]
num[0] = num[1]
num[1] = temp
② 不使用中间变量,先加
num[0] = num[0] + num[1] = a +b
num[1] = num[0] - num[1] = a+b - b = a
num[0] = num[0] - num[1] = a + b - a = b
③ 不使用中间变量,先减
num[0] = num[0] -num[1] = a - b
num[1] = num[0] + num[1] = a - b + b = a
num[0] = num[1] - num[0] = a - (a-b) = a-a+b = b
④  异或处理 
假设num[0] = 3 = 0011 num[1] = 5 = 0101
num[0] = num[0] ^ num[1] = 0011 ^ 0101 = 0110
num[1] = num[0] ^ num[1] = 0110 ^ 0101 = 0011
num[0] = num[0] ^ num[1] = 0110 ^ 0011 = 0101

方法1 :双指针

 public void reverseString(char[] s) {
        char temp = 'a';
        int len = s.length;
        for(int i = 0 , j = len-1; i < j ; i++,j--){
            temp = s[i];
            s[i] = s[j];
            s[j] = temp;
        }
    }

时间0ms,内存是47.8M;

方法2:StringBuilder的reverse方法

public void reverseString(char[] s) {
// String(char[] value):分配一个新的String 用来表示当前包含的字符数组的字符序列
        String str = new String(s);
        StringBuilder sb = new StringBuilder(str);
        sb.reverse();
        str = sb.toString();
        for(int i = 0 ; i < str.length();i++){
            s[i] = str.charAt(i);
        }
    }

时间1ms、内存是47.7M

方法3:遍历字符串收尾交换(中间变量)

public void reverseString(char[] s) {
        //对称交换
        char temp = s[0];
        for(int i = 0; i < s.length/2;i++){
            temp = s[i];
            s[i] = s[s.length-i-1];
            s[s.length-i-1] = temp;
        }
    }

时间:0ms;内存:48.2M;

方法4:遍历字符串收尾交换(先加)
 public void reverseString(char[] s) {
        //对称交换
        for(int i = 0; i < s.length/2;i++){
            s[i] = (char)(s[i] + s[s.length-i-1]);
            s[s.length-i-1] = (char)(s[i] - s[s.length-i-1]);
            s[i] = (char)(s[i] - s[s.length-i-1]);
        } 
    }
方法5:遍历字符串首尾交换(先减)
  public void reverseString(char[] s) {
        //对称交换
        for(int i = 0; i < s.length/2;i++){
            s[i] = (char)(s[i] - s[s.length-i-1]);
            s[s.length-i-1] = (char)(s[i] + s[s.length-i-1]);
            s[i] = (char)(s[s.length-i-1]-s[i]);
        } 
    }
方法6:遍历字符串首尾交换(异或处理)
 public void reverseString(char[] s) {
        //对称交换
        for(int i = 0; i < s.length/2;i++){
            s[i]^= s[s.length-i-1];
            s[s.length-i-1] ^= s[i];
            s[i] ^= s[s.length-i-1];
        } 
    }

时间:0ms,内存消耗:47.9M

方法7:递归解决(先交换再递归)
 public void reverseString(char[] s) {
        reverseStringHelper(s,0,s.length-1);
    }
    public void reverseStringHelper(char[] s, int left , int right){
        if(left < right){
        // 区别位置
            swap(s,left,right);
            reverseStringHelper(s,++left,--right);
        } 
    }
    public void swap(char[] s,int left,int right){
        s[left] = (char)(s[left] + s[right]);
        s[right] = (char)(s[left] - s[right]);
        s[left] = (char)(s[left] - s[right]);
    }
方法8:递归解决(先交换再递归)
 public void reverseString(char[] s) {
        reverseStringHelper(s,0,s.length-1);
    }
    public void reverseStringHelper(char[] s, int left , int right){
        if(left >= right)
            return ;
           // 区别位置 该位置只能写 left+1 right-1 这种形式
           // 不行写 left++ right-- 这种类型 会报java.lang.stackOverFlowError 
        reverseStringHelper(s,left+1,right-1);
        swap(s,left,right);
        
    }
    public void swap(char[] s,int left,int right){
        s[left] = (char)(s[left] + s[right]);
        s[right] = (char)(s[left] - s[right]);
        s[left] = (char)(s[left] - s[right]);
    }

整数翻转

需求
一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。

案例
输入:x = 123 输出:321
输入:x = -123 输出:-321 (符号不变)
输入:x = 120 输出:21 (翻转后0不可以打头)
输入:x = 0 输出:0 (0输出的还是0)

整数翻转思路

数字直接翻转

int y = 0;
while(x != 0){
	y = y * 10 + x % 10;
	x = x / 10 ;
}

采用StringBuilder的reverse方法

String s = String.valueOf(x); // int ⇒  String
StringBuilder sb = new StringBuilder(s); // String ⇒  StringBuilder
sb.reverse(); // 字符串翻转
String ss = sb.toString(); // StringBuilder ⇒  String
int y = Integer.valueOf(ss); // String ⇒ int
溢出比较思路

更新前后数字进行比较

int res = 0; // 存储之前的更新值
int new_res = 0; // 存储现在的更新值
int n = 0; // 表示数字的最后一位
while(x != 0 ){
	n = x % 10 ;
	new_res = res * 10 + n ;
	if(((new_res - n) /10) != res) // 相等说明没有产生溢出,不相等则说明产生了溢出
		return 0;
	// 更新x的值
	x = x / 10;
	// 更新res的值
	res = new_res ;
}

通过字符串比较

String s_x = new StringBuilder(String.valueOf(x)).reverse().toString();
String s_max = String.valueOf(Integer.MAX_VALUE);
if(s_x.length() >= s_max.length() && s_x.compareTo(x_max) > 0)
	return 0;

NumberFormatException

x 这个是正数。
try{
	int res = Integer.valueOf(new StringBuilder(String.valueOf(x)).reverse().toString());
}catch(NumberFormatException e){
	return 0;
}

强制转化判断是否溢出

long res = 0;
while( x != 0){
	res = res * 10 + x % 10;
	x = x / 10;
}
return (int)res == res ? (int)res : 0 ;

通过比较Integer.MAX_VALUE和Integer.MIN_VALUE

long res = 0;
while( x!= 0) {
	res = res *10 + x %10;
	x = x / 10;
}
if(res > Integer.MAX_VALUE || res < Integer.MIN_VALUE)
	return 0;

与前31位的最大值进行比较

int y = 0 ;
while(x != 0 ){
	if(Math.abs(x) > (Integer.MAX_VALUE - Math.abs(x % 10)) / 10 )
		return 0;
}
方法1:直接翻转数字

思路:
每次取数字的最后一位,然后于res10 + res ,乘10可以实现数字的向左移动一位。
假设:1234 res = 0
取4 则 res = 0
10 + 4 = 4
取3 则 res = 410+3 = 43
取2 则 res = 43
10+2 = 432
取1 则 res = 432*10+1 = 4321

分析:

// 测试用例出现问题1,534,236,469 翻转后有数据超出32位有符号数范围。
 if((new_res - t) /10 != res)
           return 0;
 首先32位有符号数的范围是:-2,147,483,648 ~ 2,147,486,647
 接着当该数据翻转到最后一位时,结果变成9,646,324,351 (new_res)这时超出了数据表示范围,需要输出为0
 而该数前一步的值是964,632,435 (res),1(t),而生成new_res的过程需要 res*10 则此时会有溢出发生。
 (new_res - 1/10  = 964632435 和res是相等的。

idea_代码

package com.javaface5.test520;

public class test1 {
    public int reverse(int x) {
        int res = 0; // 原始res
        int new_res = 0; // 更新的res
        int t = 0; // 数的最后一位
        if(x == 0 ) return 0;
        while( x != 0 ){//条件判断
            t = x % 10;
            // 具体执行
            new_res = res *10 + t ;

            // 判断是否有溢出,res*10+t如果溢出,则不是真实的结果值;进而逆运算会出错。
            if((new_res - t) /10 != res) {
                System.out.println("new_res" + new_res);
                // 最开始数据是:1534236469 ,105638959
                // 前一个res = 964632435,该数*10 会溢出,
                // 变成105638958 + 1 变成 105638959
                System.out.println("res" + res);
                // 最开始数据是:1534236469 , 目前值是964632435
                System.out.println("t" + t);
                // 最开始数据是:1534236469 ,t是1
                return 0;
            }
            // 条件控制
            x = x / 10 ;
            // 更新res值
            res = new_res;
        }
        return res;
    }

    public static void main(String[] args) {
        test1 t = new test1();
        int x = 1534236469;
        int result = t.reverse(x);
        System.out.println("-----------");
        System.out.println(result);
    }
}

代码:

public int reverse(int x) {
        int res = 0; // 原始res 
        int new_res = 0; // 更新的res
        // 两者都采用int类型,则当超出int范围时,会直接进行转换成int类型
        int t = 0; // 数的最后一位
        if(x == 0 ) return 0;
        while( x != 0 ){//条件判断
            t = x % 10;
            // 具体执行
            new_res = res *10 + t ;

            // 判断是否有溢出
            if((new_res - t) /10 != res)
                return 0;

            // 条件控制
            x = x / 10 ;
			// 更新res值
            res = new_res;
        }
        return res;
    }
方法2:long+强制类型转换得到是否溢出
public int reverse(int x) {
        // 采用long类型,保证x转换后的数据不会被修改 因为超过int表示范围
        long res = 0;
        if(x == 0 ) return 0;
        while( x != 0 ){//条件判断
            // 具体执行
            res = res *10 + x%10 ;
            // 改变控制变量
            x = x / 10 ;
        }
        return (int)res == res ? (int)res : 0;
    }
方法3:巧用String、NumberFormatException

思路:
1 判断x是否等于0 是 输出0
2 判断x的符号,后续操作都针对正数处理,使用Math.abs得到绝对值
3 使用字符串反转 String.valueOf、reverse、.toString、Integer.valueOf

代码:

public int reverse(int x) {
        if(x == 0) 
            return 0;
        int flag = x > 0 ? 1 : -1;
        x = Math.abs(x);
        int res = 0;
        try{
        // String.valueOf(int类型数据) =》转成String类型
        // new StringBuffer(字符串) =》StringBuffer对象
        // StringBuffer对象.reverse() =》 实现字符串翻转
        // StringBuffer对象.toString() =》 String对象
        // Integer.valueOf(字符串) =》 int数据
            res = Integer.valueOf(new StringBuffer(String.valueOf(x)).reverse().toString());
        }catch(NumberFormatException e){
            return 0;
        }
        return flag*res;
    }
方法4:溢出比较采用字符串

分析:

 // 注意xhj
        if(ss.length() >= sMax.length() && ss.compareTo(sMax) >0 )
            return 0;
      // 关于这里字符串的比较,原因:
      // 首先32位有符号数的范围是:-2,147,483,648 ~ 2,147,486,647
      // 长度相等也可能出现溢出的情况,比如:3,000,000,000 的这种情况,所以还需要比较字符串的内容。

代码

 public int reverse(int x) {
        // 如果x是0,则直接返回0
        if(x == 0)
            return 0;

        // 获取x的符号
        int flag = x > 0 ? 1:-1;
        x  = Math.abs(x);
        // x 翻转 再变成字符串
        String ss = new StringBuilder(String.valueOf(x)).reverse().toString();
        // 取Integer.MAX_VALUE的数据 将其变成字符串
        String sMax = String.valueOf(Integer.MAX_VALUE);
       // 注意xhj
        if(ss.length() >= sMax.length() && ss.compareTo(sMax) >0 )
            return 0;
        return flag * Integer.valueOf(ss);
    }

时间:2ms,内存:38.3M。

方法5:long+Integer.MAX_VALUE/MIN_VALUE
public int reverse(int x) {
        long temp = 0;
        while(x != 0){
            temp = temp *10 + x % 10 ;
            x = x/ 10 ;
        }
        if(temp > Integer.MAX_VALUE || temp < Integer.MIN_VALUE)
            return 0;
        return (int)temp;
    }
方法6:int y + 取前31位的最大值进行比较

思路:

if(Math.abs(y) > (Integer.MAX_VALUE - Math.abs(x%10)) /10 )

y 是翻转后的值,如果原始值是这个1534236469 翻转后的值 的前一个是一个964632435(Integer.MAX_VALUE - Math.abs(x%10)) /10 表示的是 Integer.MAX_VALUE就差最后一位就全部翻转完了情况,取前31位的最大值。

代码:

 public int reverse(int x) {
        int y = 0;
        while(x != 0){
            if(Math.abs(y) > (Integer.MAX_VALUE - Math.abs(x % 10) ) / 10 )
                return 0;
            y = y * 10 + x % 10;
            x = x / 10 ;
        }
        return (int)y;
    }

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

需求
给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。
示例
输入: s = “leetcode” 输出: 0
输入: s = “loveleetcode” 输出: 2
输入: s = “aabb” 输出: -1

方法1:遍历字符存储字符个数

关键思路是:
将字符串编程字符数组;
使用getOrDefault方法;
Map集合键值对类型结构;键 = 字符,值 = 字符个数;

public int firstUniqChar(String s){
	// 创建map集合,键是字符,值是字符个数
	Map<Character,Integer> map = new HashMap<Character,Integer>();
	// 把字符串转换成字符数组
	char[] array = s.toCharArray();
	// 遍历字符数组,统计字符个数
	for(char ch : array){
		map.put(ch,map.getOrDefault(ch,0) +1);
	}
	// 找寻值是1 
	for(int i = 0; i < s.length();i++){
		if(map.get(array[i]) == 1){
			return i;
		}
	}
	return -1;
}

新的方法:
public interface Map<K,V>

方法名说明
default V getOrDefault(Object key,V defaultValue)返回指定键映射到的值,如果此集合不包含该键的映射,则返回defaultValue
方法2:遍历字符串 统计字符个数

方法思路:
使用的是int类型数组
字符串转字符数组

public int firstUniqChar(String s){
	// 创建int类型数组存放字符元素个数
	int[] count = new int[26];
	// 字符串变成字符数组
	char[] array = s.toCharArray();
	// 遍历统计个数
	for(int i = 0; i < s.length(); i++){
		count[array[i] - 'a'] ++;
	}
	// 遍历找到字符只有一个的元素
	for(int j = 0 ; j < s.length() ; j++){
		if(count[array[j] - 'a' ] == 1){
			return j;
		}
	}
	return -1;
}
方法3:使用String方法indexOf和lastIndexOf
public int firstUniqChar(String s) {
	for(int i = 0; i < s.length() ; i++){
		if(s.indexOf(s.charAt(i)) == s.lastIndexOf(s.charAt(i))){
			return i;
		}
	}
	return -1;
}

String方法:

方法名说明
int indexOf(String str)返回指定子串第一次出现在字符串中的索引
int lastIndexOf(String str)返回指定字符串的最后一次出现在字符串中的索引

有效的字母异位词

需求
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

示例
输入: s = “anagram”, t = “nagaram” ; 输出: true
输入: s = “rat”, t = “car” ; 输出: false

方法1:统计两个字符串中字母个数
public boolean isAnagram(String s, String t) {
	int[] count1 = new int[26];
	int[] count2 = new int[26];
	char[] array1 = s.toCharArray();
	char[] array2 = t.toCharArray();
	
	if(s.length() != t.length()){
		return false;
	}
	for(int i =0; i < s.length() ; i++){
		count1[array1[i] - 'a']++;
		count2[array2[i] - 'a']++;
	}
	for(int i = 0 ; i < count1.length() ; i++){
		if(count1[i] != count2[i]){
			return false;
		}
	}
	return true;
}
方法2:统计一个字符串字母个数,另一个减去对应内容
public boolean isAnagram(String s, String t) {
        int[] count1 = new int[26];
        char[] array1 = s.toCharArray();
        char[] array2 = t.toCharArray();

        if(s.length() != t.length()){
            return false;
        }
        for(int i = 0; i < s.length() ; i++){
                count1[array1[i] - 'a']++;
                count1[array2[i] - 'a']--;
        }
        for(int in:count1){
            if(in != 0){
                return false;
            }
        }
        return true;
    }
方法3:排序后再比较
public boolean isAnagram(String s, String t) {
        char[] array1 = s.toCharArray();
        char[] array2 = t.toCharArray();
        Arrays.sort(array1);
        Arrays.sort(array2);
        return Arrays.equals(array1,array2);
    }

Arrays涉及方法:

方法名说明
static void sort(char[] a)按照数字顺序排雷指定的数组
static boolean equals(char[] a,char[] b)如果两个指定的字符串彼此相等,则返回true
方法4:一次遍历使用count统计元素种类

思路:
通过int[] 数组去统计字符串中元素的个数;
count 统计元素种类的新增和减少;

public boolean isAnagram(String s, String t) {
	// 统计元素种类count
	int count = 0;
	// 字符串变成字符数组
	char[] array1 = s.toCharArray();
	char[] array2 = t.toCharArray();
	// 统计字符个数
	int[] map = new int[26];

	if(s.length() != t.length()){
		return false;
	}
	// 遍历两个字符数组
	for(int i = 0 ; i < s.length() ; i++){
		// 字符串s用于查看新增字符
		if(++map[array1[i] - 'a'] == 1){
			count ++;
			// 统计元素之后发现是1 说明字符是新增的
		}
		// 字符串t用于查看减少字符
		if(--map[array2[i] - 'a'] == 0){
			count --;
		}
	}
	return count == 0;
}

验证回文串

需求
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
xhj:回文串 = 字符串前后遍历都是一样的

示例
输入: “A man, a plan, a canal: Panama” 输出: true 解释:“amanaplanacanalpanama” 是回文串
输入: “race a car” 输出: false 解释:“raceacar” 不是回文串

方法1:双指针+isLetterOrDigit+字符数组(while)+toLowerCase

Character类:

方法名说明
static boolean isLetterOrDigit(char ch)确定指定的字符是字母还是数字
public boolean isPalindrome(String s){
	// 由于不区分大小写,将字符串中所有大写转为小写
	s = s.toLowerCase();
	// 定义两个指针
	int left = 0, right = s.length()-1;
	// 将字符串转为字符数组
	char[] array = s.toCharArray();
	// 采用循环遍历
	while(left < right){
		// 判断left指针所指定的位置是字母还是数字
		while(left < right && !(Character.isLetterOrDigit(array[left])))
			left++;
		// 判断right指针所指定的位置是字母还是数字
		while(left < right && !(Character.isLetterOrDigit(array[right])))
			right--;
		// 判断两个指针所指向字符是否一致
		if(array[left] != array[right])
			return false;
		left++;
		right--;
	}
	return true;
}
方法2:双指针+isLetterOrDigit+字符数组(for)+toLowerCase
public boolean isPalindrome(String s){
	// 将字母统一转为小写字母
	s = s.toLowerCase();
	// 将字符串转为字符数组
	char[] array = s.toCharArray();
	// 定义两个指针
	int left = 0,right = s.length()-1;
	// 使用for循环遍历字符数组
	for(; left < right ; left++,right--){
		while(left < right && !(Character.isLetterOrDigit(array[left])))
			left++;
		while(left < right && !(Character.isLetterOrDigit(array[right])))
			right--;
		if(array[left] != array[right] )
			return false;
	}
	return true;
}
方法3:双指针+isLetterOrDigit
public boolean isPalindrome(String s) {
	// 将字符串转为小写
	s = s.toLowerCase();
	// 遍历字符串
	for(int i = 0 , j = s.length()-1 ; i < j ; i++,j--){
		while(i < j && !(Character.isLetterOrDigit(s.charAt(i))))
			i++;
		while(i < j && !(Character.isLetterOrDigit(s.charAt(j))))
			j--;
		if(s.charAt(i) != s.charAt(j))
			return false;
	}
	return true;
}
方法4:正则匹配+reverse

String类:

方法名说明
String replaceAll(String regex,String replacement)用给定的参数replacemet 替换 字符串所有匹配给定的正则表达式的子字符串

此案例中的正则化含义:

正则化表达式规则可以匹配
^开头字符串开头(在Java中的可有可无)
[A-F0-9xy]指定范围的字符A,…,F,0,…,9,x,y
不是很理解。
public boolean isPalindrome(String s){
	// 正则化匹配
	String ss = s.replaceAll("[^A-Z0-9a-z]","").toLowerCase();
	// 使用StringBuilder的字符串反转
	String sReverse = new StringBuilder(ss).reverse().toString();
	return ss.equals(sReverse);
}
方法5:递归方法
    public boolean isPalindrome(String s) {
        return isPalindromeHelper(s, 0, s.length()-1 );
    }
    public boolean isPalindromeHelper(String s,int left,int right){
        if(left >= right){
            return true;
        }
        while(left < right && !Character.isLetterOrDigit(s.charAt(left)))
            left++;
        while(left < right && !Character.isLetterOrDigit(s.charAt(right)))
            right--;
        return Character.toLowerCase(s.charAt(left))== Character.toLowerCase(s.charAt(right)) && isPalindromeHelper(s,++left,--right);

    }

注意:return部分采用以下这样的形式:会报错。超出时间限制。

return s.toLowerCase().charAt(left) == s.toLowerCase().charAt(right) && isPalindromeHelper(s, ++left,--right);

原因可能是:对字符串整体做多次toLowerCase操作,可能会造成时间的消耗。

字符串转换成整数(atoi)

需求
请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。
函数 myAtoi(string s) 的算法如下:
①读入字符串并丢弃无用的前导空格
②检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
③读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
④如果整数数超过 32 位有符号整数范围 [−231, 231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231− 1 。
返回整数作为最终结果。

方法1:溢出判断使用Integer.MAX_VALUE
public int myAtoi(String s) {
        // 将字符串前后的空格去掉
        s = s.trim();
        // 字符串为空
        if(s.length() == 0){
            return 0;
        }
        // 用于存储数字的符号,默认是正数
        int flag = 1;
        // 遍历字符串的索引
        int index = 0;
        // 字符串的长度
        int len = s.length();
        // 除了符号之外的结果res
        int res = 0;
        // 数字取的中间值
        int temp = 0;

        // 确定符号,只确定第一个字符
        if(s.charAt(index) == '-' || s.charAt(index) == '+')
            flag = s.charAt(index++) == '+' ? 1 :-1;

        for(;index < len;index ++){
            // 将字符取出来转化为int类型
            temp = s.charAt(index) - '0';
            // 判断是否是数字,如果不是则跳出
            if( temp < 0 || temp > 9 ){
                break;
            }
            // 越界处理 不用比较正负值,因为符号最后才弄
            // 直接判断res结果是否大于最大值  或者也可以判断是否有溢出
            // 判断此时的res结果是不是等于最大值少最后一位,且新获得的数字位tmep大于最大值的最后一位
            if(res > Integer.MAX_VALUE /10  || (res == Integer.MAX_VALUE /10 && temp >Integer.MAX_VALUE %10) ){
                return flag == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE ;
            }else{
                res  = res * 10 + temp;
            }
        }
        return flag * res;
    }
方法2:溢出判断采用result /10 != result_before
 public int myAtoi(String s) {
 		// 将字符串前后空格去掉
        s = s.trim();
       	// 数字的符号
        int flag = 1;
        // 遍历字符串的索引
        int index = 0;
        // 字符串的长度
        int len = s.length();
        if(len == 0){
            return 0;
        }
        // 字符串取的中间值
        int temp = 0;
        // 字符串转后的最终结果
        int res = 0;
        // 字符串转换的值
        int res_temp = 0;
		// 判断第一个字符是 "-" 将符号标志位置为-1,且索引index向后移动
        if(s.charAt(index) == '-'){
            flag = -1;
            ++index;
           // 判断第一个字符是“+” 将索引index向后移动
        }else if(s.charAt(index) == '+'){
            ++index;
        }
        // 遍历字符串    
        for(; index < len ; index ++){
        	// 判断字符是不是数字,不是直接跳出循环,表示结束;
            if(!Character.isDigit(s.charAt(index))){
                break;
            }
            // 将字符变成数字
            temp = s.charAt(index) - '0';
            // 数字拼接
            res_temp = res * 10 + temp;
            // 如果拼接后的数字 对10取整,与之前计算的结果不一样,那么说明该数溢出
            if(res_temp /10 != res){
            	// 溢出就需要查看符号flag的值,如果是1表示最大值溢出、如果是0表示最小值溢出;
                return flag == 1? Integer.MAX_VALUE:Integer.MIN_VALUE;
            }
            // 将中间的结果值res_temp赋值给res
            res = res_temp;
        }
        return flag*res;
    }

实现 strStr() 函数

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

说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。

示例:
输入:haystack = “hello”, needle = “ll” ;输出:2
输入:haystack = “aaaaa”, needle = “bba” ;输出:-1

方法1:使用String方法indexOf
public int strStr(String haystack, String needle) {
    int result = -1;
    if(needle.length() == 0)
        return 0;
    if(haystack.length() < needle.length() )
        return -1;
    result = haystack.indexOf(needle);
    return result;
}
方法2:逐个字符比较

分析:
在这里插入图片描述

public int strStr(String haystack, String needle) {
	if(needle.length() ==0)
		return 0;
	// 两个索引,对应两个字符串
	int i = 0;
	int j = 0;
	while( i < haystack.length() && j < needle.length()){
		if(haystack.charAt(i) == needle.charAt(j)){
			i++;
			j++;
		}else{
		// 重新开始匹配字符串
			i = i - j +1; //关键是这一步。
			j = 0;
		}
	}
	// 说明找到字符串了
	if(j == needle.length()){
		return i - j;
	}
	// 如果没有找到返回-1
	return -1;
}
方法3:KMP算法

KMP算法图示:
在这里插入图片描述
代码分析:

next数组 = 指定的下标前具有相同的字符串数量;
案例:
ABCABA
数组next[0] = -1 固定的,因为第一个A前面没有字符;
数组next[1] = 0 因为B前面就一个A,没有重复的,所以是0;
数组next[2] = 0 因为C前面是AB,没有重复的,所以是0;
数组next[3] = 0 因为A前面是ABC,没有重复的,所以是0;
数组next[4] = 1 因为B前面是ABCA,最开始的A与第二个A相同,所以是1;
数组next[5] = 2 因为A前面是ABCAB,最开始后的B与后面的B相同,所以是2;
注意:只有指定字符前面的字符和第一个字符匹配成功的时候才能往后匹配;

WABCABA
数组next[0] = -1 固定的,因为第一个W前面没有字符;
数组next[1] = 0 因为A前面就一个W,没有重复的,所以是0;
数组next[2] = 0 因为B前面是WA,没有和W匹配的元素,所以是0;
数组next[3] = 0 因为C前面是WAB,没有和W匹配的元素,所以是0;
数组next[4] = 0 因为A前面是WABC,没有和W匹配的元素,所以是0;
数组next[5] = 0 因为B前面是WABCA,没有和W匹配的元素,所以是0;
数组next[6] = 0 因为A前面是WABCAB,没有和W匹配的元素,所以是0

next数组图示
在这里插入图片描述

代码:

public int strStr(String haystack, String needle) {
        // 首先判断字符串的长度=0 的时候,如果为0直接返回0
        if (needle.length() == 0)
            return 0;
        // 定义两个指针,分别遍历两个字符串。
        int i = 0;
        int j = 0;
        // next表示指定下标前具有相同的字符串数量。
        int[] next = new int[needle.length()];
        // 得到next数组
        getNext(needle, next);

        while (i < haystack.length() && j < needle.length()) {
            // 这里的j == -1 是考虑到next[i] 对j进行赋值的时候;
           if ( j == -1 ||  haystack.charAt(i) == needle.charAt(j)) {
                i++;
                j++;
            } else {
                /**
                 * i = i - j + 1;
                 j = 0;
                 返回到指定的位置,不是返回到匹配失败的下一个位置,这里都好理解,重点是求数组next。
                 这里只要j等于0,在next[j]赋值的之后,j就会等于-1;因为next[0]等于-1
                 */
                j = next[j]; // j回到指定位置
            }
            if (j == needle.length())
                return i - j;
        }
        return -1;
    }
    // 参数传入的是第二个字符串 和 next数组
    private void getNext(String p, int next[]) {
        int len = p.length();
        // 遍历字符串索引
        int i = 0;
        // 
        int j = -1;
        // 第一个元素固定为-1
        next[0] = -1;
        // i 的值小于len-1 也就是i的值会到len-2的位置 数组的倒数第二个。
        // 匹配的时候是当前字符的前一个和前面的匹配,所以最后一个是不参与匹配的;
        while (i < len - 1) {
            // j == -1 表示刚开始的时候需要向后移动 或者 字符相等的时候需要向后移动
            if (j == -1 || p.charAt(i) == p.charAt(j)) {
                i++;
                j++;
                next[i] = j;
            } else
                j = next[j];
        }
    }

外观数列

需求
给定一个正整数 n ,输出外观数列的第 n 项。
「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。
你可以将其视作是由递归公式定义的数字字符串序列:
countAndSay(1) = “1”
countAndSay(n) 是对 countAndSay(n-1) 的描述,然后转换成另一个数字字符串。

示例

1.     1
2.     11
3.     21
4.     1211
5.     111221
第一项是数字 1 
描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 "11"
描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 "21"
描述前一项,这个数是 21 即 “ 一 个 2 + 一 个 1 ” ,记作 "1211"
描述前一项,这个数是 1211 即 “ 一 个 1 + 一 个 2 + 二 个 1 ” ,记作 "111221"
描述前一项,这个数是 111221 即 “ 三 个 1 + 两 个 2 + 一 个 1 ” ,记作 "312211"

要 描述 一个数字字符串,首先要将字符串分割为 最小 数量的组,每个组都由连续的最多 相同字符 组成。然后对于每个组,先描述字符的数量,然后描述字符,形成一个描述组。要将描述转换为数字字符串,先将每组中的字符数量用数字替换,再将所有描述组连接起来。

方法1:递归
public String countAndSay(int n) {
        // 递归出口
        if(n == 1){
            return "1";
        }
        // 假设我们上一次递归的结果 也就是迭代递归的情况
        String s = countAndSay(n-1);
        // 将字符串变成字符数组
        // char[] array = s.toCharArray();
        // 创建一个StringBuilder对象用于存储字符
        StringBuilder sb = new StringBuilder();
        // 添加的字符
        char result = s.charAt(0);
        // 创建变量用于统计个数
        int count = 0;
        // 遍历字符数组
        for(int i = 0 ; i < s.length(); i++){
            if(s.charAt(i) == result){
                count++;
            }else{
                sb.append(count);
                sb.append(result);
                count = 1;
                result = s.charAt(i);
            }
        }
        // 为了满足有些字符串只经过if条件判断就直接退出for循环的情况。
        sb.append(count);
        sb.append(result);
        return sb.toString();
    }

分析:

n = 5,此时会一直迭代到n = 1 返回字符串“1” countAndSay(1)执行结束;

countAndSay(2):
此时s =1”      result =1’        count = 0 
进入for循环(i = 0 i< 1 i++)
if条件满足 则count = 1 i = 1 for循环退出
执行sb的拼接操作。
sb.append(count);
sb.append(result);
返回 “11countAndSay(3):
此时s =11”   result =1”  count = 0
进入for循环(i = 0 i < 2 i++)
if条件满足 则count = 1 i = 1 继续判断
if条件满足 则count = 2 i = 2 for循环退出
sb.append(count);
sb.append(result);
返回“21countAndSay(4):
此时s =21”   result =2”  count = 0
进入for循环(i= 0 i < 2 i++)
if条件满足 则count = 1 i = 1
else条件满足 则 字符串“12” count = 1 result =1”  i = 2 for循环退出
sb.append(count);
sb.append(result);
返回“1211countAndSay(5):
此时s =1211”  result =1”  count = 0
进入for循环(i = 0 i < 4 i++if条件满足 则count = 1 i = 1
else 条件满足 则 字符串为“11” count = 1 result = 2 i = 2
else 条件满足 则 字符串为“1112” count = 1 result = 1 i = 3
if 条件满足 则 count = 2 i = 4  for循环退出
sb.append(count);
sb.append(result);
返回“111221”

理解s.charAt(i) 是result的所在s串中位置的后一个。
方法2:递归 + 双指针

代码:

public String countAndSay(int n) {
        // 迭代的终止条件
        if(n == 1){
            return "1";
        }

        // 假设某次迭代返回字符串
        String s = countAndSay(n-1);

        // 统计个数的变量
        int count = 0;

        // 定义双指针
        int start = 0;
        int end = 0;

        // 定义字符串拼接对象StringBuilder
        StringBuilder sb = new StringBuilder();

        while(start < s.length() && end < s.length()){
            if(s.charAt(start) == s.charAt(end)){
                count++;
                end++;
            }else{
                sb.append(count);
                sb.append(s.charAt(start));
                start = end;
                count = 0;
            }
        }
        // 作用两个:①为了没有进入过else的情况
        // ②为了start = end 赋值后 不满足while循环条件的情况;
        sb.append(count);
        sb.append(s.charAt(start));
        return sb.toString();
    }

图示:
在这里插入图片描述

方法3:双层for循环
    public String countAndSay(int n) {
        // 两个StringBuilder对象,一个是外观数组的输出、
        StringBuilder sb_res = new StringBuilder("1");
        // 另一个是外观数组中间的过程 
        StringBuilder sb_temp;
        // 统计个数
        int count;
        // 要对比的字符
        char temp;
        // 外层循环表示外观数组的输出 
        for(int i = 1 ; i < n ; i++){
        // 将 外观数列i-1的输出结果 赋值给 外观数列i 的中间计算StringBuilder
            sb_temp = sb_res;
            // 清除当前外观数列的StringBuilder
            sb_res = new StringBuilder();
            count = 1;
            temp = sb_temp.charAt(0);
            for(int j = 1; j < sb_temp.length(); j++){
            // 两个代码就这里不一样 不是这里的问题。
                if(sb_temp.charAt(j) == temp){
                    count++;
                }else{
                	// 不满足if 是存放当前比较的temp值 而不是对比的那个字符
                    sb_res.append(count).append(temp);
                    // 修改对比的那个字符
                    temp = sb_temp.charAt(j);
                    count = 1;
                }
            }
            sb_res.append(count).append(temp);
        }
        return sb_res.toString();
    }
方法4:动态规划

最长公共前缀

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

方法1:使用substring +indexOf + 两个while

public String longestCommonPrefix(String[] strs) {
	// 边界值的判断
	if(strs.length == 0 || strs == null) return "";
	
	// 获取字符串数组长度
	int len  = strs.length;
	// 定义匹配字符串
	String s_temp = strs[0];
	// 从第二个字符串数组开始匹配
	int i = 1 ;
	// 循环匹配字符串
	while( i < len ){
	// 等于0说明匹配成功了 反之则说明没有匹配成功
		while( strs[i].indexOf(s_temp) != 0){
			s_temp = s_temp.subString(0,s_temp.length() -1);
		}
		i++;
	}
	return s_temp;
}

备注:
字符串1.indexOf(字符串2) 返回指定字符串2第一次出现的字符串内的索引;
因为都是从头开始匹配的,所以输出的索引都是0;
如果字符串2没有在字符串1中出现,则返回值是-1;

方法2:使用indexOf + substring + for + while

public String longestCommonPrefix(String[] strs) {
        // 边界值判断
        if(strs == null || strs.length == 0)
            return "";

        // 设置字符串为匹配字符串
        String ss = strs[0];

        // 遍历字符串数组
        for(int i = 1 ; i < strs.length ; i++){
            while(strs[i].indexOf(ss) != 0){
                ss = ss.substring(0,ss.length()-1);
            }
        }
        return ss;
    }

方法3:找最小字符串 + startsWith

思路:

1 找字符串长度最小的字符串 
2 使用startWith(String regex)——检测此字符串是否以指定的前缀regex开头

代码:

public String longestCommonPrefix(String[] strs) {
	// 定义最小字符串,以及最小字符串长度
	String smin = strs[0];
	int len = smin.length();
	for(String str : strs){
		if(len > str.length()){
			smin = str;
			len = smin.length();
		}
	}
	// 遍历字符串 匹配最长公共前缀 遍历每个字符串数组元素
	for(String str : strs){
		while(len > 0){
			if(str.startsWith(smin.substring(0,len)){
				break;
			}else{
				len--;
			}
		}
		// 目的是找到每个字符串的最长前缀
		smin = smin.substring(0,len);
	}
	return smin;
}

offer5 替换空格

需求
实现一个函数,把字符串 s 中的每个空格替换成"%20"。

示例
输入:s = “We are happy.” 输出:“We%20are%20happy.”

方法1:使用split(错误注意)

对于字符串全是空格的不能处理。

class Solution {
    public String replaceSpace(String s) {
        // split
        String[] strs = s.split(" ");

        StringBuilder sb = new StringBuilder();
        for(int i = 0 ;i <strs.length ; i++){
            if(i != strs.length-1)
                sb.append(strs[i]).append("%20");
            else
                sb.append(strs[i]);
        }
        return sb.toString();
    }
}

方法2:遍历字符串、StringBuilder拼接

class Solution {
    public String replaceSpace(String s) {
        // 遍历字符串、StringBuilder拼接
        StringBuilder sb = new StringBuilder();
        for(int i = 0 ; i < s.length();i++){
            if(s.charAt(i) != ' ')
                sb.append(s.charAt(i));
            else
                sb.append("%20");
        }
        return sb.toString();
    }
}

时间复杂度:O(N),遍历O(N)、每轮修改O(1),总的来说是O(N);
空间复杂度:O(N),因为新建了StringBuilder对象;

方法3:字符数组+String的构造方法

class Solution {
    public String replaceSpace(String s) {
        char[] array = new char[s.length() * 3];

        int size = 0;
        for(int i = 0 ; i < s.length() ; i++){
            if(s.charAt(i) == ' '){
                array[size++] = '%';
                array[size++] = '2';
                array[size++] = '0';
            }else{
                array[size++] = s.charAt(i);
            }
        }

        String str = new String(array,0,size);
        return str;
    }
}

时间复杂度:O(N)*2 也等于O(N)
空间复杂度:O(N)*3 也等于O(N)

方法4:字符数组toCharArray+StringBuilder

class Solution {
    public String replaceSpace(String s) {
        // 字符数组形式
        char[] array = s.toCharArray();

        // StringBuilder
        StringBuilder sb = new StringBuilder();
        for(int i = 0 ; i < array.length ; i++){
            if(array[i] == ' ')
                sb.append("%20");
            else 
                sb.append(array[i]);
        }

        return sb.toString();
    }
}

方法5:replace方法

class Solution {
    public String replaceSpace(String s) {
        return s.replace(" ","%20");   
    }
}

方法6:双指针

思路
统计字符串中空格的个数,使用StringBuilder对象存储元素。
(其中一个空格,需要在StringBuilder对象中添加两个空格,因为一个空格替换成3个元素,每产生一次替换多2个位置。)
做字符串的拼接:旧字符串拼接 StringBuilder对象。
两个指针left是原始字符串末尾元素;right是新字符串末尾元素。

class Solution {
    public String replaceSpace(String s) {
        // 如果字符串为空 或者 字符串是null 类型 直接返回即可。
        if(s.length() == 0 || s == null)
            return s;

        StringBuilder sb = new StringBuilder();
        for(int i = 0 ; i < s.length(); i++){
            if(s.charAt(i) == ' ')
                sb.append("  ");
                // 这里需要多两个空格,因为将字符串中的空格替换成3个字符,实际上只需要增加两个位置就可以了。
        }
        if(sb.length() == 0)
            return s;
        
        int left = s.length()-1;
        s += sb.toString();
        int right = s.length()-1;

        char[] array = s.toCharArray();
        while(left >= 0){
            if(array[left] == ' '){
                array[right--] = '0';
                array[right--] = '2';
                array[right--] = '%';
            }else{
                array[right--] = array[left];
            }
            left--;
        }
        return new String(array);
    }
}

offer58 左旋转字符串

需求
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。
请定义一个函数实现字符串左旋转操作的功能。
比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

示例
输入: s = “abcdefg”, k = 2 输出: “cdefgab”
输入: s = “lrloseumgh”, k = 6 输出: “umghlrlose”

方法1:字符串拼接

分析
将两个字符串拼接,转化为字符数组String.toCharArray,在通过字符数组转字符串new String(char[],n,原始字符串长度)

class Solution {
    public String reverseLeftWords(String s, int n) {
        // 特殊值
        if(s.length() == 0 || n == 0)
            return s;
        
        String ss = s + s;
        int len = s.length();
        char[] array = ss.toCharArray();
        return new String(array,n,len);
    }
}

方法2:StringBuilder + 字符串遍历

class Solution {
    public String reverseLeftWords(String s, int n) {
        StringBuilder sb = new StringBuilder();
        for(int i = n ; i < s.length() ; i++){
            sb.append(s.charAt(i));
        }
        for(int i = 0 ; i < n ; i++){
            sb.append(s.charAt(i));
        }
        return sb.toString();
    }
}

方法3:字符串切片拼接

class Solution {
    public String reverseLeftWords(String s, int n) {
        return s.substring(n) + s.substring(0,n);
    }
}

方法4:对方法2简化 遍历使用求余

class Solution {
    public String reverseLeftWords(String s, int n) {
        StringBuilder sb = new StringBuilder();

        for(int i = n ; i < n+s.length() ; i++){
            sb.append(s.charAt(i%s.length()));
        }
        return sb.toString();
    }
}

方法5:翻转字符串

class Solution {
    public String reverseLeftWords(String s, int n) {
        StringBuilder sb = new StringBuilder(s);

        reverse(sb,0,n-1);
        reverse(sb,n,s.length()-1);
        
        return sb.reverse().toString();
    }

    public void reverse(StringBuilder sb,int start,int end){
        while(start < end){
            char temp = sb.charAt(start);
            sb.setCharAt(start,sb.charAt(end));
            sb.setCharAt(end,temp);
            start++;
            end--;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值