【算法很美】字符串问题(上)

一、字符串之形

一些小注意点

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
    	}
    }
    

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值