一些笔试算法
1.数组中的数分为两组,让给出一个算法,使得两个组的和的差的绝对值最小
数组中的数的取值范围是0<x<100,元素个数也是大于0, 小于100
比如a[]={2,4,5,6,7},得出的两组数{2,4,6}和{5,7},abs(sum(a1)-sum(a2))=0;
比如{2,5,6,10},abs(sum(2,10)-sum(5,6))=1,所以得出的两组数分别为{2,10}和{5,6}。
1 /** 2 * 数组中的数分为两组,让给出一个算法,使得两个组的和的差的绝对值最小 3 * 数组中的数的取值范围是0<x<100,元素个数也是大于0, 小于100 4 比如a[]={2,4,5,6,7},得出的两组数{2,4,6}和{5,7},abs(sum(a1)-sum(a2))=0; 5 比如{2,5,6,10},abs(sum(2,10)-sum(5,6))=1,所以得出的两组数分别为{2,10}和{5,6}。 6 * @author hasee 7 * 8 */ 9 public class cutArray { 10 11 /** 12 * isOK[i][v]表示是否可以找到i个数,使得他们之和等于v 13 * 动态规划 使用前一个isOK计算后一个 14 * if(v>=arr[k] && isOK[i-1][v-arr[k]]) 15 * isOK[i][v] = true; 16 */ 17 static boolean isOK[][]; 18 static int sum; 19 static{ 20 isOK = new boolean[20][100]; 21 isOK[0][0] = true; 22 for (boolean[] bs : isOK) { 23 for (boolean b : bs) { 24 b = false; 25 } 26 } 27 } 28 public static void cutBySum(int[] arr){ 29 for (int i = 0; i < arr.length; i++) {//计算总和 30 sum+=arr[i]; 31 } 32 //总和分成两个和,小的那边肯定在(0,sum/2)之间 33 //只用计算一边的和,两边的差值 = sum - (onePlus<<2); 34 for (int v = 1; v <= sum/2; v++) { 35 //一边的个数最多有length-1个,最少有一个 36 for (int i = arr.length-1; i >= 1; i--) { 37 //遍历数组中所有元素 38 for (int k = 1; k < arr.length; k++) { 39 if(v>=arr[k] && isOK[i-1][v-arr[k]]) 40 isOK[i][v] = true; 41 } 42 } 43 } 44 } 45 46 /** 47 * 递归,遍历解决 所有集 2^n 48 */ 49 static List<Integer> list1 = new ArrayList<Integer>();//临时数组1 50 static List<Integer> list2 = new ArrayList<Integer>();//临时数组2 51 static Integer[] final1;//结果数组1 52 static Integer[] final2;//结果数组2 53 static int dValue=Integer.MAX_VALUE;//结果差值 54 public static void cut(int[] arr, int cur){ 55 if (cur == arr.length) { //递归边界,数组里所有数都已确定位置 56 int a=0,b=0; 57 for (Integer i : list1) { //计算和 58 a+=i; 59 } 60 for (Integer i : list2) {//计算和 61 b+=i; 62 } 63 int c = a-b < 0 ? b-a : a-b; 64 if(c<dValue){ //生成最后结果数组 65 dValue=c; 66 final1=(Integer[])list1.toArray(new Integer[list1.size()]); 67 final2=(Integer[])list2.toArray(new Integer[list2.size()]); 68 } 69 return; 70 } 71 list1.add(arr[cur]);//加入数组1的情况 72 cut(arr, cur+1); 73 list1.remove((Integer)arr[cur]);//还原 74 75 list2.add(arr[cur]);//加入数组2的情况 76 cut(arr, cur+1); 77 list2.remove((Integer)arr[cur]);//还原 78 } 79 80 public static void main(String[] args) { 81 int[] arr = {1,3,5,8,6,2,9,4,5,1,2,5}; 82 83 //方法一 2^n 84 cut(arr, 0); 85 for (int i : final1) { 86 System.out.println("final1: "+i); 87 } 88 for (int i : final2) { 89 System.out.println("final2: "+i); 90 } 91 System.out.println(dValue); 92 93 //方法2 n^2*sum 94 cutBySum(arr); 95 dValue=Integer.MAX_VALUE; 96 int onePlus=0; 97 for (int i = 0; i < arr.length/2; i++) { 98 for (int v = 0; v < sum; v++) { 99 if (isOK[i][v]) { 100 int tempDValue = sum - (v<<1); 101 if (tempDValue<dValue) { 102 dValue = tempDValue; 103 onePlus = v; 104 } 105 } 106 } 107 } 108 System.out.println(dValue+"d和p"+onePlus); 109 } 110 }
这里提供了一种思路,当想使用动态规划时,但是Sk 和 Sk-1 的关系不能完全直接关联时,要换一种表示方法,可以尝试使用数组存储找的到还不是找不到!
2.两个数组的交集
这个其实就是数据库的join(连接)
先来看看ORACLE的做法:
*嵌套循环连接(nested loop)
1 /** 2 * 求两个数组的交集 3 * @author jslee 4 * 5 */ 6 public class Intersection { 7 8 public static int[] result=new int[1000]; 9 static{ 10 for (int i =0; i<result.length; i++) { 11 result[i]=Integer.MAX_VALUE; 12 } 13 } 14 public static void main(String[] args) { 15 int[] a = {5,7,9,3,176,7,4}; 16 int[] b = {8,7,1,3,14,56,23}; 17 interByHJ(a,b); 18 for (int i = 0; i < result.length; i++) { 19 if(result[i]==Integer.MAX_VALUE) 20 return; 21 System.out.println(result[i]); 22 } 23 } 24 25 //嵌套循环连接(nested loop) 26 public static void interByNL(int[] a, int[] b) { 27 if (a==null || b==null) 28 return; 29 int[] bigArr,smallArr; 30 //选择大表(外部表)排序,小表做驱动表 31 if (b.length > a.length) { 32 bigArr=b; 33 smallArr=a; 34 }else { 35 bigArr=a; 36 smallArr=b; 37 } 38 Arrays.sort(bigArr); 39 int number=0; 40 for (int i = 0; i < smallArr.length; i++) { 41 if(binaryFind(bigArr,smallArr[i])){ 42 result[number++]=smallArr[i]; 43 } 44 } 45 } 46 47 //排序合并连接(merge sort join) 48 public static void interByMSJ(int[] a, int[] b) { 49 if (a==null || b==null) 50 return; 51 Arrays.sort(a); 52 Arrays.sort(b); 53 int i=0,j=0; 54 int number=0; 55 while (i<a.length && j<b.length) { 56 if (a[i]>b[j]) 57 j++; 58 else if (a[i]<b[j]) 59 i++; 60 else{ 61 result[number++]=a[i]; 62 i++;j++; 63 } 64 } 65 } 66 67 //哈希连接(Hash Join) 68 public static void interByHJ(int[] a, int[] b) { 69 if (a==null || b==null) 70 return; 71 int[] bigArr,smallArr; 72 //选择小表做Hash表 73 if (b.length > a.length) { 74 bigArr=b; 75 smallArr=a; 76 }else { 77 bigArr=a; 78 smallArr=b; 79 } 80 boolean[] hash = new boolean[1000]; 81 for (int i : smallArr) 82 hash[i]=true; 83 int number=0; 84 for (int i : bigArr) { 85 if (hash[i]==true) { 86 result[number++]=i; 87 } 88 } 89 } 90 91 //二分查找 92 public static boolean binaryFind(int[] arr, int k) { 93 int i=0,j=arr.length-1; 94 int mid = 0; 95 while(i<j){ 96 mid = i+((j-i)>>>1); 97 if (arr[mid]>k) 98 j=mid-1; 99 else if (arr[mid]<k) 100 i=mid+1; 101 else 102 return true; 103 } 104 if (arr[i]!=k) 105 return false; 106 else 107 return true; 108 } 109 110 }
3.子数组和最大值
1).分治法:分成前半段和后半段,然后最大值则是以下三种情况的最大值:
*(start,mid-1)的最大子数组和
*(mid+1,end)的最大子数组和
*跨过前半和后半数组的子数组,也就是说包含 mid 的值。
前两个递归求,第三个只需遍历一遍数组即可求组。
2).动态规划 -> 递归
start[i] :arr[i] - arr[end] 的包含arr[i]的最大子数组和
all[i] :arr[i] - arr[end] 的最大子数组和
因为子数据具有连续性,所以构建一个新变量 start[i] 来动态规划!
start[i] = max(arr[i], arr[i]+start[i+1]);
all[i] = max(start[i], all[i+1]);
由于只需算出最大的值,所以直接优化成两个变量即可。
1 public static int max(int a,int b){ 2 return a>b ? a : b; 3 } 4 public static int subMaxByBinary(int[] arr, int start, int end){ 5 if (arr==null) 6 return -1; 7 if (end == start) 8 return arr[end]; 9 int mid = start+(end-start>>>1); 10 int midSum=arr[mid]; 11 int tempSum=arr[mid]; 12 for (int i = mid-1; i >= start; i--) { 13 tempSum += arr[i]; 14 if (tempSum > midSum) 15 midSum = tempSum; 16 } 17 tempSum=midSum; 18 for (int i = mid+1; i <= end; i++) { 19 tempSum += arr[i]; 20 if (tempSum > midSum) 21 midSum = tempSum; 22 } 23 return max(max(subMaxByBinary(arr,start,mid-1), 24 subMaxByBinary(arr,mid+1,end)),midSum); 25 } 26 27 public static int subMaxByDP(int[] arr){ 28 if (arr==null) 29 return -1; 30 int start = arr[arr.length-1]; 31 int all = start; 32 for (int i = arr.length-2; i >= 0; i--) { 33 start = max(arr[i],arr[i]+start); 34 all = max(start, all); 35 } 36 return all; 37 }
4.最大出现次数的数(超过一半)
方法有多种:*排序后选,*快速排序的思路,*数组特点
这里选得利用数组特点的方法:
1 public static int findMax(int[] arr) { 2 if (arr == null) 3 return Integer.MIN_VALUE; 4 //使用两个变量记录当前存活的数字, 和 出现的次数 5 int num=arr[0],time=1; 6 //超过一半的数字肯定要比其它数字的总和多 7 //所以最后存活的肯定是那个数字 8 for (int i = 1; i < arr.length; i++) { 9 /*if (time==0) { //前面判断和后面判断一样的效果 10 num = arr[i]; 11 time = 1; 12 }*/ 13 if (arr[i]==num) //次数加1 14 time++; 15 else 16 time--; //不一样次数减1,所以其它数肯定减不过那个数 17 if (time==-1) { //time次数为负时,此时应该更换当前数字 18 num = arr[i]; 19 time = 1; 20 } 21 } 22 return num; 23 }
5.约瑟夫环问题
约瑟夫环是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。
eg:n = 9,k = 1,m = 5【解答】出局人的顺序为5,1,7,4,3,6,9,2,8。
1).直接先是用boolean数组来模拟整个游戏过程
1 public static void countJos(int n, int m,int k) { 2 boolean[] result = new boolean[n+1];//排列的最后值,为了下标加1,好处理 3 int remain=n; //剩下的总数 4 int count=0,index=k; 5 while(true){ 6 if (index==n+1) //越界了 7 index=1; 8 if (result[index]) { //已被选过 就跳过了 9 index++; 10 continue; 11 } 12 if (remain==1) { //最后一个了。 13 System.out.println(index); 14 break; 15 } 16 ++count; 17 if (count == m) { //数到要求了 18 count=0; 19 result[index] = true; //标记它为已被选状态 20 System.out.println(index);//打印 21 remain--; 22 } 23 index++; 24 } 25 }
2).数学策略
1 public static void countJosBymath(int n, int m) { 2 int s=0; 3 for (int i=2; i<=n; i++) 4 s=(s+m)%i; 5 s++; 6 System.out.println("最后一个为"+s); 7 }
这种有重复子问题的问题,一定要往递归和数学方向想!。
6.字符串剔除
两个字符串A、B。从A中剔除存在于B中的字符。比如A=“hello world”,B="er",那么剔除之后A变为"hllowold"。空间复杂度要求是O(1),时间复杂度越优越好。
位Hash映射的方法:
1 /** 2 * 两个字符串A、B。从A中剔除存在于B中的字符。 3 * 比如A=“hello world”,B="er",那么剔除之后A变为"hllowold"。 4 * 空间复杂度要求是O(1),时间复杂度越优越好。 5 * @param str1 6 * @param str2 7 * @return 8 */ 9 public static String elimChar(String str1, String str2) { 10 long[] hash=new long[2]; //两个long构成的128的位图空间 11 for (int i = 0; i < str2.length(); i++) { //hash映射 12 byte b = (byte) str2.charAt(i); 13 if (b<=64) //java的默认运算是int 14 hash[0] |= (long)1<<b;//注意1必须强制转换为long 15 else 16 hash[1] |= (long)1<<(b-64); 17 } 18 19 int index=0; //使用一个index记录现有多少元素 20 char[] arr = new char[str1.length()];//结果数组 21 for (int i = 0; i < str1.length(); i++) {//计算是否有包含 22 byte b = (byte) str1.charAt(i); 23 if (b<=64) { 24 if (((hash[0]>>>b)&1) == 0) { //注意为无符号位移 25 arr[index] = str1.charAt(i); 26 index++; 27 } 28 } 29 else{ 30 if (((hash[1]>>>(b-64))&1) == 0) { 31 arr[index] = str1.charAt(i); 32 index++; 33 } 34 } 35 } 36 return new String(arr); 37 }
7.trie树
Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
建立+查询在trie中是可以同时执行的(可以看到,建树和查询极其相似),建立的过程也就可以成为查询的过程。
关于节点,如果只有26个字母,可以使用一个26的长度的数组来表示子节点(类似Hash)。
1 /* 2 * trie树 建树,查找的实现 3 */ 4 public class Trie { 5 //树节点 6 public class Node{ 7 public char key; //char值 8 public List<Node> next = new ArrayList<Node>();//儿子们 9 public int time = 1; //此值为此节点重复次数 10 public Node(char key) { 11 this.key = key; 12 } 13 } 14 //建树 15 public Node buildTrie(String[] strArr, Node preRoot) { 16 Node root = preRoot; //可以使用preRoot来扩建树 17 if(root == null) //也可以新建树根节点,根节点为127 18 root = new Node((char)127); 19 for (String str : strArr) { 20 Node node = root; //循环中的当前节点 21 for (int i = 0; i < str.length(); i++) { 22 char c = str.charAt(i); 23 boolean bool = true; //此值为是否树中已有节点 24 for (Node iter : node.next) {//遍历当前节点儿子 25 if (iter.key == c) {//节点已存在,不用新建节点 26 node = iter; 27 node.time++;//增加重复值 28 bool = false; 29 break; 30 } 31 } 32 if (bool) {//新建节点 33 Node temp = node; 34 node = new Node(c); 35 temp.next.add(node);//添加为当前节点的儿子 36 } 37 } 38 } 39 return root; 40 } 41 //深度遍历 42 public void depthLook(Node node){ 43 if (node == null) return; 44 if (node.key != (char)127) //不为根节点,打印 45 System.out.print(node.key); 46 if (node.next.size() == 0) { //没儿子了,为叶节点,打印换行 47 System.out.println(); 48 return; 49 } 50 for (Node iter : node.next) {//递归 51 depthLook(iter); 52 } 53 } 54 //查找 55 public boolean findStr(String str, Node root){ 56 Node node = root; 57 for (int i = 0; i < str.length(); i++) { 58 char c = str.charAt(i); 59 boolean bool = true;//此值为是否树中已有节点 60 for (Node iter : node.next) { 61 if (iter.key == c) { 62 node = iter; 63 bool = false; 64 break; 65 } 66 } 67 if (bool) {//如果没有节点,直接返回没找到 68 return false; 69 } 70 } 71 return true; 72 } 73 public static void main(String[] args) { 74 Trie tr = new Trie(); 75 String[] strArr = {"hello","goodbye","happy","yesterday"}; 76 Node root = tr.buildTrie(strArr,null); 77 tr.depthLook(root); 78 boolean bool = tr.findStr("happya", root); 79 System.out.println(bool ? "找的到" : "没找到啊"); 80 } 81 }
8.字符串的模拟加减法
注意代码中的错误注释。
1 package arithmetic; 2 3 /** 4 * 通过键盘输入100以内正整数的加、减运算式,请编写一个程序输出运算结果字符串。 5 输入字符串的格式为:“操作数1 运算符 操作数2”,“操作数”与“运算符”之间以一个空格隔开。 6 补充说明: 7 1. 操作数为正整数,不需要考虑计算结果溢出的情况。 8 2. 若输入算式格式错误,输出结果为“0”。 9 要求实现函数: 10 void arithmetic(const char *pInputStr, long lInputLen, char *pOutputStr); 11 【输入】 pInputStr: 输入字符串 12 lInputLen: 输入字符串长度 13 【输出】 pOutputStr: 输出字符串,空间已经开辟好,与输入字符串等长; 14 【注意】只需要完成该函数功能算法,中间不需要有任何IO的输入输出 15 示例 16 输入:“4 + 7” 输出:“11” 17 输入:“4 - 7” 输出:“-3” 18 输入:“9 ++ 7” 输出:“0” 注:格式错误 19 * @author hasee 20 * 21 */ 22 public class Arithmetic { 23 24 public static void main(String[] args) { 25 char[] in = "9 ++ 7".toCharArray(); 26 char[] out = new char[5]; 27 arith(in, out); 28 for (char c : out) { 29 System.out.print(c); 30 } 31 System.out.println(); 32 } 33 34 public static void arith(char[] in, char[] out){ 35 try { //错误!!没有考虑每一步都有可能数组越界 36 if (in==null||out==null) 37 return; 38 int index=0; 39 int a,b; 40 char sign; 41 //计算a,各种检查 42 if (in[index]<'0'||in[index]>'9') { 43 out[0] = '0'; 44 return; 45 } 46 a = in[index++] - '0'; 47 if (in[index]!=' ') { 48 if (in[index]<'0'||in[index]>'9') { 49 out[0] = '0'; 50 return; 51 } 52 a = a*10 + in[index++]-'0'; 53 } 54 //空格 55 if (in[index] != ' ') { 56 out[0] = '0'; 57 return; 58 } 59 index++; 60 //sign符号 61 if (in[index] != '+' && in[index] != '-') { 62 out[0] = '0'; 63 return; 64 } 65 sign = in[index++]; 66 //空格 67 if (in[index] != ' ') { 68 out[0] = '0'; 69 return; 70 } 71 index++; 72 //b数字计算,各种检查 73 if (in[index]<'0'||in[index]>'9') { 74 out[0] = '0'; 75 return; 76 } 77 78 b = in[index] - '0'; 79 //错误! 没有考虑数组过界,这种情况越界不算错误,10以下的时候 80 if (in.length-1 > index) { 81 index++; 82 if (in[index]!=' ') { 83 if (in[index]<'0'||in[index]>'9') { 84 out[0] = '0'; 85 return; 86 } 87 b = b*10 + in[index]-'0'; 88 } 89 } 90 91 //计算最终结果 92 int c; 93 if (sign == '+') 94 c = a+b; 95 else 96 c = a-b; 97 //错误!!没有考虑String.valueOf(c).toCharArray()返回的新对象, 98 //out为临时指针,函数完了就销毁。 99 char[] temp = String.valueOf(c).toCharArray(); 100 for (int i = 0; i < temp.length; i++) { 101 out[i] = temp[i]; 102 } 103 return; 104 } catch (ArrayIndexOutOfBoundsException e) { 105 out[0] = '0'; 106 return; 107 } 108 } 109 110 111 }
参考:编程之美