算法------编程珠玑(ProgrammingPeals)第二章习题(JAVA)

package code_02_chapter;
import java.util.Arrays;
import java.util.Comparator;

/*
 *Created by William on 2018/6/15 0015
 */
public class Question2InChapter2 {
    /**
     * Q1:
     * 考虑查找给定输入单词的所有变位词的问题。仅给定单词和字典的情况下,如何解决该问题?
     * 如果有一些时间和空间可以响应任何查询之前预处理字典,又会如何处理?
     * 不能预处理时,为给点单词做标记,如mississippi的标记为(i4m1p2s4)标记内字母按字典序排列,
     * 遍历字典,每查询一个单词,同样为其作出标记,做完后比较标记。还有种方式是在做标记的时候就进行判断,
     * 如果查询单词中存在字母数量大于目标单词,break,如果存在字母字典序小于目标单词的第一个字母,break。
     * 如果可以预处理,建立森林,根结点依次为字母。
     */

    /**
     * Q2:
     * 给定包含4 300 000 000个32位整数的顺序文件,如何找出一个至少出现两次的整数。
     * 给定的32位整数的个数是4 300 000 000大于2^32-1, 如果其中没有任何一个缺失的32整数,
     * 那么重复整数个数就是(4300000000-2^32+1)个。
     * 方法1:如果有足够的内存,使用位图技术就更方便了!
     * 方法2:使用几个中间文件,按每次中间值的大小作为临界值分割整数,
     * 统计每个文件中的应该有的整数个数和实际的整数个数,最终可以确定重复的整数应该在哪个文件中!
     * 对该文件排序,使用二分搜索!
     */
    public static class Q2 {
        //先行搜索
        public static void resolve1() {
            int[] arr = {2, 2, 3, 4, 5, 7, 11, 12, 13, 14, 15};
            int increase = arr[0];
            for (int iCount = 0; iCount < arr.length; iCount++) {
                if (arr[iCount] > iCount + increase) {
                    increase += (arr[iCount] - iCount - increase);
                    continue;
                }
                if (arr[iCount] < iCount + increase) {
                    System.out.println("重复的数字是:" + arr[iCount]);
                    break;
                }
            }
        }

        public static void main(String[] args) {
            resolve1();
        }
    }

    /**
     * Q3:
     * 前面涉及了两个需要精巧代码来实现的向量旋转算法。将其分别作为独立的程序实现。
     * 在每个程序中,i和n的最大公约数如何出现?
     */
    public static class Q3 {
        /**
         * 题目:将一个n元一维数组a[n]左移i个位置。例如,当n=8,i=3时,数组abcdefgh旋转为defghabc。
         * 请设计一个算法完成这个任务。
         */

        /**
         * 1. 块交换法
         * 分析:将n元一维数组a[n]分解为两块,将第一块存储在临时数组中,
         * 将第二块前移i个单位,再将临时数组加入到第二块后面。
         * 如:n=8,i=3时,将第一块abc存储为临时变量,第二块defgh前移3个单位,再将abc放入到defgh后面。
         * 思考:这种方法最大的缺陷至少需要与两块中较小的一块大小的临时变量。
         */

        /**
         * 2.杂技算法
         * 分析:将a[0]存储在一个临时变量中,然后将a[i]替换a[0],a[2i]替换a[i]….当一个循环结束的时候,
         * 若替换次数小于n,则从a[1]开始替换…,需要经过gcd(n,i)(n和i的最大公约数)次循环后,
         * 才能把每一个元素都移到该移的地方。
         */
        //求最大公约数,辗转相除法
        public static int gcd(int a, int b) {
            while (a != 0) {
                if (a >= b) a -= b;
                else {
                    int t = a;
                    a = b;
                    b = t;
                }
            }
            return b;
        }

        //杂技算法
        public static String rotate1(String a, int i) {
            int length = a.length();
            int gcdNum = gcd(i, length);
            char[] chars = a.toCharArray();
            for (int j = 0; j < gcdNum; j++) {
                char temp = chars[j];
                int first = j;
                while (true) {
                    int second = (first + i) % length;
                    if (second == j) break;
                    chars[first] = chars[second];
                    first = second;
                }
                chars[first] = temp;
            }
            return String.valueOf(chars);
        }

        public static void main(String[] args) {
            System.out.println(rotate1("abcdefg", 3));
        }

        /**
         * 求逆算法
         * 分析:将a[n]看做是向量BC组成,最终的结果需要时CB,过程如下:将BC各分别取逆B^-1C^-1,
         * 再对整个式子取逆,得到CB。
         */
        public static class Reverse {
            public static String revert(String str, int start, int end) {
                char[] chars = str.toCharArray();
                while (start < end) {
                    char temp = chars[start];
                    chars[start] = chars[end];
                    chars[end] = temp;
                    start++;
                    end--;
                }
                return String.valueOf(chars);
            }

            public static void main(String[] args) {
                String str = "abcdefgh";
                String a = revert(str, 0, 2);
                String b = revert(a, 3, 7);
                String result = revert(b, 0, 7);
                System.out.println(result);
            }
        }


    }

    /**
     * Q5:
     * 将向量abc转为cba
     * 对abc取a^ b^ c^  再取(a^ b^ c^ )^  ,^ 表示对向量的转置。
     */

    /**
     * Q6:
     * 题目太长,不想看。。。
     */

    /**
     * Q7:
     * 在20世纪60年代早期,Vic Vyssotsky与一个程序员一起工作,该程序员需要转置一个存储在磁带上的4000*4000的矩阵
     * (每条记录的格式相同,为数十个字节).他的同事最初提出的程序需要运行50个小时.
     * Vyssotsky如何将运行时间减少到半小时呢?
     * 思想:为每条记录插入行号和列号,先按  列  排序,然后删除行号和列号
     */
    public static class Q7 {
        public static class Data {
            public Data() {
                init();
            }
            public void init() {
                row = 0;
                col = 0;
                element = 0;
            }
            int row;
            int col;
            int element;
        }

        static class ComByCol implements Comparator<Data> {
            public int compare(Data o1, Data o2) {
                return o1.col - o2.col;
            }
        }
        public static int[][] generateMatrix(int rows, int cols) {
            int[][] matrix = new int[rows][cols];
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < cols; j++) {
                    matrix[i][j] = i * rows + j + 1;
                }
            }
            return matrix;
        }
        public static Data[] matrixToDataArray(int[][] matrix) {
            int row = matrix.length;
            int cols = matrix[0].length;
            Data[] data = new Data[row * cols];
            int count = 0;
            for (int i = 0; i < row; i++) {
                for (int j = 0; j < cols; j++) {
                    data[count] = new Data();
                    data[count].row = i;
                    data[count].col = j;
                    data[count].element = matrix[i][j];
                    count++;
                }
            }
            return data;
        }
        public static void printMatrix(int[][] matrix) {
            for (int i = 0; i < matrix.length; i++) {
                for (int j = 0; j < matrix[i].length; j++) {
                    System.out.print(matrix[i][j] + " ");
                }
                System.out.println();
            }
        }
        public static void printDataArray(Data[] datas) {
            System.out.println("-------------------- data array -----------------");
            int width = (int) Math.sqrt(datas.length);
            for (int i = 0; i < datas.length; i++) {
                if (datas[i].element < 10) {
                    System.out.print(" " + datas[i].element + ":[" + datas[i].row + "," + datas[i].col + "]  ");
                } else {
                    System.out.print(datas[i].element + ":[" + datas[i].row + "," + datas[i].col + "]  ");
                }
                if ((i + 1) % width == 0) {
                    System.out.println();
                }
            }
            System.out.println();
        }
        //矩阵过大的话不能直接这么干
        public static int[][] revertMatrix(int[][] matrix) {
            int[][] newMatrix = new int[matrix.length][matrix[0].length];
            for (int i = 0; i < matrix.length; i++) {
                for (int j = 0; j < matrix[0].length; j++) {
                    newMatrix[i][j] = matrix[j][i];
                }
            }
            return newMatrix;
        }
        public static void main(String[] args) {
            int[][] matrix = generateMatrix(6, 6);
            printMatrix(matrix);
            Data[] data = matrixToDataArray(matrix);
            printDataArray(data);
            int[][] matrixReverted = revertMatrix(matrix);  //算法1,最笨的算法,也浪费空间,矩阵过大的话不能直接这么干
            Data[] matrixRevertedDatas = matrixToDataArray(matrixReverted);
            printDataArray(matrixRevertedDatas);
            System.out.println("---------------分割线-----------------");
            Arrays.sort(data, new ComByCol());       //算法2,优化后的算法,按列排序
            printDataArray(data);
        }
    }

    /**
     * Q8:
     * 给定一个n元实数集合、一个实数t和一个整数k,如何快速确定是否存在一个k元子集,其元素之和不超过t?
     * 将n个元素从小到大排序,计算头k个数和,如果包含k个最小元素的子集的和不超过t时,
     * 那么就肯定存在和不超过t的k元子集存在。
     *
     * 法1:利用快速排序,对元素进行排序,排序时间复杂度是nlogn,然后取前k个,
     * 所以在正比于nlogn时间内确定是不是有这个子集。
     * 法2:不用全部排序。比如原集合存放在数组A[0,...,n-1]中,定义辅助数组B[0,...,k-1],
     * 先将A中的前k个元素排序放在B中。A中从第k+1个元素(A[k])开始,用插入排序的方法,
     * 插入到B数组中(B中最大的自然会被丢弃),最后B中就是最小的k个。时间复杂度是O(kn)。
     * 法3:构建一个最小堆,取其前k个最小的数。 也可以构建一个k个元素的最大堆来获得k个最小的元素。
     * 时间复杂度是O(nlogk)。
     * 法4:《算法导论》第九章中位数和顺序统计学的9.2节中以期望线性时间找出n元素集合中第k小的元素。
     * 若知道第k小元素了,以此作为游标,在线性时间内将k个最小元素都找到。
     * 两个线性时间满足题目的正比于n的时间复杂度的要求。这带来另一个问题如何找到出n元集合中第k小的元素呢?(见算法导论)
     * 法5:改进快速排序不用排序出全部的数,只需要快速排序+二分搜索在线性时间找到第K小的数同时从这个数开始
     * 往左都是最小的k个数。时间复杂度是O(n).
     */
}

猜你喜欢

转载自blog.csdn.net/weianluo/article/details/80719657