5.替换空格:
请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
解题思路:直接使用Java自带的字符串替换方法,或者从前向后记录空格的数量,改变bufferstring的长度,从后向前替换空格(Java通过length()方法和charAt()方法结合遍历字符串中的每一个字符)
最简单方法:
public class Solution {
public String replaceSpace(StringBuffer str) {
return str.toString().replaceAll("\\s", "%20");
}
}
或者:
public class Solution {
public String replaceSpace(StringBuffer str) {
int oldLength = 0;
int numOfSpace = 0;
for(int i=0; i<str.length();i++){
oldLength++;
if(str.charAt(i)==' ')
numOfSpace++;
}
int newLength = oldLength + numOfSpace*2;//替换后实际长度
str.setLength(newLength);
int indexOfOld = oldLength-1;
int indexOfNew = newLength-1;
while(indexOfOld>=0 && indexOfNew>indexOfOld){
if(str.charAt(indexOfOld)==' '){
str.setCharAt(indexOfNew--,'0');
str.setCharAt(indexOfNew--,'2');
str.setCharAt(indexOfNew--,'%');
}else{
str.setCharAt(indexOfNew--,str.charAt(indexOfOld));
}
indexOfOld--;
}
return str.toString();
}
}
Java StringBuffer 类 ,详见:http://www.runoob.com/java/java-stringbuffer.html
setLength(int newLength)设置字符序列的长度。
charAt(int index)返回位于字符串的指定索引处的字符。该字符串的索引从零开始。
setCharAt(int index,Char ch)用来实现替换,第一个参数是取代的位置(索引从0开始)第二个参数是要替换为的字符串。
举一反三:在合并两个数组(包括字符串)时,如果从前往后需要重复移动数字或字符多次,可以考虑从后往前复制,较少移动次数,提高效率。
19.正则表达式匹配:
请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配。
解题思路:注意分多种情况考虑,并结合字符的特点,充分考虑每一种情况,尤其是当字符串与模式均未结束且下一个字符是‘*’,需要考虑3种情况。
public class Solution {
public boolean matchStr(char[] str, int starts, char[] pattern, int startp ){
//均到末尾
if(starts == str.length && startp == pattern.length)
return true;
//字符串没有结束,表达式结束
if(starts != str.length && startp == pattern.length)
return false;
//字符串到末尾,表达式没有结束
if(starts == str.length && startp != pattern.length){
if( startp +1 < pattern.length && pattern[startp+1]=='*')
return matchStr(str,starts,pattern,startp+2);
else
return false;
}
//均未结束,第二个是*,且字符串第1个跟模式第1个匹配,分3种匹配模式;如不匹配,模式后移2位
if(startp+1<pattern.length && pattern[startp+1]=='*'){
if(starts < str.length && (str[starts] == pattern[startp] || pattern[startp]=='.'))
//匹配0个字符,1个字符,再匹配str中的下一个
return matchStr(str,starts,pattern,startp+2)
|| matchStr(str,starts+1,pattern,startp+2)
|| matchStr(str,starts+1,pattern,startp);
else
return matchStr(str,starts,pattern,startp+2);
}
if(str[starts] == pattern[startp] || (pattern[startp]=='.' && starts!=str.length))
return matchStr(str,starts+1,pattern,startp+1);
return false;
}
public boolean match(char[] str, char[] pattern){
if(str == null || pattern ==null)
return false;
int starts = 0;
int startp = 0;
return matchStr(str,starts,pattern,startp);
}
}
20.表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是 "12e", "1a3.14" , "1.2.3", "+-5"和"12e+4.3"都不是。
解题思路:利用正则表达式来进行匹配。
* :零次或多次匹配前面的字符或子表达式;\d :匹配数字,包括0~9;
+ :一次或多次匹配前面的字符或子表达式。
? :零次或一次匹配前面的字符或子表达式。
public class Solution {
public boolean isNumeric(char[] str) {
String s = String.valueOf(str);
return s.matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?");
}
}
或者,考虑表示数值的几种情况,有几个关键点:基本边界;E或者e,不能重复出现;符号位只能出现在字符串首或者指数标志后;小数点不能重复出现;不能是其他字母。
public class Solution {
public boolean isNumeric(char[] str) {
if(str == null)
return false;
boolean hasE = false;
boolean flag = false;
boolean point = false;
for(int i = 0;i<str.length;i++){
if(str[i]=='e' || str[i]=='E'){
if(hasE)//已经出现过E
return false;
if(i+1==str.length)
return false;//出现在了末尾
hasE = true;
}else if(str[i]=='+' || str[i]=='-'){
if(flag && str[i-1]!='e' && str[i-1]!='E')
return false;//作为符号位出现过且不是指数
if(!flag && i>0 && str[i-1]!='e' && str[i-1]!='E')
return false;//不是指数,出现位置不是开头
flag = true;
}else if(str[i]=='.'){
if(point || hasE)//小数点出现过,或者是E出现过
return false;
point = true;
}else if(str[i]>'9' || str[i]<'0')
return false;
}
return true;
}
}
58.翻转字符串:
1)翻转单词顺序:
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变,为简单起见,标点符号和普通字母一样处理,例如,输入字符串“I am a student.”,则输出“student. a am I ”。
解题思路:首先将整个句子进行翻转,然后在根据空格的位置,将句子中的每个单词进行翻转。对于字符串中的字符处理,可以采用StringBuffer类。
public class Solution {
public String Reverse(String str,int left, int right) {
if(left<0 || right<0)
return null;
StringBuilder string = new StringBuilder(str);
while(left<right){
char temp = string.charAt(right);
char temp2 = string.charAt(left);
string.setCharAt(left,temp);
string.setCharAt(right,temp2);
left++;
right--;
}
str = string.toString();
return str;
}
public String ReverseSentence(String str) {
String result = new String();
if(str.length()==0)//空
return result;
if(str.length()==1 || !str.contains(" "))
return str;//不含空格,或者只含有一个空格
int begin =0;
int end = str.length()-1;
str = Reverse(str,begin,end);//全部翻转
int i = 0;
int start =0;
int index = str.lastIndexOf(" ");//最后字符翻转
str = Reverse(str,index+1,end);
while(i<index){
i++;
if(str.charAt(i) ==' '){
str = Reverse(str,start,i-1);
start = i+1;
}
}
return str;
}
}
另外,字符串中有trim()方法,可以去掉字符串首尾空格。根据空格将句子分成一个个单词,并将单词数组从后往前遍历,每次添加一个后增加一个空格,最后将字符串首尾的空格去掉。
public class Solution {
public String ReverseSentence(String str) {
if("".equals(str.trim())) return str;
String[] word=str.split(" ");
StringBuffer sb=new StringBuffer();
for(int i=word.length-1;i>=0;i--){
sb.append(word[i]+" ");
}
return sb.toString().trim();
}
}
2)左旋转字符串:
汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
解题思路:调用三次翻转函数,即可实现字符串的左旋,注意特殊情况。
public class Solution {
public String Reverse(String str,int left, int right) {
if(left<0 || right<0)
return null;
StringBuilder string = new StringBuilder(str);
while(left<right){
char temp = string.charAt(right);
char temp2 = string.charAt(left);
string.setCharAt(left,temp);
string.setCharAt(right,temp2);
left++;
right--;
}
str = string.toString();
return str;
}
public String LeftRotateString(String str,int n) {
String result = new String();
if(str.length()==0 || n > str.length())
return result;
if(n==0)
return str;
str= Reverse(str,0,n-1);
str = Reverse(str,n,str.length()-1);
str = Reverse(str,0,str.length()-1);
return str;
}
}
或者直接使用string类的substring寻找到对应的子串,进行输出:
public class Solution {
public String LeftRotateString(String str,int n) {
StringBuffer sb = new StringBuffer();
if(str.length()==0 || n>str.length())
return sb.toString();
sb.append(str.substring(n,str.length()));
sb.append(str.substring(0,n));
return sb.toString();
}
}
38.字符串的排列:
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
解题思路:输出字符串的全排列。对于输入长度为n的字符串数组,全排列组合为n!种。可以利用递归,第一个字符一共有n种选择,剩下的变成一个n-1规模的递归问题。而第一个字符的n种选择,都是字符串里面的。因此可以使用第一个字符与1-n的位置上进行交换,得到n中情况,然后递归处理n-1的规模,只是处理完之后需要在换回来,变成原来字符的样子。
由于本题采用字典的方式打印序列,需要在所有结果添加完成后进行一次排序。Collections是一个工具类,sort是其中的静态方法,是用来对List类型进行排序的。
import java.util.*;
public class Solution {
public void answer(char [] s, int start,ArrayList<String> result) {
if(start==s.length-1){
result.add(new String(s));
}
for(int i = start ;i< s.length; i++){
if(i== start || s[i]!=s[start]){
char temp = s[start];
s[start] = s[i];
s[i] = temp;
answer(s,start+1,result);
temp = s[start];
s[start] = s[i];
s[i] = temp;
}
}
}
public ArrayList<String> Permutation(String str) {
ArrayList<String> result = new ArrayList<String>();
if(str.length()==0)
return result;
if(str.length()==1){
result.add(str);
return result;
}
char [] s = str.toCharArray();
answer(s,0,result);
Collections.sort(result);
return result;
}
}
扩展:如果在输入N个字符,则这N个字符能够构成长度1,2,3,……N的组合,求N个字符组成长度为M的组合的问题,可以分解为两个子问题,分别求N-1个字符中长度为M-1的组合,以及求N-1个字符中长度为M的组合。
import java.util.*;
public class Solution {
ArrayList<Character> list = new ArrayList<Character>();
ArrayList<String> result = new ArrayList<String>();
public static void main(String [] args) {
Scanner sc = new Scanner(System.in);
String string = sc.nextLine();
char [] ch = string.toCharArray();
int n = ch.length;//n个字符
int m = sc.nextInt();//使用m个字符
find(ch,n-1,m);
Collections.sort(result);
for(int i =0;i<result.size();i++){
System.out.println(result.get(i).toString());
}
}
public static void find(char [] ch , int i , int m) {
if(m==0){
result.add(list.toString());
}else if(i<0 || m<0){
return ;
}else{
list.add(ch[i]);
find(ch,i-1,m-1);
list.remove(list.size()-1);
find(ch,i-1,m);
}
}
}
全组合:abc三个字符的所有组合。求所有组合也就是abc各个位是否选取的问题,第一位2种可能,第二位2种。。。所以一共有2^n种。{ ,a, b, c, ab, ac, bc,abc} 。用0表示不取,1表示选取,这样可以用110这样的形式表示ab。abc一共的表示形式从0到2^3-1。然后按位与运算,如果结果为1就输出当前位,结果0不输出。
import java.util.*;
public class Solution {
public static void main(String[] args) {
char[] chs = {'a','b','c'};
comb(chs);
}
public static void comb(char[] chs) {
int len = chs.length;
int nbits = 1 << len;
for (int i = 0; i < nbits; ++i) {
int t;
for (int j = 0; j < len; j++) {
t = 1 << j;
if ((t & i) != 0) { // 与运算,同为1时才会是1
System.out.print(chs[j]);
}
}
System.out.println();
}
}
}
可重复排列:abc三个字符组成的所有长度为3的字符串,知道字符串的长度,利用多重循环可以得出结果。abc三个字符组成的所有长度为3的字符串,每一位有三种选择,三位,一共27种。(其他排列组合问题参见我的博客:https://blog.csdn.net/danjuanzi2684/article/details/83340320)
相关题目:1.输入一个含有8个数字的数组,判断有没有可能把这8个数字分别放到正方体的8个顶点上,使得正方体上三组相对的面上的4个顶点的和相等。------->相当于求出这8个数字的全排列,然后依次判断有没有一组排列可以实现题目中所给条件。
2.8*8的国际象棋上摆放8个皇后,使其不能相互攻击,即任意两个皇后不能处于同一行、同一列、或者同一对角线,有多少种摆法?---->不能处于同一行,则必然每一行有一个皇后,可以定义一个长度为8的数组s,代表每一个皇后的列号,并初始化为0-7,对数组进行全排列,结果中满足i-j = s[i]-s[j]或者j-i = s[i]-s[j]即可。
import java.util.*;
public class Queens {
int result = 0;
public int nQueens(int n) {
int [] list = new int[n];
for(int i =0; i<n; i++)
list[i] = i;//保证每一行、每一列只有一个皇后
permutation(list,0);
return result;
}
public void permutation(int [] list ,int start){
if(start == list.length){//全排列
if(isQueen(list))
result++;
}else{
for(int i = start ; i<list.length;i++){
swap(list,i,start);
permutation(list,start+1);
swap(list,i,start);
}
}
}
public void swap(int [] list ,int i ,int j){
int temp = list[i];
list[i] = list[j];
list[j] = temp;
}
public boolean isQueen(int [] list){//保证不在对角线上
for(int i = 0 ; i<list.length-1;i++){
for(int j = i+1 ; j<list.length;j++){
if(i-j==list[i]-list[j] || i-j ==list[j]-list[i])
return false;
}
}
return true;
}
}
但会超时!!!,建议用动态规划处理。
import java.util.*;
public class Queens {
public static int sum ;
public int nQueens(int n) {
// 利用递归法
// 运用数组cols[n]保存第n行的皇后位置
sum = 0 ;
int cols[]= new int[n];
find(cols,n,0);
return sum;
}
public void find(int [] cols,int n,int row){
if(row==n){
sum++;
return;
}
for(int i = 0;i<n;i++){
if(isValid(cols,row,i)){
cols[row]=i;
find(cols,n,row+1);
}
}
}
public boolean isValid(int [] cols,int row,int col){
for(int i= 0;i<row;i++){
if(cols[i] == col)
return false;
if(row-i == Math.abs(col-cols[i]))
return false;
}
return true;
}
}
举一反三:如果题目要求按照一定顺序摆放若干个数字,则可以求出这些数字的所有排列,然后一一判断每个排列是不是满足题目要求。
45.把数组排成最小的数:
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
解题思路:最直接的方式是先求出这个数组中所有数字的全排列,然后把每个排列拼起来保存到list集合中,注意整型拼接后可能会溢出,保存时要保存成long型,最后将list进行排序,第一个值即为最小值。N个数字共有N!种排列,这种算法较复杂。
import java.util.*;
public class Solution {
ArrayList<Long> list = new ArrayList<Long>();
//全排列
public void answer(int [] numbers,int start,ArrayList<Long> list) {
StringBuilder sb = new StringBuilder ();
if(start == numbers.length-1){
for(int i = 0 ; i<numbers.length ; i++)
sb.append(numbers[i]);
list.add(Long.parseLong(sb.toString()));
}
for(int i = start ; i<numbers.length ; i++){
if(i ==start || numbers[i]!=numbers[start]){
int temp = numbers[start];
numbers[start] = numbers[i];
numbers[i] = temp;
answer(numbers,start+1,list);
temp = numbers[start];
numbers[start] = numbers[i];
numbers[i] = temp;
}
}
}
public String PrintMinNumber(int [] numbers) {
String result = new String();
if(numbers.length ==0 || numbers==null)
return result;
answer(numbers,0,list);
Collections.sort(list);
result = String.valueOf(list.get(0));
return result;
}
}
可以通过:比较数字拼接后的大小,可以先将整型拼接成字符串,然后再转化为数字,进行比较,每轮比较可以找到最小数,添加到字符串中,遍历完成后得到结果。
import java.util.ArrayList;
public class Solution {
public String PrintMinNumber(int [] numbers) {
StringBuilder sb = new StringBuilder();
if(numbers==null || numbers.length==0)
return sb.toString();
for(int i=0;i< numbers.length-1;i++){
for(int j=i+1;j< numbers.length;j++){
long s1 = Integer.valueOf(numbers[i]+""+numbers[j]);
long s2 = Integer.valueOf(numbers[j]+""+numbers[i]);
if(s1>s2){
int temp = numbers[i];
numbers[i]=numbers[j];
numbers[j] = temp;
}
}
sb.append(numbers[i]);
}
sb.append(numbers[numbers.length-1]);
return sb.toString();
}
}
67.把字符串转换成整数:
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0
解题思路:对于字符串转数值,需要考虑空字符串“”、null、正负号、溢出、不合法字符串等问题。转整数时,扫描字符串中的每个字符,然后每次乘以10,再加上该位数,注意要转化成整型,需要减去'0'
public class Solution {
public int StrToInt(String str) {
if(str.length()==0 || str ==null)
return 0;
int result = 0;
int flag =1;
for(int i =0;i<str.length();i++){
if(str.charAt(i)=='+' || str.charAt(i)=='-'){
if(i==0){
if(str.charAt(i)=='+' )
flag = 1;
if(str.charAt(i)=='-' )
flag = -1;
}
else
return 0;
}else if(str.charAt(i)>'0' && str.charAt(i)<'9'){
result = result*10+str.charAt(i)-'0';
if(flag == 1 && result>Integer.MAX_VALUE)
throw new RuntimeException("上溢出");
if(flag == -1 && result<Integer.MIN_VALUE)
throw new RuntimeException("下溢出");
}
else
return 0;
}
return result*flag;
}
}
46. 把数字翻译成字符串:
给定一个数字,按照如下规则翻译成字符串:0翻译成“a”,1翻译成“b”...25翻译成“z”。一个数字有多种翻译可能,例如12258一共有5种,分别是bccfi,bwfi,bczi,mcfi,mzi。实现一个函数,用来计算一个数字有多少种不同的翻译方法。
解题思路:自下而上,动态规划,从最小的问题开始 : f(r)表示以r为开始(r最小取0)到最右端所组成的数字能够翻译成字符串的种数。对于长度为n的数字,f(n)=0,f(n-1)=1,求f(0)。 递推公式为 f(r-2) = f(r-1)+g(r-2,r-1)*f(r); 其中,如果r-2,r-1拼接起来的数字能够翻译成字符,则g(r-2,r-1)=1,否则为0。很类似与斐波那契数列的递推思想。基于循环代码效率更高些。
public class Solution {
public static int getTranslationCount(int number){
if(number<0)
return 0;
if(number==1)
return 1;
return getTranslationCount(Integer.toString(number));
}
//动态规划,从右到左计算。
//f(r-2) = f(r-1)+g(r-2,r-1)*f(r);
//如果r-2,r-1能够翻译成字符,则g(r-2,r-1)=1,否则为0
public static int getTranslationCount(String number) {
int f1 = 0,f2 = 1,g = 0;
int temp;
for(int i=number.length()-2;i>=0;i--){
if(Integer.parseInt(number.charAt(i)+""+number.charAt(i+1))<26)
g = 1;
else
g = 0;
temp = f2;
f2 = f2+g*f1;
f1 = temp;
}
return f2;
}
public static void main(String[] args){
System.out.println(getTranslationCount(-10)); //0
System.out.println(getTranslationCount(1234)); //3
System.out.println(getTranslationCount(12258)); //5
}
}
47.礼物的最大值:
现在有一个m*n的棋盘,每个小的棋盘上面放置一个礼物(礼物的价值大于0),一个人的初始位置在棋盘的左上角,每次他只能向下或向右移动一步,并拿走对应棋盘上的礼物,结束位置在棋盘的右下角,请设计一个算法使其能够获得最大价值的礼物。
解题思路:动态规划问题,新建一个二维数组来保存每个点的礼物最大值,f(i,j) = max(f(i-1,j), f(i,j-1)) + gift[i,j];使用循环来计算避免重复子问题。
public class Solution {
public static int getMaxValue(int[][] data){
int rows = data.length;
int cols = data[0].length;
if (rows==0 || cols==0)
return 0;
int[][] maxValues = new int[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
int left = 0; int up = 0;
if (i > 0)
up = maxValues[i - 1][j];
if (j > 0)
left = maxValues[i][j - 1];
maxValues[i][j] = Math.max(left, up) + data[i][j];
}
}
return maxValues[rows - 1][cols - 1];
}
public static void main(String[] args) {
int[][] data = { {1, 10, 3, 8}, {12, 2, 9, 6}, {5, 7, 4, 11},{3, 7, 16, 5}};
System.out.println(getMaxValue(data));//53
}
}
优化空间复杂度,由于某一点的坐标最大值只依赖于坐标(i-1,j)和(i,j-1),可设置一个一维数组来替代二维数组,数组长度为棋盘列数,数组中前j个数字为当前第 i 行前面 j 个格子礼物的最大价值,之后的数字分别保存前面i-1行的n-j个格子礼物的最大值。
public class Solution {
public static int getMaxValue(int[][] data){
int rows = data.length;
int cols = data[0].length;
if (rows==0 || cols==0)
return 0;
int[] maxValues = new int[cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
int left = 0; int up = 0;
if (i > 0)
up = maxValues[j];
if (j > 0)
left = maxValues[j - 1];
maxValues[j] = Math.max(left, up) + data[i][j];
}
}
return maxValues[cols - 1];
}
public static void main(String[] args) {
int[][] data = { {1, 10, 3, 8}, {12, 2, 9, 6}, {5, 7, 4, 11},{3, 7, 16, 5}};
System.out.println(getMaxValue(data));//53
}
}
48.最长不含重复字符的子字符串:
已知一个字符串,求这个字符串中不包含重复字符的最长子串的长度,如abba返回2,aaaaabc返回3,bbbbbbb返回1.
解题思路: 以一个hashmap作为辅助,map的key存储的是字符,value存储的是该字符当前的位置,遍历字符串,如果map中不包含这个字符,字符串当前的位置-头指针的位置与最大长度作比较并更新最大长度;如果map中包含这个字符,把头指针指向,头指针当前的位置与map中存储该字符位置的下一个位置 当中的较大者,成为新的头指针位置。
import java.util.*;
public class DistinctSubstring {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> map = new HashMap<Character, Integer>();
int maxLength = 0;
int now = 0;
for(int i=0; i<s.length(); i++){
Integer lastPosOfChar = charPosition.get(str2charArr[i]);
if(map.containsKey(s.charAt(i))){
now = Math.max(map.get(s.charAt(i))+1,now);
if((i-now+1)>maxLength)
maxLength = i-now+1;
}else{
if((i-now+1)>maxLength)
maxLength = i-now+1;
}
map.put(s.charAt(i),i);
}
return maxLength;
}
}
引申:去除字符串中所有重复的字符(只保留第一个重复的字符),只使用一个int来实现:
public String removeRepeatChar(String str) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < str.length(); i ++) {
char c = str.charAt(i);
if (str.indexOf(c) == str.lastIndexOf(c)){
sb.append(c);
}else{
int firstPosition = s.indexOf(c);
if(firstPosition ==i)
sb.append(c);
}
}
return sb.toString();
}
50.第一个只出现一次的字符:
1)字符串中第一个只出现一个的字符:
在一个字符串(1<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置.
解题思路:由于字符是长度为8的数据类型,共有256个字符,可以设置一个字符数组,用来记录每个字符出现的次数,然后重扫描一遍数组,当字符出现次数为1时,返回字符所在位置。对应的时间复杂度为O(n)。
public class Solution {
public int FirstNotRepeatingChar(String str) {
if(str == null || str.length()==0)
return -1;
char [] count = new char[256];
for(int i =0 ;i<str.length();i++)
count[str.charAt(i)]++;
for(int j =0 ;j<str.length();j++){
if(count[str.charAt(j)]==1)
return j;
}
return -1;
}
}
当字符较多时,创建字符数组来保存会占用很大空间,采用哈希表的方式求解,会使问题变简单。
HashMap的主要对外接口:
clear() 的作用是清空HashMap;
containsKey() 的作用是判断HashMap是否包含key;
containsValue() 的作用是判断HashMap是否包含“值为value”的元素;
get() 的作用是获取key对应的value;
remove() 的作用是删除“键为key”元素;
put() 的作用是将“key-value”添加到HashMap中,已存在,则更新。
import java.util.*;
public class Solution {
public int FirstNotRepeatingChar(String str) {
if(str == null || str.length()==0)
return -1;
//采用哈希表的方式:
HashMap<Character,Integer> map=new HashMap<Character,Integer>();
for(int i = 0;i<str.length();i++){
if(map.containsKey(str.charAt(i))){
int count = map.get(str.charAt(i));
count++;
map.put(str.charAt(i),count);
}else
map.put(str.charAt(i),1);
}
for(int i = 0;i<str.length();i++){
if(map.get(str.charAt(i))==1)
return i;
}
return -1;
}
}
相关题目:1.定义一个函数,输入两个字符串,从第一个字符串中删除在第二个字符串中出现过的所有字符。(建立一个字符型哈希表来存储第二个字符串,然后遍历第一个字符串,如果哈希表中不存在该字符,则添加到新建的字符串中,最后输出结果即可。)
import java.util.*;
public class Solution {
public String removeAppeared(String iniStr,String appearStr) {
if(iniStr == null || iniStr.length()<=0)
return null;
if(appearStr == null || appearStr.length()<=0)
return iniStr;
HashMap<Character,String> map=new HashMap<Character,String>();
for(int i =0; i<appearStr.length(); i++){
char c = appearStr.charAt(i);
map.put(c ,null);
}
StringBuffer sb = new StringBuffer();
for(int i =0; i<iniStr.length(); i++){
char c = iniStr.charAt(i);
if(map.containsKey(c))
continue;
sb.append(c);
}
return sb.toString();
}
}
2.定义一个函数,删除字符串中所有重复出现的字符。例如,"google"->"gole". (创建一个布尔类型的哈希表来存储字符串中出现字符的信息,创建一个字符串来保存结果,如果该字符出现过,接着扫描下一个字符,如果该字符没有出现过,添加到哈希表中,并添加到字符串中)
import java.util.*;
public class Solution {
//采用哈希表的方式:
public String removeRepeat(String iniStr) {
if(iniStr == null || iniStr.length()<=0)
return null;
HashMap<Character,Boolean> map=new HashMap<Character,Boolean>();
StringBuffer sb = new StringBuffer();
for(int i =0; i<iniStr.length(); i++){
char c = iniStr.charAt(i);
if(map.containsKey(c)) {
continue;
}else{
map.put(c,true);
sb.append(c);
}
}
return sb.toString();
}
}
3.在英语中,如果两个单词中出现的字母相同,每个字母出现的次数也相同,那么两个单词互为变位词,例如,live和evil。完成一个函数,判断输入的两个字符串是不是互为变位词。(创建一个整型哈希表,遍历第一个字符串,存储字符及其次数,然后遍历第二个字符,如果哈希表中没有该字符,则返回false,如果含有,将次数减1 ,接着扫描下一个字符,直到结尾,最后如果哈希表中的字符的次数都变成0,则返回true)
import java.util.*;
public class Solution {
//采用哈希表的方式:
public boolean isAnagram(String str1, String str2) {
if(str1 == null || str1.length()<=0 || str2 == null || str2.length()<=0 || str1.length()!= str2.length())
return false;
boolean isAnagram = false;
HashMap<Character,Integer> map=new HashMap<Character,Integer>();
for(int i =0; i<str1.length(); i++){
char c = str1.charAt(i);
if(map.containsKey(c)) {
int count = map.get(c)+1;
map.put(c,count);
}else{
map.put(c,1);
}
}
for(int i =0; i<str2.length(); i++){
char c = str2.charAt(i);
if(map.containsKey(c)) {
int count = map.get(c)-1;
map.put(c,count);
}else{
return isAnagram;
}
}
for(Integer value:map.values()){
if(value !=0)
return isAnagram;
}
isAnagram = true;
return isAnagram;
}
}
举一反三:如果需要判断多个字符是不是在某个字符串中出现过或者统计多个字符在字符串中出现的次数,可以创建哈希表,用很小的空间消耗来换取时间效率的提升。
2)字符流中第一个只出现一次的字符:
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。如果当前字符流没有存在出现一次的字符,返回#字符。
解题思路:使用一个HashMap来统计字符出现的次数,同时用一个ArrayList来记录输入流,每次返回第一个出现一次的字符。判断时,将ArrayList中的字符作为key去map中查找,第一个值为1的值即为结果。
import java.util.*;
public class Solution {
//采用哈希表的方式:
HashMap<Character,Integer> map=new HashMap<Character,Integer>();
ArrayList<Character> list = new ArrayList<Character>();
//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(){
char result = '#';
for(char temp :list){
if(map.get(temp)==1){
result = temp;
break;
}
}
return result;
}
}
41.数据流中的中位数 :
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
解题思路:用一个集合来存储数据流中的数字,然后利用系统自带的排序函数进行排序,如果是奇数个,取中间数,如果为偶数个数,取中间两个值的平均值。
import java.util.*;
public class Solution {
ArrayList<Integer> list = new ArrayList<Integer>();
public void Insert(Integer num) {
if(num !=null)
list.add(num);
}
public Double GetMedian() {
int len = list.size();
Collections.sort(list);
if(len==0)
return 0.0;
if(len==1)
return Double.valueOf(list.get(0));
if(len%2==0)
return Double.valueOf(list.get(len/2)+list.get(len/2-1))/2;
else
return Double.valueOf(list.get((len-1)/2));
}
}
如果不使用系统自带的排序函数,可以选择在数据流中数字插入集合中时,进行排序。此时宜选用linkedlist。
import java.util.*;
public class Solution {
LinkedList<Integer> list = new LinkedList<Integer>();
public void Insert(Integer num) {
//在插入时进行排序
if(list.size()==0 )
list.add(num);
if(num > list.getLast()){
list.addLast(num);
}else if(num < list.getFirst()){
list.addFirst(num);
}else{
for(int i =0;i<list.size();i++){
if(num<list.get(i)){
list.add(i,num);
break;
}
}
}
}
public Double GetMedian() {
int len = list.size();
if(len==0)
return 0.0;
if(len==1)
return Double.valueOf(list.get(0));
if(len%2==0)
return Double.valueOf(list.get(len/2)+list.get(len/2-1))/2;
else
return Double.valueOf(list.get((len-1)/2));
}
}