(二)剑指offer 字符串篇

1.正则表达式匹配

题目

在这里插入图片描述

答案

当模式中的第二个字符不是“ * ”时:
1、如果字符串第一个字符和模式中的第一个字符相匹配,
那么字符串和模式都后移一个字符,然后匹配剩余的。
2、如果字符串第一个字符和模式中的第一个字符相不匹配,直接返回false。
而当模式中的第二个字符是“ * ”时:
如果字符串第一个字符跟模式第一个字符不匹配,则模式后移2个字符,继续匹配。
如果字符串第一个字符跟模式第一个字符匹配,可以有3种匹配方式:
1、模式后移2字符,相当于x * 被忽略;
2、字符串后移1字符,模式后移2字符; 相当于x * 算一次
3、字符串后移1字符,模式不变,即继续匹配字符下一位,因为 * 可以匹配多位,相当于算多次
这里需要注意的是:Java里,要时刻检验数组是否越界。

代码如下:

	public  boolean match(char[] str, char[] pattern){
		 int strStart = 0;
		 int patternStart = 0;
		 return matchPox(str, strStart, pattern, patternStart);
	    }
	public  boolean matchPox(char[] str, int strStart, char[] pattern, int patternStart) {
//		有效检验:是否同时到尾
		if(str.length == strStart && pattern.length <= patternStart) 
			return true;
//		有效检验:str没有到尾 pattern已经到尾
		if(strStart < str.length && patternStart == pattern.length)
			return false;
		
//		判断pattern的第二个字符是否为 *
		if(patternStart + 1 < pattern.length && '*' == pattern[patternStart + 1]) {
//			如果是,在判断str和pattern的第一个是否相等,
			if((strStart != str.length && str[strStart] == (pattern[patternStart])) 
				||  (strStart != str.length && '.' == pattern[patternStart]) ) {
//			如果相等,有三种情况:0, 1 ,多
//				pattern滑动二个,str不变,*代表零个
//				str和滑动一个,pattern滑动两个,*代表一个
//				str滑动一个,pattern不变,*代表多个
				return matchPox(str, strStart, pattern, patternStart + 2)
						|| matchPox(str, strStart + 1, pattern, patternStart + 1)
						|| matchPox(str, strStart + 1, pattern, patternStart);
			}else {
//				如果不等,pattern滑动两个
				return matchPox(str, strStart, pattern, patternStart + 2);
//				
			}
		}else {
//			如果不是:
			if((strStart != str.length && str[strStart] == (pattern[patternStart]))
					|| (strStart != str.length &&  '.' == pattern[patternStart]) ) {
//				str的第一个字符和pattern的第一个字符比较,如果相等,str与pattern各滑一
				return matchPox(str, strStart + 1, pattern, patternStart + 1);
			}else {
//				如果不相等,返回false
				return false;
			}
		}
	}

2.第一个只出现一次的字符

题目

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写)

答案

思路:采用空间换时间的方法。
str依次从0len-1,放入到map中,如果map中有这个key就让value++,如果没有,就让value = 1,最后再遍历一次mapvalue=1的第一个元素就是第一个只出现一次的字符。

	/**
	 * 第一个只出现一次的字符
	 * @param str
	 * @return
	 */
	public static int FirstNotRepeatingChar(String str) {
//		这里用LinkedHashMap,因为它是有序的,放进去的位置不会发生变化
		LinkedHashMap<Character, Integer> map = new LinkedHashMap<Character, Integer>();
		for(int i = 0; i < str.length(); i++) {
			if(!map.containsKey(str.charAt(i))) 
				map.put(str.charAt(i), 1);
			else
				map.put(str.charAt(i), map.get(str.charAt(i)) + 1);
		}
		
		for(int i = 0; i < str.length(); i++) {
			if(map.get(str.charAt(i)) == 1)
				return i;
		}
        return -1;
	}

3.翻转单词顺序列

题目

牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

答案

思路:
第一种:先将字符串用" "分割成字符串数组,最后再用StringBuffer从数组的最后一个元素开始连接,除了最后一次连接外,每次连接都加一个“空格”。

第二种:先将整个字符串反转,然后再将每个单词反转,这个解题方法没有第一个好用,但是这是一种思路,在这道题里面可能没有第一种好用,但是在其他的题里面就不好说了。

 	/**
	 * 翻转单词顺序列
	 * @param str
	 * @return
	 */
	 //第一种思路
	 public static String ReverseSentence(String str) {
		 if(str == null || str.length() == 0)
			 return str;
         if (str.trim().length() == 0)
			return str;
	     String[] strA = str.split(" ");
		 StringBuffer sb = new StringBuffer();
		 
		 for(int i = strA.length - 1; i >= 0; i--) {
			 sb.append(strA[i]);
			 if(i > 0)
				 sb.append(" ");
		 }
		 return sb.toString();
	 }
	 
	 //第二种思路:
	 public String ReverseSentence(String str) {
		if (str == null || str.length() == 0)
			return str;
		if (str.trim().length() == 0)
			return str;
		StringBuilder sb = new StringBuilder();
		String re = reverse(str);
		String[] s = re.split(" ");
		for (int i = 0; i < s.length - 1; i++) {
			sb.append(reverse(s[i]) + " ");
		}
		sb.append(reverse(s[s.length - 1]));
		return String.valueOf(sb);
	}
	public String reverse(String str) {
		StringBuilder sb = new StringBuilder();
		for (int i = str.length() - 1; i >= 0; i--) {
			sb.append(str.charAt(i));
		}
		return String.valueOf(sb);
	}

4.左旋转转字符串

题目

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

答案

思路:
第一种:前n位反转,后几位反转,最后总的反转
先反转前n位,再反转后几位,变为了cbafedZYX,再整体反转变为XYZdefabc

第二种:就很简单,只要取出字符串的前n位子串和后面的子串,只要把前n位的子串接到后面子串的尾部,就OK。

	 /**
	  * 左旋转转字符串
	  * @param str
	  * @param n
	  * @return
	  */
	  //第一种:
	public static String LeftRotateString(String str,int n) {
		if(str == null || str.length() == 0)
			return str;
		char[] charStr = str.toCharArray();
		int len = charStr.length;
		n = n % len;
		reverse(charStr, 0, n - 1);
		reverse(charStr, n, len - 1);
		reverse(charStr, 0, len - 1);
		return new String(charStr);
	}
	public static void reverse(char[] charStr, int i, int j) {
		while(i < j) {
			char temp = charStr[i];
			charStr[i] = charStr[j];
			charStr[j] = temp;
			i++;
			j--;
		}
	}
	
	//第二种:
	public static String LeftRotateString(String str, int n) {
		if (str == null || str.length() == 0)
			return str;
		n = n % str.length();
		if (n == 0)
			return str;
		String str1 = str.substring(0, n);
		String str2 = str.substring(n, str.length());
		return new StringBuffer(str2).append(str1).toString();
	}

5.把字符串转换成整数

题目

题目描述:
将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。

输入描述:
输入一个字符串,包括数字字母符号,可以为空

输出描述:
如果是合法的数值表达则返回该数字,否则返回0

示例1
输入:
+2147483647
1a33

输出:
2147483647
0

答案

思路:这个简单,主要就是检测判断,只要能做出来,性能都差不多一样,就是写得好的算法看上去简洁,好理解。

	/**
	 * 字符串转整数
	 * @param str
	 * @return
	 */
public static int strToInt(String str) {
	int len = str.length(), sign = 1, res = 0;
	if(len == 0 || str == null)
		return 0;
	if(str.charAt(0) == '-')
		sign = -1;
	for(int i = (str.charAt(0) == '+' || str.charAt(0) == '-') ? 1 : 0; i < len; i++) {
		if(str.charAt(i) < '0' || str.charAt(i) > '9')
			return 0;
		res = res * 10 + str.charAt(i) - '0';
	}
    return sign*res;
}

6.*字符的全排列

题目

输入一个字符串,打印出该字符串中字符的所有排列。

例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

答案

思路:
固定第一个字符,递归取得首位后面的各种字符串组合;

再把第一个字符与后面每一个字符交换,并同样递归获得首位后面的字符串组合;

递归的出口,就是只剩一个字符的时候,递归的循环过程,就是从每个子串的第二个字符开始依次与第一个字符交换,然后继续处理子串。

在这里插入图片描述

假如有重复值呢?
第一种方法:
由于全排列就是从第一个数字起,每个数分别与它后面的数字交换,我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这两个数就不交换了。
例如abb,第一个数与后面两个数交换得babbba。然后abb中第二个数和第三个数相同,就不用交换了。
但是对bab,第二个数和第三个数不 同,则需要交换,得到bba
由于这里的bba和开始第一个数与第三个数交换的结果相同了,因此这个方法不行。

换种思维,对abb,第一个数a与第二个数b交换得到bab,然后考虑第一个数与第三个数交换,此时由于第三个数等于第二个数。所以第一个数就不再用与第三个数交换了。
再考虑bab,它的第二个数与第三个数交换可以解决bba。此时全排列生成完毕!

第二中方法:
比较笨,把所有的结果放在list中,在每次放的时候,都检查一下list中是否存在这个串,如果不存在,就放进去。

第一种方法相对于第二种, 虽然只是看上去只是很微小的改变,但是时间上面的改进不只是十几倍,而是几十倍上百倍。

	/**
	 * 字符全排列
	 * @param str
	 * @return
	 */
public static ArrayList<String> Permutation(String str) {
	List<String> res = new ArrayList<String>();
	
	if(str != null && str.length() > 0)
		PermutationHelp(str.toCharArray(), 0, res);
	Collections.sort(res);
	return (ArrayList<String>)res;
	}
public static void PermutationHelp(char[] ch, int index, List<String> res) {
	if(index == ch.length -1) {
		String str = String.valueOf(ch);
//		if(!res.contains(str))
			res.add(str);
	}else {
		for(int i = index; i < ch.length; i++) {
			/*
			 * 理解了这个点,也就对这个算法的核心思想理解了
			 * 去掉下面的if检测,去掉上面的if(!res.contains(str))注释,就是第二种方法
			 */
			if(isSwap(ch, index, i)) { 
			swap(ch, index, i);
			PermutationHelp(ch, index + 1, res);
			swap(ch, index, i);
			}
		}
	}
}
public static void swap(char[] ch, int i, int j) {
	char temp = ch[i];
	ch[i] = ch[j];
	ch[j] = temp;
}
public static boolean isSwap(char[] ch, int begin, int end) {
	boolean sign = true;
	for(int i = begin; i < end; i++) {
		if(ch[i] == ch[end])
			sign = false;
	}
	return sign;
}

也可以用hashset来取出重复的,简单高效!

	/**
	 * 字符全排列
	 * 
	 * @param str
	 * @return
	 */
	public static ArrayList<String> Permutation(String str) {
		List<String> res = new ArrayList<String>();
		HashSet<String> set = new HashSet<>();

		if (str != null && str.length() > 0)
			PermutationHelp(str.toCharArray(), 0, set);
		res.addAll(set);
		Collections.sort(res);
		return (ArrayList<String>) res;
	}

	public static void PermutationHelp(char[] ch, int index, HashSet<String> set) {
		if (index == ch.length - 1) {
			String str = String.valueOf(ch);
			set.add(str);
		} else {
			for (int i = index; i < ch.length; i++) {
				swap(ch, index, i);
				PermutationHelp(ch, index + 1, set);
				swap(ch, index, i);
			}
		}
	}

	public static void swap(char[] ch, int i, int j) {
		char temp = ch[i];
		ch[i] = ch[j];
		ch[j] = temp;
	}

7.表示数值的字符串

题目

在这里插入图片描述

答案

思路:
按照一定的规则,如果第一位是+或-,就后移一位。

如果是数字,索引后移,数字表示1.

如果是点,要判断至此点的数量和e的数量是否已经有了,因为java 中e要求后面为整数,如果有了肯定false。索引后移,dotnum增加。

如果是e,判断是否重复e,或者前面没有数字返回false。enum++, 索引++,此时还要判断最后一位是不是e或者+或者-,如果是false。

	/**
	 * 表示数值的字符串
	 * @param str
	 * @return
	 */
	public static boolean isNumeric(char[] str) {
		if (str == null)
			return false;
		int length = str.length;
		int dotNum = 0;// 记录点的数量
		int index = 0;// 索引
		int eNum = 0;// 记录e的数量
		int num = 0;// 记录数字的数量
		if (str[0] == '+' || str[0] == '-') {
			index++;
		}
		while (index < length) {
			if (str[index] >= '0' && str[index] <= '9') {
				index++;
				num = 1;
				// .前面可以没有数字,所以不需要判断num是否为0
			} else if (str[index] == '.') {
				// e后面不能有.,e的个数不能大于1.java科学计数要求aeb,b为整数
				if (dotNum > 0 || eNum > 0)
					return false;
				dotNum++;
				index++;
			} else if (str[index] == 'e' || str[index] == 'E') {
				// 重复e或者e前面没有数字
				if (eNum > 0 || num == 0)
					return false;
				eNum++;
				index++;
				// 符号不能在最后一位
				if (index < length && (str[index] == '+' || str[index] == '-'))
					index++;
				// 表示e或者符号在最后一位
				if (index == length)
					return false;
			} else {
				return false;
			}

		}
		return true;
	}

8.字符流中第一个不重复的字符

题目

在这里插入图片描述

答案

思路:很简单,两种方法,hashmap和 字符数组。

hashmap:

	/**
	 * 字符流中第一个不重复的字符
	 */
	HashMap<Character, Integer> map = new HashMap<>();// 记录字符出现次数
	ArrayList<Character> list = new ArrayList<>();// 记录当前的所有的字符
	// Insert one char from stringstream
	public void Insert(char ch) {
		if (map.containsKey(ch))
			map.put(ch, map.get(ch) + 1);
		else
			map.put(ch, 1);
		list.add(ch);
	}
	// return the first appearence once char in current stringstream
	public char FirstAppearingOnce() {
		for (char c : list) {
			if (map.get(c) == 1)
				return c;
		}
		return '#';
	}

字符数组:

   char [] chars = new char[256];//ascii字符共128,其他字符非中文认为256个,
                                //为每个字符预留空间。默认每个存的ascii值为0 
   StringBuffer sb = new StringBuffer();//记录当前的所有字符
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        sb.append(ch);
        chars[ch]++;//如果字符是1,那么就是在字符1对应的下标的地方
                    //也就是49的下标处,ascii加1.此时如果输出chars[ch],里面存ascii值
                    //为1,所以是一个不可显示的字符。
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
         char [] str = sb.toString().toCharArray();
         for(char c:str) {
        	 if(chars[c] == 1)//判断这个字符数组中在这个字符下标处值是否为1.
        		 return c;
         }
         return '#';
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yelvens

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值