一些笔试算法

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

  这里提供了一种思路,当想使用动态规划时,但是Sk 和 Sk-1 的关系不能完全直接关联时,要换一种表示方法,可以尝试使用数组存储找的到还不是找不到

2.两个数组的交集

  这个其实就是数据库的join(连接)

  先来看看ORACLE的做法:

  *嵌套循环连接(nested loop)

  1、 Oracle首先选择一张表作为连接的驱动表,这张表也称为外部表(Outer Table)。由驱动表进行驱动连接的表或数据源称为内部表(Inner Table)。 
  2、 提取驱动表中符合条件的记录,与被驱动表的连接列进行关联查询符合条件的记录。
  事实就是用驱动表来查询外部表,因为外部表经常有索引,所以查询(使用B树或二分查找)比较快。复杂度为0(nlog(n)+mlog(n))。
  *排序合并连接(merge sort join)
  排序合并连接的方法非常简单。在排序合并连接中是没有驱动表的概念的,两个互相连接的表按连接列的值先排序,排序完后形成的结果集再互相进行合并连接提取符合条件的记录。相比嵌套循环连接,排序合并连接比较适用于返回大数据量的结果。   
  MSJ其实就是排序后,用两个指针指向两个数组,逐个比较,不算排序,复杂度为0(mlog(m)+nlog(n)+n+m)。
 *
哈希连接(Hash Join)
  当内存能够提供足够的空间时,哈希(HASH)连接是Oracle优化器通常的选择。哈希连接中,优化器根据统计信息,首先选择两个表中的小表,在内存中建立这张表的基于连接键的哈希表;优化器再扫描表连接中的大表,将大表中的数据与哈希表进行比较,如果有相关联的数据,则将数据添加到结果集中。
  典型的空间换时间的做法,对小表建立HASH,然后大表直接对照HASH,就能找到交集,算法复杂度为O(m+n)。  
  算法实现:
  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 }
View Code

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

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

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

  2).数学策略

 递推公式
f[1]=0;
f[i]=(f[i-1]+m)%i;  (i>1)
 递推后最后一个为最后留下的值。
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     }
View Code

  这种有重复子问题的问题,一定要往递归和数学方向想!。

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

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

8.字符串的模拟加减法

  1).题目描述(50分): 
  通过键盘输入100以内正整数的加、减运算式,请编写一个程序输出运算结果字符串。
  输入字符串的格式为:“操作数1 运算符 操作数2”,“操作数”与“运算符”之间以一个空格隔开。
   补充说明:
  1. 操作数为正整数,不需要考虑计算结果溢出的情况。
  2. 若输入算式格式错误,输出结果为“0”。
  要求实现函数: 
  void arithmetic(const char *pInputStr, long lInputLen, char *pOutputStr);
  【输入】 pInputStr:  输入字符串
           lInputLen:  输入字符串长度         
  【输出】 pOutputStr: 输出字符串,空间已经开辟好,与输入字符串等长;
  【注意】只需要完成该函数功能算法,中间不需要有任何IO的输入输出
  2).当看到这个道题,第一反应很简单。。可是就是这种字符串的题,反而容易出错,主要是数组的越界检查

   注意代码中的错误注释

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

 

 

参考:编程之美

posted on 2013-11-27 21:33  依蓝jslee  阅读(409)  评论(0编辑  收藏  举报

导航