字符串问题 上
一、字符串之形
一些小注意点
Java中String类型是不可变的,可变字符串StringBuffer是可以的。
char型要用单引号 `` ; String 要用双引号 “ ”。
获取String类型 s 中的下标 i 位字符: s.charAt(i) 。
将数组转为字符串:new String(arr) 。
翻转字符串函数库:先转为StringBuffer型(设名为sb),再sb.reverse().toString() 进行翻转后再转为String型。
StringBuilder型(设名为sb)转String型:sb.toString()
判断两个字符数组char[] a、char[] b是否相同:Arrays.equals(a, b) 。
将 String s 转为字符数组arr:char[] arr = s.toCharArray() 。
String s 进行字符替换: s.replaceAll("\\s", “%20”) ,前一个为要替换的字符,后一个为替换成的字符,此例即将 空格替换为%20。
对字符串某个位置进行替换: stringName.replaceAll(regex, replacement) 把字符串中的regex都替换为replacement。
Map进行元素遍历:
for (Map.Entry<String, Integer> entry : mapName.entrySet()) { String key = entry.getKey(); String key = entry.getValue(); }
· 1. 判断字符串有无重复字符
借助辅助数组(ASCII码有 128 个字符,因此数组大小开辟长度为128即可),将字符转下标,辅助数组值记录字符出现次数,为2即可停止。
public static boolean checkDifferent(String s) {
if(s.isEmpty()) return true;
int[] help = new int[128];
for(int i = 0; i < s.length(); i++) {
int c = (int)s.charAt(i);
if(help[c] > 0) return false;
help[c]++;
}
return true;
}
· 2. 翻转字符串
- 方法一:
public static String reverseString(String s) {
int len = s.length();
char[] help = new char[len];
for(int i = 0; i < len; i++) {
help[len - i - 1] = s.charAt(i);
}
return new String(help);
// 若使用return help.toString()会直接返回string的地址
}
- 方法二:借助可变字符串StringBuffer,再调用reverse函数库。
public static String reverseString2(String s) {
StringBuffer sb = new StringBuffer(s);
return sb.reverse().toString();
}
· 3. 变形词问题
题:给定两个字符串,请编写程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串(即变形词)。规定大小写为不同字符,且考虑字符串中的空格。
变形词:两个串有相同的字符及数量组成 abc、cba、aabcd、bcada
- 方法一:
public static boolean checkSam(String a, String b) {
int len1 = a.length();
int len2 = b.length();
if(len1 != len2) return false;
// 转成字符数组
char[] arr1 = a.toCharArray();
char[] arr2 = b.toCharArray();
// 排序
Arrays.sort(arr1);
Arrays.sort(arr2);
return Arrays.equals(arr1, arr2);
}
- 方法二:用一个辅助数组,借
题1
的做法对字符串a中字符计数,再遍历字符串b,对辅助数组相应位置上的字符进行减数。若a和b中字符完全相同,最终辅助数组是全为0的。
public static boolean checkSam2(String a, String b) {
int len1 = a.length();
int len2 = b.length();
if(len1 != len2) return false;
int[] help = new int[128];
for(int i = 0; i < len1; i++) {
int c1 = (int)(a.charAt(i));
help[c1]++;
int c2 = (int)(b.charAt(i));
help[c2]--;
}
for(int i = 0; i < 128; i++) {
if(help[i] != 0) return false;
}
return true;
}
· 4. 替换字符串中的空格
- 方法一:使用Java自带的库
public static String replaceSpace(String s, int length) {
return s.replaceAll("\\s", "%20");
}
- 方法二:对空格进行计数,一个空格替换后比原来多了三个位置
public static String replaceSpace(char[] s, int length) {
int count = length;
for(int i = 0; i < length; i++) {
if(s[i] == ' ') count += 2;
}
int p1 = length - 1;
int p2 = count -1;
// 由后向前排
for(; p1 >= 0; p1--) {
if(s[p1] == ' ') {
s[p2--] = '0';
s[p2--] = '2';
s[p2--] = '%';
}else {
s[p2--] = s[p1];
}
}
return new String(s);
}
· 5. 压缩字符串
public static String zipString(String src) {
StringBuilder sb = new StringBuilder(); // 用来拼接形成新字符串
char temp = 0; // 上一个字符
int count = 0; // 计数,字符出现了几次
for(int i = 0; i < src.length(); i++) {
char charAt = src.charAt(i); // 当前字符
if (sb.length() == 0) { // 处理第一个字符
sb.append(charAt);
count = 1;
}else { // 处理除第一个字符外其他字符
if(temp == charAt) count++; // 当前字符与temp相同,计数+1
else { // 当前字符与temp不同
temp = charAt; // 更新temp值
sb.append(count).append(charAt); // 将前一个不同字符的数量、新一个字符加入sb
count = 1; // 更新计数值为1
}
}
}
// 循环结束后最后一个字符的情况可能没有记录下去
if(count >= 1) sb.append(count);
// 比较新字符串和原字符串
if(sb.length() >= src.length()) return src;
return sb.toString();
}
6. 判断两字符串的字符集是否相同
用数组进行辅助的话有限制:
/**
* 限制串由ASCII码字符串组成
* @param s1
* @param s2
* @return
*/
public boolean check(String s1, String s2) {
int[] help = new int[256];
// 扫描s1
for(int i = 0; i < s1.length(); i++) {
char c = s1.charAt(i);
if(help[c] == 0) help[c]++;
}
// 扫描s2
for(int i = 0; i < s1.length(); i++) {
char c = s1.charAt(i);
if(help[c] == 0) return false;
}
return true;
}
使用hash表:
思路与用数组一样,只是借集合类存储。
public static boolean hashCheck(String s1, String s2){
// 集合
Map<Character, Integer> map = new HashMap<Character, Integer>();
// 扫描s1
for(int i = 0; i < s1.length(); i++) {
char c = s1.charAt(i);
if(map.get(c) == null)
map.put(c, 1);
}
// 扫描s2
for(int i = 0; i < s2.length(); i++) {
char c = s2.charAt(i);
if(map.get(c) == null)
return false;
}
return true;
}
7. 旋转词
将 s1 与 s1 拼接为 s ,如果 s 中包含 s2 ,说明s2是通过s1循环旋转得到的。
public boolean IsRotate(String s1, String s2) {
// if( 1.length() != s2.length()) return false; 加上这一段即可判断是否为变形词
StringBuilder sb = new StringBuilder(s1).append(s1);
return sb.toString().contains(s2);
}
8. 将字符串中按单词翻转
- 将字符串按单词翻转,如here you are 翻转成are you here
public static void main(String[] args) {
String s = reverse("here you are");
System.out.println(s);
}
private static String reverse(String src) {
String[] help = src.split("\\s");
StringBuilder sb = new StringBuilder();
for(int i = help.length - 1; i >= 0; i--) {
sb.append(help[i] + " ");
}
return sb.deleteCharAt(sb.length() - 1).toString();
}
9. 去掉字符串中连接出现的k次的0
- 移除字符串中连续出现的K个0
方法一:正则表达式
// 正则表达式
public static String remove(String s, int k) {
String regexp = "0{" + k + "}"; // 0{k}代表连续出现了k次的0
return s.replaceAll(regexp, "");
}
方法二:常规做法
// 常规做法
public static String remove2(String s, int k) {
char[] arr = s.toCharArray(); // 转为字符型数组
int cnt = 0; // 计数
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
char c = arr[i];
if (c == '0') // 若发现0,进行计数
cnt++;
else { // 若不是0,加入sb
for (int j = 0; j < cnt % k; j++) { // 将超过k个的0超出部分加入sb
sb.append('0');
}
sb.append(c); // 不是0的字符直接加入
cnt = 0; // 重置计数器
}
}
// 字符串末尾可能存在0,跳出上一个循环后只进行了计数,没有判断是否需要加入sb
// 在此补充操作
for (int i = 0; i < cnt % k; i++) {
sb.append('0');
}
return sb.toString();
}
简单测试:
public static void main(String[] args) {
// 移除字符串中连续出现的K个0
String test = "ab0000skd000sd000000";
System.out.println("原字符串:" + test);
int K = 4;
String r1 = remove(test, K);
System.out.println("正则表达式:" + r1);
String r2 = remove2(test, K);
System.out.println("常规的做法:" + r2);
}
10. 回文字符串
蓝桥杯基础练习题:
问题描述
1221是一个非常特殊的数,它从左边读和从右边读是一样的,编程求所有这样的四位十进制数。
输出格式
按从小到大的顺序输出满足条件的四位十进制数。
方法一:枚举
// 暴力枚举
static void palindromeNumber1() {
// 枚举四位数1000~9999
for (int i = 1000; i <= 9999; i++) {
int g = i % 10;
int s = i / 10 % 10;
int b = i / 100 % 10;
int q = i / 1000 % 10;
if (g == q && s == b)
System.out.print(i + " ");
}
}
方法二:
static void palindromeNumber2() {
for (int i = 1; i < 10; i++) {
for (int j = 0; j < 10; j++) {
int temp = i * 1001 + j * 110;
System.out.print(temp + " ");
}
}
}
简单测试:
public static void main(String[] args) {
palindromeNumber1();
System.out.println();
palindromeNumber2();
}
11. 最短摘要的生成
阿里巴巴笔试题:
给定一段产品的英文描述,包含M个英文字母,每个英文单词以空格分隔,无其他标点符号;再给定N个英文单词关键字,请说明思路并编程实现方法
String extractSummary(String description,String[] key words)
目标是找出此产品描述中包含N个关键字(每个关键词至少出现一次)的长度最短的子串,作为产品简介输出。(不限编程语言)20分。
-
方法一:暴力法
import java.util.HashMap; import java.util.Map; public class demo11 { public static void main(String[] args) { solve1(new String[] { "a", "b", "c", "seed", "h", "e", "f", "c", "c", "seed", "e", "f", "seed", "c" }, new String[] { "c", "e" }); } // 暴力法 private static void solve1(String[] w, String[] keys) { int minLen = Integer.MAX_VALUE; // 初始化设为无穷大 int begin = -1, end = -1; for (int i = 0; i < w.length; i++) { // 求以i开头包含所有关键字的序列 for (int j = i; j < w.length; j++) { // 判断i,j之间是否包含所有关键词 if (containAll(keys, w, i, j)) { // 判断是否需要更新minLen if (j - i + 1 < minLen) { minLen = j - i + 1; begin = i; end = j; } break; // 此时的begin和end是以i开头的包含全部关键字最短的范围,i继续右移缩小范围 } } } print(w, begin, end); // 输出最短摘要 } private static boolean containAll(String[] keyWords, String[] w, int i, int j) { // 将关键字存入map中 Map<String, Integer> mapKey = new HashMap<String, Integer>(); for (int k = 0; k < keyWords.length; k++) { String key = keyWords[k]; if (mapKey.get(key) == null) mapKey.put(key, 1); else mapKey.put(key, mapKey.get(key) + 1); } // 将i到j之间的单词存入另一个map中 Map<String, Integer> map = new HashMap<>(); for (int k = i; k <= j; k++) { String key = w[k]; if (map.get(key) == null) map.put(key, 1); else map.put(key, map.get(key) + 1); } // 查看keys是否全部包含在i和j之间 for (Map.Entry<String, Integer> entry : mapKey.entrySet()) { String key = entry.getKey(); if (map.get(key) == null) return false; } return true; } // 输出w中begin到end的字符串 private static void print(String[] w, int begin, int end) { System.out.println(begin + " " + end); for (int i = begin; i <= end; i++) { System.out.print(w[i] + " "); } System.out.println(); } }
-
方法二:尺取法
1、begin先找到产品描述中第一个关键字;end找到产品描述中包含每个关键字的字串的最后一个关键字;此时所有的关键字都在begin~end范围内,当前minLen = end - begin + 1;
2、begin、end继续分别找关键字,找到后判断新的 end - begin + 1 与minLen 的大小,判断是否需要更新。
import java.util.Arrays; import java.util.HashMap; import java.util.Map; public class demo11 { public static void main(String[] args) { new String[] { "c", "e" }); solve2(new String[] { "a", "b", "c", "seed", "c", "e", "f", "c", "c", "seed", "e", "f", "seed", "c" }, new String[] { "c", "c", "e", "f" }); solve2(new String[] { "a", "b", "a", "a", "b", "c", "d", "h", "e", "f", "f" }, new String[] { "b", "c", "d" }); } private static void solve2(String[] w, String[] keys) { int minLen = Integer.MAX_VALUE; // 初始化设为无穷大 int begin = -1, end = -1; int j = -1; // 记录上一次囊括了所有关键字的右边界 Arrays.sort(keys); // 后面使用二分查找法查找关键词的index,故此处要排序(二分查找前提) for (int i = 0; i < w.length; i++) { // 关键词左边界 String word1 = w[i]; int index = Arrays.binarySearch(keys, word1); // 查看当前i所指单词是否为关键词 if (index == -1) // 不是关键词,继续右移 continue; else { // 是关键词 if (j < w.length && j >= i && containAll(keys, w, i, j)) { // 判断i、j间是否包含所有关键词 // 若包含所有关键词,判断minLen是否需要更新 if (j - i + 1 < minLen) { minLen = j - i + 1; begin = i; end = j; } continue; } } // 关键词右边界 if (j == -1) j = i + 1; while (j < w.length) { String word2 = w[j]; int index1 = Arrays.binarySearch(keys, word2); if (index == -1) { j++; // 继续向右找 continue; } else {// 找到关键字 if (containAll(keys, w, i, j)) { if (j - i + 1 < minLen) {// 更新 minLen = j - i + 1; begin = i; end = j; } break; } else { j++; // j此时指向的是关键词,但是i和j范围内没有包含所有的关键词,j继续向右扩大范围 } } } } print(w, begin, end); } private static boolean containAll(String[] keyWords, String[] w, int i, int j) { ... // 同暴力法中的containAll } private static void print(String[] w, int begin, int end) { ... // 同暴力法中的print } }