LeetCode刷题:字符串

LeetCode刷题:字符串

  • 344. 反转字符串
  • 7. 整数反转
    • 常用判断溢出的方法:
    • ⚠️一种判断 t m p ∗ 10 tmp*10 tmp10溢出比较取巧的方法:tmp*10/10!=tmp
  • 387. 字符串中的第一个唯一字符
  • 242. 有效的字母异位词
  • 8. 字符串转换整数 (atoi)
    • 有限状态机FSM
    • 正则表达式 [待补充]
  • 14. 最长公共前缀
    • 方法一:纵向扫描
    • 方法二:横向扫描
    • 方法三:分治
    • 方法四:二分查找【待填充】
  • 38. 外观数列
    • ⚠️ 输出到文件
    • 28. 实现 strStr()
    • 方法二:双指针 - 线性时间复杂度【待写】
    • 方法三: Rabin Karp - 常数复杂度【待写】

以下采用【Java的解法】
参考了LeetCode的题解

344. 反转字符串

	public void reverseString(char[] s) {
        int i=0,j=s.length-1;
        char tmp;
        while(i<j){
            tmp=s[i];
            s[i]=s[j];
            s[j]=tmp;
            i++;
            j--;
        }
    }

7. 整数反转

  • 收获:
    • 字符串的处理:
    String tmp="test";
    System.out.println(tmp.charAt(0));
    System.out.println(tmp.substring(1));
    System.out.println(tmp.substring(1,3));
    
    • 霍纳算法
    • int类型的范围是 [ 2 31 , 2 31 − 1 ] [2^{31},2^{31}-1] [231,2311],即[-2147483648,2147483647]。
  • 我的思路
  • 先用字符串存储反转后的数据,然后转化为数字。参考了霍纳算法的思路:
    tmp=tmp*10+s[i];
位数 0 1 2 3 4 5
a0 a1 a2 a3 a4 a5
  • 具体计算: 记l=6
    a 0 × 1 0 ( l − 1 ) + a 1 × 1 0 ( l − 2 ) + ⋯ + a 5 × 1 0 ( l − 6 ) = 10 ( 10 ( 10 ( 10 ( 10 ( a 0 ) + a 1 ) + a 2 ) + a 3 ) + a 4 ) + a 5 a0\times10^{(l-1)}+a1\times10^{(l-2)}+\dots+a5\times10^{(l-6)}=10(10(10(10(10(a0)+a1)+a2)+a3)+a4)+a5 a0×10(l1)+a1×10(l2)++a5×10(l6)=10(10(10(10(10(a0)+a1)+a2)+a3)+a4)+a5
public long Str2Int(String s) {
		if(s.charAt(0)=='-') {
			return -Str2Int(s.substring(1));
		}
		else {
			//先判断是否溢出
			long tmp=0;
			
			// 霍纳算法
			for(int i=0;i<s.length();i++) {
				tmp=tmp*10+(s.charAt(i)-'0');
			}
			return tmp;
		}
	}
	public int reverse(int x) {
		if (x == 0)
			return 0;
        else if(x==-2147483648)
			return 0;
		else if (x < 0)
			return -reverse(-x);
		else {
			String tmp = "";
			// 先处理最后一位
			if(x%10!=0)
				tmp += (x % 10);
			x /= 10;
			while (x > 0) {
				tmp += (x % 10);
				x /= 10;
			}
			long res=Str2Int(tmp);
			if(res>2147483647)
				return 0;
			else
				return (int)res;
		}
	}

常用判断溢出的方法:

转换为 INT_MAX 的逆操作。比如判断某数乘 10 是否会溢出,那么就把该数和 INT_MAX 除 10 进行比较。

⚠️一种判断 t m p ∗ 10 tmp*10 tmp10溢出比较取巧的方法:tmp*10/10!=tmp

  • 原理:
    • 首先,tmp*10如果超过int范围,会被转换为long的类型,然后/10就保留最后的31位(有一个符号位)。例如 2147483647 0 10 = 1001111111111111111111111111111011 0 2 21474836470_{10}=10011111111111111111111111111110110_{2} 2147483647010=100111111111111111111111111111101102它最后31位为 111111111111111111111111111011 0 2 = 214748364 8 10 1111111111111111111111111110110_{2}=2147483648_{10} 11111111111111111111111111101102=214748364810所以java输出的值为-1
    • 是否有重复?即一个数在10进制最后添了一位0,然后保留最后的31位和原来的数字一样?
    • 实际上我们可以用代码验证:(这里从ciel(2147483647)开始验证,减少运算量)
    for(int i=214748365;i<2147483647;i++) {
    		if(i*10/10==i)
    			System.out.println(i);
    	}
    System.out.println("END");
    for(int i=-214748365;i>-2147483648;i--) {
    		if(i*10/10==i)
    			System.out.println(i);
    	}
    System.out.println("END");
    
    • 所以可以用tmp*10==tmp来判断是否溢出,如果为true则没有溢出。
  • 具体实现:(参考)
public int reverse(int x) {
	int ans = 0;
	while (x != 0) {
		if ((ans * 10) / 10 != ans) {
			ans = 0;
			break;
		}
		ans = ans * 10 + x % 10;
		x = x / 10;
	}
	return ans;
}
  • 此外也可能出现 a n s ∗ 10 ans * 10 ans10没溢出,但是 a n s ∗ 10 + x ans * 10 + x % 10 ans10+x溢出的情况。

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

  • 一种粗暴的解法:
 public int firstUniqChar(String s) {
        int[] tab=new int[26]; //对应26个字母,这里我们的输入为小写
		//如果一个字母出现了第一次,就在对应位置填上它的下标,若出现多次就改为-1
		int l=s.length();
		int index;
		for(int i=0;i<l;i++) {
			index = (int)(s.charAt(i)-'a');
			if(tab[index]==0) {
				tab[index]=i+1; //存储的是i+1,为了使得存储下表的最小值为1
			}
			else if(tab[index]>0){
				tab[index]=-1;
			}
		}
		
		//返回的是正数中最小的数
		int tmp=l+1;
		for(int i=0;i<26;i++) {
			if(tab[i]>0 && tab[i]<tmp){
				tmp=tab[i];
			}
		}
		if(tmp==l+1)
			return -1;
		else	
			return tmp-1;
    }

242. 有效的字母异位词

  • 基于26个字母的map:
public int[] count(String s) {
		int[] tmp=new int[26];
		int l=s.length();
		for(int i=0;i<l;i++) {
			int index=(int)(s.charAt(i)-'a');
			tmp[index]+=1;
		}
		return tmp;
}
public boolean isAnagram(String s, String t) {
		int[] s1,s2;
		s1=count(s);
		s2=count(t);
		for(int i=0;i<s1.length;i++) {
			if(s1[i]!=s2[i])
				return false;
		}
		return true;
}
  • 可以通过一些小技巧提升运行速度,如将两次循环合并+观察两者map的差值(即0,0,0,0…0)才停止
public boolean isAnagram(String s, String t) {
		int[] diff=new int[26];
		int ls=s.length();
		int lt=t.length();
		if(ls!=lt)
			return false;
		for(int i=0;i<ls;i++) {
			diff[(int)(s.charAt(i)-'a')]++;
			diff[(int)(t.charAt(i)-'a')]--;
		}
		for(int i=0;i<diff.length;i++) {
			if(diff[i]!=0)
				return false;
		}
		return true;
}

8. 字符串转换整数 (atoi)

  • 收获:
    • 有限状态机
    • 多元比较符号:1>2 1:2;如果1>2为true,则输出1,否则为2。
    • 字符串的遍历:
		char[] temp=str.toCharArray();
        for(char c:temp) {
        	a.update(c);
        	System.out.print(c);
        }
  • 题目中的比较难的地方
    • 比较恶心的输入:
      3.14, .1, +1, +-2,其中+-2输出是0。
    • 一个修修补补还不正确的解法
public String clean(String str) {
		int l,r;
		int i=0;
		while(i<str.length()){
			if(str.charAt(i)!=' ' &&  str.charAt(i)!='+')
				break;
			i++;
		}
		l=i;
		while(i<str.length()) {
			if(str.charAt(i)==' ' || str.charAt(i)=='.' )
				break;
			else if(str.charAt(i)>'9' || str.charAt(i)<'0' && str.charAt(i)!='-' && str.charAt(i)!='+') //如果出现非数字,直接返回0
				return "0";
			i++;
		}
		r=i;
		return str.substring(l,r);
	}
	public int myAtoi(String str) {
		String tmp = clean(str);
		if(tmp==null || tmp.length()==0)
			return 0;
		else{ 
			int sign=1;
			if(tmp.charAt(0)=='-') {
				sign=-1;
				tmp=tmp.substring(1);
			}
			//霍纳算法
			int res=0;
			for(int i=0;i<tmp.length();i++) {
				//判断是否溢出,如果溢出,直接返回
				if(res*10/10!=res) {
					if(sign<0)
						return -2147483648;
					else
						return 2147483647;
				}
				//如果没有溢出,就可以加上去
				res=10*res+(int)(tmp.charAt(i)-'0');
			}
			return sign*res;
		}
} 

有限状态机FSM

LeetCode刷题:字符串_第1张图片

public class Automata {
		private int state = 0; // 一开始的状态为0:start
		private int[][] table = { { 0, 1, 2, 3 }, { 3, 3, 2, 3 }, { 3, 3, 2, 3 }, { 3, 3, 3, 3 } };
		long num = 0; // 用来记录读取的数据
		int sign = 1;

		// 用来读取当前输入导致状态的变化
		public int getStat(char c) {
			if (c == ' ')
				return 0;
			else if (c == '+' || c == '-')
				return 1;
			else if ('0' <= c && '9' >= c)
				return 2;
			else
				return 3;
		}

		public void update(char c) {
		state = table[state][getStat(c)];
		// 只有当前状态为2(当前字符为数字)才对输入进行处理
		if (state == 2) {
			num = num * 10 + (long) (c - '0');
			num = sign==1 ? Math.min(num, Integer.MAX_VALUE):-Math.max(-num, Integer.MIN_VALUE); //如果是正号,就取Math.min()。
		}
		if (state == 1 && c == '-')
			sign = -1;
	}
	}
	
	public int myAtoi(String str) {
        Automata a=new Automata();
        char[] temp=str.toCharArray();
        for(char c:temp){
        	a.update(c);
            // System.out.println("status = "+a.state);
        }
        return a.sign*(int)a.num;
    }

正则表达式 [待补充]

  • https://www.runoob.com/regexp/regexp-tutorial.html

14. 最长公共前缀

方法一:纵向扫描

public String longestCommonPrefix(String[] strs) {
		if (strs == null || strs.length == 0)
			return "";
		if (strs.length == 1)
			return strs[0];
		int minL = strs[0].length();
		for (int i = 0; i < strs.length; i++) {
			if (strs[i].length() < minL)
				minL = strs[i].length();
		}
		for (int j = 0; j < minL; j++) {
			for (int i = 0; i < strs.length; i++) {
				if (strs[0].charAt(j)!=strs[i].charAt(j))
					return strs[0].substring(0,j);
			}
		}
		return strs[0].substring(0, minL);
	}

方法二:横向扫描

  • 令LCP为一个得到两个字符串中最大的公共前缀的函数,则我们的问题可以转化为 L C P ( L C P ( L C P ( S 1 , S 2 ) , S 3 ) , S 4 ) LCP(LCP(LCP(S1,S2),S3),S4) LCP(LCP(LCP(S1,S2),S3),S4)
  • 具体实现:
public String LCP(String s1, String s2) {
		if(s1==null || s1.length()==0 || s2==null || s2.length()==0) {
			return "";
		}
		int l = Math.min(s1.length(), s2.length());
		for (int i = 0; i < l; i++) {
			if(s1.charAt(i)!=s2.charAt(i))
				return s1.substring(0,i);
		}
		return s1.length()<s2.length() ? s1:s2;
	}

	public String longestCommonPrefix(String[] strs) {
		if (strs == null || strs.length == 0)
			return "";
		else {
			String tmp=strs[0];
			for(int i=1;i<strs.length;i++) {
				tmp=LCP(tmp,strs[i]);
			}
			return tmp;
		}
	}
  • 时间复杂度为:O(mn),m为字符串平均长度,n为字符串数量。最坏情况是所有的字符串都相同的情况。
  • 空间复杂度: O(1)

方法三:分治

  • 注意到前面的LCP函数满足结合律:
    L C P ( S 1 , . . . , S n ) = L C P ( L C P ( S 1 , . . . , S n / 2 ) , L C P ( S n / 2 + 1 , S n ) ) LCP(S1,...,S_n)=LCP(LCP(S1,...,S_{n/2}),LCP(S_{n/2+1},S_n)) LCP(S1,...,Sn)=LCP(LCP(S1,...,Sn/2),LCP(Sn/2+1,Sn))
  • 从而可以用递归的写法,出口是 S 1 = = S n / 2 S1==S_{n/2} S1==Sn/2
public static String LCP(String s1, String s2) {
		if (s1 == null || s1.length() == 0 || s2 == null || s2.length() == 0) {
			return "";
		}
		int l = Math.min(s1.length(), s2.length());
		for (int i = 0; i < l; i++) {
			if (s1.charAt(i) != s2.charAt(i))
				return s1.substring(0, i);
		}
		return s1.length() < s2.length() ? s1 : s2;
	}

	// 两边(start,end)都取得到
	public static String longestCommonPrefix(String[] strs, int start, int end) {
		if (start == end)
			return strs[start];
		else {
			int mid = (start + end) / 2;
			String leftLCP=longestCommonPrefix(strs,  start,  mid);
			String rightLCP=longestCommonPrefix(strs,  mid+1,  end);
			return LCP(leftLCP,rightLCP);
		}
	}

	// 对应题目的输入
	public static String longestCommonPrefix(String[] strs) {
		if (strs == null || strs.length == 0)
			return "";
		else {
			return longestCommonPrefix(strs,0,strs.length-1);
		}
	}

方法四:二分查找【待填充】

38. 外观数列

  • 收获:

⚠️ 输出到文件

PrintStream psOld = System.out; // 保存原来的输出路径
System.setOut(new PrintStream(new File("countAndSay.txt")));// 设置输出重新定向到文件
// 这里用print就可以了
System.setOut(psOld); // 恢复原来的输出路径

此外,还需要导入:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;

以及,在main方法处做一些修改:

public static void main(String[] args) throws FileNotFoundException
  • 解题方法:利用一个循环打表:
String test="11";
tmp[0]="1";
tmp[1]="11";
System.out.print("\""+1+"\""+","+"\""+11+"\""+",");
for(int i=1;i<30;i++) {
	test = generate(test);
	System.out.print("\""+test+"\""+",");
	tmp[i+1]=test;
}

28. 实现 strStr()

  • 方法一:子串逐一比较 - 线性时间复杂度
 public int strStr(String haystack, String needle) {
	if(needle==null || needle.equals(""))
		return 0;
	int i=0,j=i+needle.length();
	while(j<=haystack.length()){
            if(needle.equals(haystack.substring(i,j)))
                return i;
            j=(++i)+needle.length();
	}
	return -1;
}

方法二:双指针 - 线性时间复杂度【待写】

方法三: Rabin Karp - 常数复杂度【待写】

你可能感兴趣的:(LeetCode,字符串,java,leetcode,算法)