字符串问题经典问题
一。字符串循环移位问题;
给定一个字符串S[0...N-1],要求把S的前k个字符移动到S的尾部,如把字符串“abcdef”向左移动2位得到“cdefab”。
- 循环左移n+k位和k位的结果是一样的;
- 循环右移k位相当于循环左移n-k位。
- 算法要求:时间复杂度O(n), 空间复杂度O(1).
- 不能采用BF,时间复杂度为O(kN);
- 这里采用类似矩阵逆置的思想:(XTYT)T= YX.
-
/** * 该算法实现将字符串循环左移k位 * @param s * @param k * @return */ public static String leftShifting(String s, int k) { char[] ch = s.toCharArray(); k %= s.length(); leftShiftingHelper(ch, 0, k-1); leftShiftingHelper(ch, k, ch.length-1); leftShiftingHelper(ch, 0, ch.length-1); return new String(ch, 0, ch.length); } /** * 将字符数组的m-n位逆置 * @param ch * @param m * @param n */ private static char[] leftShiftingHelper(char[] ch, int m, int n) { int i = m, j = n; while(i < j) { char c = ch[i]; ch[i++] = ch[j]; ch[j--] = c; } return ch; }
二。压缩空格问题。将给定字符串中所有的空格去掉;
给定某字符串S,该字符串中有若干空格,删除这些空格,并返回修改后的字符串,要求时间复杂度为:O(N),空间复杂度为O(1).
- 给出两个指针,一个指针向后寻找不为空的元素,一个指针指示目前字符串已经达到的位数;代码如下:
-
/** * *算法描述: * 删除给定字符串中所有的空格 * @param s * @return */ public static String deleteSpaceSpaces(String s) { if(s == null || s.length() == 0) return s; int i = 0, j = 0; char[] ch = s.toCharArray(); while(j < ch.length && i < ch.length) { if(ch[j] != ' ') { if( i != j) { ch[i] = ch[j]; } i++; } j++; } return new String(ch, 0, i); }
三。求一个数组中最大的2个数。(TopN问题)
给定一个数组,求该数组中最大的两个数。可以假设数组的长度大于2. O(N) and O(1).
/** * 该算法寻找一个数组中前2大的数,假设数组长度大于2 * @param nums * @return */ public static int[] topTwo(int[] nums) { int[] res = new int[2]; res[0] = nums[0]; res[1] = nums[0]; for(int i=0; i<nums.length; i++) { if(nums[i] > res[0]) { res[1] = res[0]; res[0] = nums[i]; } else if(nums[i] > res[1]) res[1] = nums[i]; } return res; }
四。Huffman编码问题(字符串的最优无损压缩)
- 凡是问如何保证传输中信息不丢失,最优编码,无损压缩等都要自动反应到Huffman编码!
- 思想:根据源字符出现的(估算)概率对字符编码,概率高的字符使用较短的编码,概率低的字符使用较长的编码,从而使得编码后的字符串长度期望最小。
- 是一种贪心算法:每次总选择两个最小概率的字符节点合并。(概率可用频率代替)
- 初始节点为N的,形成Huffman树后一共有(2*N-1)个节点。
- 使用数组代替二叉树;
- Huffman编码不是唯一的。
-
import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; class Huffman { Huffman parent; Huffman left; Huffman right; int weight; } public class HuffmanCoding { public static ArrayList<String> Huff(String s) { ArrayList<String> list = new ArrayList<String>(); HashMap<Character, Integer> map = computWordFrequency(s); Huffman[] huffTree = new Huffman[2 * map.size() - 1]; //用数组模拟霍夫曼树 for(int i=0; i<huffTree.length; i++) huffTree[i] = new Huffman(); Iterator<Entry<Character, Integer>> it = map.entrySet().iterator(); int i=0; while(it.hasNext()) { Map.Entry entry = (Entry) it.next(); huffTree[i].weight = (int) entry.getValue(); System.out.println(i + " " + huffTree[i].weight); i++; } //建树 int len = map.size(); for(int j=map.size(); j<huffTree.length; j++) { int[] mins = topTwoMin(huffTree, 0, len); System.out.println(Arrays.toString(mins)); huffTree[j].weight = huffTree[mins[0]].weight + huffTree[mins[1]].weight; huffTree[j].left = huffTree[mins[0]]; huffTree[j].right = huffTree[mins[1]]; huffTree[mins[0]].parent = huffTree[j]; huffTree[mins[1]].parent = huffTree[j]; len++; } //编码 for(int j=0; j<map.size(); j++) { Huffman temp = huffTree[j]; StringBuffer sb = new StringBuffer(); while(temp.parent != null) { if(temp.parent.left == temp) sb.append("0"); else sb.append("1"); temp = temp.parent; } list.add(sb.toString()); } return list; } /** * 该算法寻找一个数组中最小的2数,假设数组长度大于2 * @param nums * @return */ public static int[] topTwoMin(Huffman[] huffTree, int start, int end) { int[] res = new int[2]; res[0] = Integer.MAX_VALUE; res[1] = Integer.MAX_VALUE; int[] index = new int[2]; for(int i=start; i<end; i++) { if(huffTree[i].parent == null && huffTree[i].weight < res[0]) { res[1] = res[0]; res[0] = huffTree[i].weight; index[1] = index[0]; index[0] = i; } else if(huffTree[i].parent == null && huffTree[i].weight < res[1]) { res[1] = huffTree[i].weight; index[1] = i; } } return index; } /** * 该函数返回每个字符出现的词频数 * @param s * @return */ public static HashMap<Character, Integer> computWordFrequency(String s) { HashMap<Character, Integer> map = new HashMap<Character, Integer>(); char[] ch = s.toCharArray(); for(int i=0; i<ch.length; i++) { if(!map.containsKey(ch[i])) map.put(ch[i], 1); else map.put(ch[i], map.get(ch[i]) + 1); } return map; } public static void main(String[] args) { // TODO Auto-generated method stub String s = "aadabcbacdaebdebcdbebdacaedaacaebcaaaaa"; System.out.println(Huff(s)); } }
五。字符串的全排列枚举
- (无重复字符串的递归是最优算法)分析:如给出字符串“1234”,
- 若以1当首位,则需要再对“234”进行全排列;
- 若依2当首位,则需要再对“134”进行全排列;
- 若以3为首位,则需要再对“214”进行全排列;
- 若以4为首位,则需要再对“231”进行全排列;
-
/** * 当给定数组中没有重复元素时,将数组中饿元素全排序 * @param nums * @param k */ public static void permutation(int[] nums, int k) { if(k == nums.length-1) { for(int n : nums) System.out.print(n + " "); System.out.println(); return; } for(int i=k; i<nums.length; i++) { int temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; permutation(nums, k+1); temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; } }
- 空间换时间
- (非递归实现)从字典顺序12345到字典逆序54321
- 因此问题演变为:找一个序列的下一个序列是多少?
- 逐位考虑哪个能增大?
- 一个数右面有比它大的数存在,它就能增大;
- 这个数应该增大到多少?
- 增大到它后面比它大的所有数中最小的那个。
- 如“21543”的下一个序列应该是“23xxx”。显然xxx应该有小到大排列:145,故“21543”的写一个序列应该是“23145”。
- 从后往前找,找最后一个升序的位置;
- 找后面比该位置大的所有数中最小的一个;
- 交换这两个位置;
- 翻转后面的位数。
- 如何知道一个给定的序列,知道它是全排列中的第几个排列?
- Cantor数组。
六。KMP算法
- 字符串查找问题:给定文本串text和模式串pattern,从文本串text中找出模式串pattern第一次出现的位置。
- 暴力求解(Brute Force)?O(M*N) O(1)
- KMP算法是一种线性时间复杂度的字符串匹配算法,是对BF的一种改进。O(M*N) O(M)