Java 算法题及答案

1,392 阅读29分钟

有3n+1个数字,其中3n个中是重复的,只有1个是不重复的,怎么找出来

public class Demo {
    public static void main(String[] args) {
        int[] array = new int[] {1,2,3,4,6,7,9,6,8};
        System.out.println(findOneNum(array));
    }
    public static int findOneNum(int[] array) {
        // 数组长度
        int length = array.length;
        // 要找的数
        int num;
        // 如果是奇数的情况
        if(length & 1 != 0){
            int temp = array[0];
            int i = 1;
            while (i < length){
                temp = temp ^ array[i];
                i++;
            }
            num = temp;
        }else{
            for(int i=0;i<length;i++){
               //  
            }
        }
        return temp;
    }
}

有1亿个数字,其中有2个是重复的,快速找到它,时间和空间要最优

public class Demo {
    public static void main(String[] args) {
        int[] array = new int[] {1,2,3,4,6,7,9,6,8};
        System.out.println(bigDataFindOneNum(array));
    }
    /**
     * 有1亿个数字,其中有2个是重复的,快速找到它,时间和空间要最优
     * 用位图法解决
     * @param array 一亿个数据的数据源
     * @return 返回重复的数字或不存在返回-1
     */
    public static int bigDataFindOneNum(int[] array) {
        // 最大数 + 1
        int max = 100000001;
        // 初始化位图
        BitSet bitSet = new BitSet(max);
        int i = 0;
        while (i < array.length){
            int value = array[i];
            //判断该数是否存在bitSet里
            if (bitSet.get(value)){
                // 返回重复的数
                return value;
            }else {
                bitSet.set(value, true);
            }
            i++;
        }
        // 不存在返回-1
        return -1;
    }
}

现在有50亿个int类型的正整数,要从中找出重复的数并返回

public class Demo {
    public static void main(String[] args) {
        int[] array = new int[] {11,12,4,4,6,5,3,2,8,1,7,5,6,0,10};
        Set<Integer> t2 = bigNumberFind(array);
        System.out.println(t2);
    }
    /**
     * 题目描述: 现在有五十亿个int类型的正整数,要从中找出重复的数并返回
     * 分析: 判断50亿个数有哪些是重复和判断一个数置否存在50亿个数当中是否存在,其实是一样的。
     * 我们采用bitmap算法来做。不过这里50亿个数,别人肯定是以文件流的形式给你的。
     * 这样我们为了方便,我们就假设这些数是以存在int型数组的形式给我们的。
     * @param array
     * @return
     */
    public static Set<Integer> bigNumberFind(int[] array){
        int j = 0;
        //用来把重复的数返回,存在Set里,这样避免返回重复的数
        Set<Integer> output = new HashSet<>();
        BitSet bitSet = new BitSet(Integer.MAX_VALUE);
        int i = 0;
        while (i < array.length){
            int value = array[i];
            //判断该数是否存在bitSet里
            if (bitSet.get(value)){
                output.add(value);
            }else {
                bitSet.set(value, true);
            }
            i++;
        }
        return output;
    }
}

二分查找法(折半查找法)

public class Demo {
    public static void main(String[] args) {
        int[] array = new int[] {11,12,4,4,6,5,3,2,8,1,7,5,6,0,10};
        System.out.println(binarySearch(array, 7));
    }
    /**
     * 二分查找法
     * @param array
     * @param target
     * @return
     */
    public static int binarySearch(int []array,int target){
        //查找范围起点
        int start=0;
        //查找范围终点
        int end=array.length-1;
        //查找范围中位数
        int mid;
        while(start<=end){
            //mid=(start+end)/2 有可能溢出
            mid=start+(end-start)/2;
            if(array[mid]==target){
                return mid;
            } else if(array[mid]<target){
                start=mid+1;
            }else{
                end=mid-1;
            }
        }
        return -1;
    }
}

小偷来到了一个神秘的王宫,突然眼前一亮,发现5个宝贝,每个宝贝的价值都不一样,且重量也不一样,但是小偷的背包携带重量有限,所以他不得不在宝贝中做出选择,才能使偷到的财富最大,请你帮助小偷计算一下(华为编程题)

/**
 * 输入描述:
 * 宝贝价值:6,3,5,4,6
 * 宝贝重量:2,2,6,5,4
 * 小偷背包容量:10
 * 输出描述:
 * 偷到宝贝的总价值:15
 * 示例1
 * 输入
 * 6,3,5,4,6
 * 2,2,6,5,4
 * 10
 * 输出
 * 15
 */
public class Demo {
    public static void main(String[] args) {
        // 打开输入
        Scanner sc = new Scanner(System.in);
        // 宝贝价值字符串
        String str1 = sc.next();
        // 宝贝重量字符串
        String str2 = sc.next();
        // 背包容量字符串
        String str3 = sc.next();
        // 宝贝价值数组
        int[] value = transform(str1);
        // 宝贝重量数组
        int[] weight = transform(str2);
        // 背包容量数组
        int[] bag = transform(str3);
        // 关闭输入
        sc.close();
        // 获取第一个背包容量
        int m = bag[0];
        int n = value.length;
        int w[] = weight;
        int p[] = value;
        int c[][] = backPackSolution(m, n, w, p);
        System.out.println(c[c.length-1][c[0].length-1]);
    }
    
    public static int[][] backPackSolution(int m, int n, int[] w, int[] p) {
        //c[i][v]表示前i件物品恰放入一个重量为m的背包可以获得的最大价值
        int c[][] = new int[n + 1][m + 1];
        for (int i = 0; i < n + 1; i++){
            c[i][0] = 0;
        }
        for (int j = 0; j < m + 1; j++){
            c[0][j] = 0;
        }
        for (int i = 1; i < n + 1; i++) {
            for (int j = 1; j < m + 1; j++) {
                //当物品为i件重量为j时,如果第i件的重量(w[i-1])小于重量j时,c[i][j]为下列两种情况之一:
                //(1)物品i不放入背包中,所以c[i][j]为c[i-1][j]的值
                //(2)物品i放入背包中,则背包剩余重量为j-w[i-1],所以c[i][j]为c[i-1][j-w[i-1]]的值加上当前物品i的价值
                if (w[i - 1] <= j) {
                    if (c[i - 1][j] < (c[i - 1][j - w[i - 1]] + p[i - 1])){
                        c[i][j] = c[i - 1][j - w[i - 1]] + p[i - 1];
                    }else{   
                        c[i][j] = c[i - 1][j];
                    }
                } else{
                    c[i][j] = c[i - 1][j];
                }
            }   
        }  
        return c;
    }
    /**
     * 把字符串转换为数组
     */
    private static int[] transform(String str1) {
        String[] split = str1.split(":");
        String[] split2 = split[split.length-1].split(",");
        int[] value=new int[split2.length ];
        for (int i = 0; i < value.length; i++) {
            value[i]=Integer.parseInt(split2[i]);
        }
        return value;
    }
}

给定一个正数数组arr(即数组元素全是正数),找出该数组中,两个元素相减的最大值,其中被减数的下标不小于减数的下标。即求出:maxValue = max{arr[j]-arr[i] and j >= i}

public class Demo {
    public static int maxValueSub(int[] array){
        // 最大值
        int max = 0; // 当 i=j=0 时
        // 最小减数下标
        int i = 0;
        // 差
        int sub;
        for (int j = 1; j < array.length; j++) {
            sub = array[j] - array[i];
            if (sub>max){
                // 判断差是否大于最大值
                max = sub;
            }else if (sub <0){
                // 记录最小减数下标
                i= j;
            }
        }
        return max;
    }
    public static void main(String[] args) {
        int [] array = {1,2,4,6,7};
        System.out.println(maxValueSub(array));
    }
}

实现一个函数把两个有序的int数组结合成新的有序数组

public class Demo {
    public static int[] mergeTwoSortedArrays(int[] array1, int[] array2){
        int l1 = array1.length;
        int l2 = array2.length;
        // 定义一个数组
        int [] result = new int [l1+l2];
        int i=0,j=0,k=0; //分别代表数组array1 ,array2 , result 的索引
        // 同时遍历 array1、array2 数组
        while (i<l1 && j<l2){
            if (array1[i]<array2[j]){
                // 如果 array1[i]小于array2[j],则先把array1[i]的放入 result数组中
                result[k++] = array1[i++];
            }else {
                result[k++] = array2[j++];
            }
        }
        // 判断 array1 是否遍历完
        while (i<l1){
            result[k++] = array1[i++];
        }
        // 判断 array2 是否遍历完
        while (j<l2){
            result[k++] = array2[j++];
        }
        return result;
    }
    public static void main(String[] args) {
        int [] array = {1,2,4,6,7};
        System.out.println(Arrays.toString(mergeTwoSortedArrays(array,array)));
    }
}

求一个数组中不存在的最小正整数

public class Demo {
    // 采用标记法可以简单实现
    public static int firstMissingPositive(int[] array){
        // 如果数组为空,则返回1
        if (array == null || array.length<=0)
        {
            return 1;
        }
        int n = array.length;
        // 定义一个长度为array.length 的临时数组
        int[] tempArray = new int[n];
        for (int i = 0; i < array.length; i++) {
            // 如果 array[i] 在 1 -- n 的范围,则在临时数组 array[i]-1的位置标记1
            if (array[i]>=1 && array[i] <= n){
                tempArray[array[i]-1] = 1;
            }
        }
        // 遍历tempArray数组判断是否标记为1,如果不是1,则返回下表对应的正整数 i+1
        for (int i = 0; i < tempArray.length; i++) {
            if (tempArray[i] != 1){
                return i+1;
            }
        }
        //如果rempArray对应位置都标记为1,则 n+1 就是不存在的正整数
        return n+1;
    }
    public static void main(String[] args) {
        int [] array = {3,4,2,5};
        System.out.println(firstMissingPositive(array));
    }
}

求两个大整数的和

public class Demo {
    public static String bigNumberSum(String bigNumberA, String bigNumberB) {
        //1.把两个大整数用数组逆序存储,数组长度等于较大整数位数+1
        int maxLength = bigNumberA.length() > bigNumberA.length() ? bigNumberA.length() : bigNumberB.length();
        int[] arrayA = new int[maxLength + 1];
        int[] arrayB = new int[maxLength + 1];
        for (int i = 0; i < bigNumberA.length(); i++) {
            arrayA[i] = bigNumberA.charAt(bigNumberA.length() -1-i) - '0'; //“ - '0'”是将String型转化为int型
        }
        for (int i = 0; i < bigNumberB.length(); i++) {
            arrayB[i] = bigNumberB.charAt(bigNumberB.length() -1-i) - '0'; //“ - '0'”是将String型转化为int型
        }
        //2.构建result数组,数组长度等于较大整数位数+1
        int[] result = new int[maxLength + 1];
        //3.遍历数组,按位相加
        for (int i = 0; i < result.length; i++) {
            int temp = result[i];
            temp += arrayA[i];
            temp += arrayB[i];
            //判断是否进位
            if (temp > 10){
                temp = temp - 10;
                result[i+1] = 1;
            }
            result[i] = temp;
        }
        //4.把result数组再次逆序并转成String
        StringBuilder sb = new StringBuilder();
        //是否找到大整数的最高有效位
        boolean findFirst = false;
        for (int i = 0; i < result.length; i++) {
            if (! findFirst){
                if (result[i] == 0){
                    continue;
                }
                findFirst = true;
            }
            sb.append(result[i]);
        }
        return sb.toString();
    }
    public static void main(String[] args) {
        System.out.println(bigNumberSum("426709752318", "95481253129"));
    }
}

求两个大整数的积

public class Demo {
    public static int isPowerOf2(int num) {
        return (num&num-1) == 0;
    }
    public static void main(String[] args) {
        System.out.println(isPowerOf2(1200));
    }
}

如何判断一个数是否为2的整数次幂

// 最快的方法就是采用位与运算
// 如果一个整数是2的整数次幂,那么当它转化成二进制时,只有最高位是1,其他位都是0!
// 如果2的整数次幂一旦减1,它的二进制数字就全部变成了1!
// 而0和1按位与运算的结果是0,所以凡是2的整数次幂和它本身减1的结果进行与运算,结果都必定是0。
// 反之,如果一个整数不是2的整数次幂,结果一定不是0!
// 最后,对于一个整数n,只需要计算n&(n-1)的结果是不是0。这个方法的时间复杂度只有O(1)
public class Demo {
    public static int isPowerOf2(int num) {
        return num > 0 && (num&num-1) == 0;
    }
    public static void main(String[] args) {
        System.out.println(isPowerOf2(1200));
    }
}

如何判断一个数是否为4的整数次幂

// 4的幂首先是2的幂,因为4^n = (2^2)^n;
// 先判断是否为2的幂,同样利用n & (n - 1);
// 不同是,4的幂的二进制表示中,1全奇数位上。
// 所以进一步判断其与0x55555555按位与的结果,0x55555555是十六进制表示,
// 换成二进制表示,可以发现,其奇数位上全是1,那么相与结果为true,则是4的幂,否则不是
public class Demo {
    public static int isPowerOf4(int num) {
        return num > 0 && ((num&num-1) == 0) && ((num & 0x55555555) != 0);
    }
    public static void main(String[] args) {
        System.out.println(isPowerOf4(1200));
    }
}

给你一组整型数据,这些数据中,其中有一个数只出现了一次,其他的数都出现了两次,让你来找出一个数。

// 高效的解决方法就是采用位异或运算方法
public class Demo {
    public static int mergeSort(int[] array) {
        int temp = array[0];
        for (int i = 1; i < array.length; i++) {
            temp = temp ^ array[i];
        }
        return temp;
    }
    public static void main(String[] args) {
        int[] array2 = {1,2,3,4,6,1,3,4,6};
        System.out.println(findNumber(array2));
    }
}

用多线程统计1到1000000之间有多少素数

code

给一个数组,归并排序

// 给数组进行归并排序
public class Demo {
    public static void mergeSort(int[] array, int start, int end) {
        if (start < end){
            //折半成两个小集合,分别进行递归
            int mid = (start + end)/2;
            mergeSort(array, start, mid);
            mergeSort(array, mid+1, end);
            // 把两个有序小集合,归并成一个大集合
            merge(array, start, mid, end);
        }
    }
    private static void merge(int[] array, int start, int mid, int end){
        //开辟额外大集合,设置指针
        int[] tempArray = new int[end-start+1];
        int p1 = start; //左指针
        int p2 = mid + 1; //右指针
        int p = 0; //数组临时指针
        //比较两个小集合的元素,依次放入大集合
        while (p1 <= mid && p2 <= end){
            if (array[p1] <= array[p2]){
                tempArray[p++] = array[p1++];
            }else {
                tempArray[p++] = array[p2++];
            }
        }
        //左侧小集合还有剩余,依次放入大集合尾部
        while (p1<=mid){
            tempArray[p++] = array[p1++];
        }
        //右侧小集合还有剩余,依次放入大集合尾部
        while (p2<=end){
            tempArray[p++] = array[p2++];
        }
        //把大集合的元素复制回原数组
        for (int i=0; i<tempArray.length; i++){
            array[i+start] = tempArray[i];
        }
    }
    public static void main(String[] args) {
        int[] array = {5, 8, 6, 3, 9, 2, 1, 7};
        mergeSort(array,0,array.length-1);
        System.out.println(Arrays.toString(array));
    }
}

二叉树遍历

// 通过Map.entrySet遍历key和value
public class Demo {
    /**
     * 构建二叉树(先建左树在建右树的方法)
     * @param inputList 输入序列
     * @return
     */
    public static TreeNode createBinaryTree(LinkedList<Integer> inputList){
        TreeNode node = null;
        if (inputList == null || inputList.isEmpty()){
            return null;
        }
        Integer data = inputList.removeFirst();
        if (data != null){
            node = new TreeNode(data);
            node.leftChild = createBinaryTree(inputList);
            node.rightChild = createBinaryTree(inputList);
        }
        return node;
    }
    
    /**
     * 二叉树节点
     */
    private static class TreeNode {
        int data;
        TreeNode leftChild;
        TreeNode rightChild;

        TreeNode(int data){
            this.data = data;
        }
    }
    
    /**
     * 二叉树遍历(递归)
     * @param node 二叉树节点
     */
    public static void preOrderTraveral(TreeNode node){
        if (node == null){
            return;
        }
        System.out.println(node.data); // 前序遍历打开 根-> 左-> 右
        preOrderTraveral(node.leftChild);
        //System.out.println(node.data); // 中序遍历打开 左-> 根-> 右
        preOrderTraveral(node.rightChild);
        //System.out.println(node.data); // 后序遍历打开 左-> 右-> 根 
    }
    
    /**
     * 二叉树非递归遍历
     * @param root 二叉树根节点
     */
    public static void preOrderTraveralWithStack(TreeNode root){
        Stack<TreeNode> stack = new Stack<>();
        TreeNode treeNode = root;
        while (treeNode != null || !stack.isEmpty()){ //节点不为空,结点入栈,并且指向下一个左孩子
            while (treeNode != null){
                System.out.println(treeNode.data); // 前序遍历打开
                stack.push(treeNode);
                treeNode = treeNode.leftChild;
            }
            if (!stack.isEmpty()){
                treeNode = stack.pop();
                System.out.println(treeNode.data); // 中序遍历打开
                treeNode = treeNode.rightChild;
            }
        }
    }
    
    /**
     * 二叉树非递归后序遍历
     * @param root 二叉树根节点
     */
    public static void postOrderTraveralWithStack(TreeNode root){
        Stack<TreeNode> stack = new Stack<>();
        TreeNode treeNode = root;
        TreeNode lastVisit = null;   // 标记每次遍历最后一次访问的节点
        while (treeNode != null || !stack.isEmpty()){ // 节点不为空,结点入栈,并且指向下一个左孩子
            while (treeNode != null){
                stack.push(treeNode);
                treeNode = treeNode.leftChild;
            }
            // 栈不为空
            if (!stack.isEmpty()){
                // 出栈
                treeNode = stack.pop();
                if(treeNode.rightChild == null || treeNode.rightChild == lastVisit) {
                    System.out.println(treeNode.data);
                    lastVisit = treeNode;
                    treeNode  = null;
                }else{
                    stack.push(treeNode);
                    treeNode = treeNode.rightChild;
                }
            }
        }
    }
    
    /**
     * 二叉树层序遍历
     * @param root 二叉树根节点
     */
    public static void levelOrderTraversal(TreeNode root){
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            TreeNode node = queue.poll();
            System.out.println(node.data);
            if (node.leftChild != null){
                queue.offer(node.leftChild);
            }
            if (node.rightChild != null){
                queue.offer(node.rightChild);
            }
        }
    }
    
    public static void main(String[] args) {
        LinkedList<Integer> inputList = new LinkedList<>(Arrays.asList(new Integer[]{3,2,9,null,null,10,null,null,8,null,4}));
        TreeNode treeNode = createBinaryTree(inputList);
        System.out.println(" 二叉树递归前序遍历: ");
        preOrderTraveral(treeNode);
        System.out.println(" 二叉树非递归前序遍历: ");
        preOrderTraveralWithStack(treeNode);
        System.out.println(" 二叉树层序遍历: ");
        levelOrderTraversal(treeNode);
    }
}

如何判断一个字符串中某个字符出现的次数?

// 面试官想要的答案不是for循环,而是字符串替换计算法
public class Demo {
    public static int cuntCharNum(String str, String searchChar) {
        // 字符串原始长度
        int origialLength = str.length();
        // 把要查找的字符串替换为空字符串
        str = str.replace(searchChar, "");
        // 替换后字符串的长度
        int newLength = str.length();
        // 某个字符出现的次数 = 旧字符串的长度 - 替换后字符串的长度
        retrun origialLength - newLength;
    }
    public static void main(String[] args) {
        String str = "ABC123ABC";
        String searchChar = "B";
        System.out.println("字符" + searchChar + "出现的次数为:" + cuntCharNum(str,searchChar));
    }
}

如何反转一个字符串?

// 面试官问这个问题想要的答案是可以利用数据结构中的栈,因为栈的特点是先入后出,即可实现字符串的反转
public class Demo {
    public static int myReverse(String str) {
        // 把字符串转为字符数组
        char[] charArray = str.toCharArray();
        // 定义一个栈
        Stack<Character> stack = new Stack<>();
        // 定义一个新的字符串
        StringBuilder newStr = new StringBuilder();
        // 把字符数组入栈
        for (char item : charArray) {
            stack.push(item);
        }
        // 出栈,实现字符串反转
        for (int i = 0; i < charArray.length; i++) {
            // 出栈的字符添加到新的字符串前面
            newStr.append(stack.pop());
        }
        return newStr;
    }
    public static void main(String[] args) {
        String str = "ABC123ABC";
        System.out.println("反转前:" + str);
        System.out.println("反转后:" + myReverse(str).toString());
    }
}

给定一个字符串A,字符串由所有数字和字母组成,求这个字符串里面所出现的数字和字母出现次数?

// 如 input: A="aaaBBBaaA23"...output:{A:1,B:3,a:5,2:1,3:1}
public class Demo {
    public static int charCount(String str) {
        StringBuilder cuntStr = new StringBuilder();
        while (str.length()>0){
            // 字符串的长度
            int strLength = str.length();
            // 获取字符串的第一个字符
            char at = str.charAt(0);
            // 字符串里的 at 字符替换为空字符串""
            str = str.replace(""+ at +"", "");
            // 计算字符出现的次数
            int cunt = strLength - str.length();
            // 添加到返回的字符串里面
            cuntStr.append(","+ at +":" + cunt);
        }
        String result = "{" + cuntStr.substring(1) + "}";
        return result;
    }
    public static void main(String[] args) {
        System.out.println(charCount("aaaBBBaaA23"));
    }
}

高效遍历 HashMap

// 通过Map.entrySet遍历key和value
public class Demo {
    public static void hforMap(Map<String,String> map) {
        for(Map.Entry<String, String> entry: map.entrySet())
        {
         System.out.println("Key: "+ entry.getKey()+ " Value: "+entry.getValue());
        }
    }
    public static void main(String[] args) {
        Map<String,String> map=new HashMap<String,String>();
        map.put("1", "value1");
        map.put("2", "value2");
        map.put("3", "value3");
        map.put("4", "value4");
        hforMap(map);
    }
}

发红包算法(二倍均值法、线段切割法)

// 金额参数以分为单位
public class Demo {
    /**
     * 二倍均值法
     * @param totalAmount 红包金额
     * @param totalPeopleNum 红包个数
     * @return
     */
    public static List<Integer> divideRedPackage(Integer totalAmount, Integer totalPeopleNum) {
        // 红包发放数组
        List<Integer> amountList = new ArrayList<Integer>();
        // 剩余红包金额
        Integer restAmount = totalAmount;
        // 红包个数
        Integer restPeopleNum = totalPeopleNum;
        // 随机数初始化
        Random random = new Random();
        for (int i = 0; i < totalPeopleNum - 1; i++) {
			// 随机范围:[1,剩余人均金额的两倍),左闭右开
			int amount = random.nextInt(restAmount / restPeopleNum * 2 - 1) + 1;
			// 计算剩余红包金额
			restAmount -= amount;
			// 计算剩余红包个数
			restPeopleNum--;
			// 红包金额放入红包发放数组
			amountList.add(amount);
		}
		amountList.add(restAmount);
		return amountList;
    }
    
    /**
     * 线段切割法
     * @param totalAmount 红包金额
     * @param totalPeopleNum 红包个数
     * @return
     */
    public static List<Integer> lineCuttingRedPackage(Integer totalAmount, Integer totalPeopleNum){
        // 红包发放数组
        List<Integer> amountList = new ArrayList<>();
        // 切节点数组
        List<Integer> list = new ArrayList<>();
        // 随机数初始化
        Random random = new Random();
        while (list.size()<=totalPeopleNum-1) {
            int i=random.nextInt(totalAmount)+1;//最低1分钱
            if(list.indexOf(i)<0){//非重复切割添加到集合
                list.add(i);
            }
        }
        // 切点排序:由小到大
        Collections.sort(list);
        // 上一个切点值
        int  flag = 0;
        // 累计切断的长度
        int sumAmount = 0;
        // 开始切
        for (int i=0;i<list.size();i++) {
            // 当前红包金额 = 当前切点值 - 上一个切点值
            int amount=list.get(i)-flag;
            flag = list.get(i);
            sumAmount += amount;
            amountList.add(amount);
        }
        // 最后一个红包
        amountList.add(totalAmount-sumAmount);
        return amountList;
    }
    
    public static void main(String[] args) {
        List<Integer> amountList = divideRedPackage(5000, 30);
        for (Integer amount : amountList) {
            // 由分转换为元
            BigDecimal decimal = new BigDecimal(amount).divide(new BigDecimal(100));
            System.out.println("抢到金额:" + decimal);
        }
        List<Integer> list = lineCuttingRedPackage(5000, 30);
        for (Integer amount : list) {
            // 由分转换为元
            BigDecimal decimal = new BigDecimal(amount).divide(new BigDecimal(100));
            System.out.println("抢到金额:" + decimal);
        }
    }
}

动态规划解决扔鸡蛋问题

// 换句话说,有M层楼 / N个鸡蛋,要找到鸡蛋摔不碎的临界点,需要尝试几次?
public class Demo {
     public static int getMinSteps(int eggNum, int floorNum) {
        if (eggNum < 1 || floorNum < 1){
            return 0;
        }
        //上一层备忘录,存储鸡蛋数量-1的floorNum层楼条件下的最优化尝试次数
        int [] preCache = new int[eggNum+1];
        //当前备忘录,存储当前鸡蛋数量的floorNum层楼条件下的最优化尝试次数
        int [] currentCache = new int[floorNum+1];
        //把备忘录每个元素初始化成最大的尝试次数
        for (int i = 0; i <= floorNum; i++) {
            currentCache[i] = i;
        }
        for (int n = 2; n <= eggNum; n++) {
            // 当前备忘录拷贝给上一次备忘录,并重新初始化当前备忘录
            preCache = currentCache.clone();
            for (int i = 1; i <= floorNum; i++) {
                currentCache[i] = i;
            }
            for (int m = 1; m <= floorNum; m++) {
                for (int k = 1; k < m; k++) {
                    // 扔鸡蛋的楼层从1到m枚举一遍,如果当前算出的尝试次数小于上一次算出的尝试次数,则取代上一次的尝试次数。
                    // 这里可以打印k的值,从而知道第一个鸡蛋是从第几次扔的。
                    currentCache[m] =Math.min(currentCache[m], 1+Math.max(preCache[k-1],currentCache[m-k]));
                }
            }
        }
        return currentCache[floorNum];
    }
    
    public static void main(String[] args) {
        System.out.println(getMinSteps(1,600));
    }
}

去掉一个数,如何让剩余的数乘积最大

public class Demo {
    /**
     * 去掉一个数,如何让剩余的数乘积最大
     * @param array
     */
    public static int findRemovedIndex(int[] array) {
        // 1.统计负元素的个数
        int negativeCount = 0;
        for (int i = 0; i < array.length; i++) {
            if (array[i] < 0){
                negativeCount ++;
            }
        }
        // 2.根据不同情况,选择要删除的元素
        int tempIndex = 0;
        if ((negativeCount&1) == 1){
            //情况A:负数个数是奇数, 则删除最大负数
            for (int i = 1; i < array.length; i++) {
                if (array[i] <0){
                    if (array[tempIndex] >= 0 || array[i] > array[tempIndex]){
                        tempIndex = i;
                    }
                }
            }
            return tempIndex;
        }else {
            //情况B:负数个数是偶数
            if (negativeCount == array.length){
                //子情况:所有元素都是负数,这删除最小的负数
                for (int i = 1; i < array.length; i++) {
                    if (array[i] < array[tempIndex]){
                        tempIndex = i;
                    }
                }
                return tempIndex;
            }
            for (int i = 1; i < array.length; i++) {
                if (array[i]>=0){
                    if (array[tempIndex]<0 || array[i]<array[tempIndex]){
                        tempIndex = i;
                    }
                }
            }
            return tempIndex;
        }
    }
    public static void main(String[] args) {
        int [] array1 = {-4,3,-5,-7,-21,9,-1,5,6};
        int index = findRemovedIndex( array1);
        System.out.println("删除元素下标:"+array1[index]);
        int []array2={4,3,5,-7,-21,9,-1,-5,6,0};
        index =findRemovedIndex(array2);
        System.out.println("删除元素下标:"+array2[index]);
        int[]array3={-4,-3,-5,-7,-21,-9,-1,-8};
        index=findRemovedIndex(array3);
        System.out.println("删除元素下标:"+array3[index]);
    }
}

选择排序

public class Demo {
    /**
     * 选择排序(每次只找出一个最小值或最大值)是个不稳定排序
     * @param array
     * @return
     */
    public static void selectionSort(int[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            // 每次只找出一个最小的值进行交换位置
            for (int j = i+1; j < array.length; j++) {
                // 初始最小值下标索引
                int minIndex = i;
                if (array[i] > array[j]){
                    minIndex = j;
                }
                // 如果最小值下标索引与初始值不同,则需要进行数据交换位置
                if (minIndex != i){
                    int temp = array[i];
                    array[i] = array[j];
                    array[j] = temp;
                }
            }
        }
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,6,1,3,4,6};
        selectionSort(array);
        System.out.println(Arrays.toString(array));
    }
}

插入排序

public class Demo {
    public static void insertSort(int[] array) {
        for (int i = 1; i < array.length; i++) {
            int insertValue = array[i];
            int j = i -1;
            // 从右向左比较元素的同时,进行元素复制
            for (; j>0 &&array[j]>insertValue; j--) {
                array[j+1] = array[j];
            }
            // insertValue的值插入适当的位置
            array[j+1] = insertValue;
        }
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,6,1,3,4,6};
        insertSort(array);
        System.out.println(Arrays.toString(array2));
    }
}

冒泡排序

public class Demo {
    public static void main(String[] args) {
        int[] array = new int[] {4,4,6,5,3,2,8,1,7,5,6,0,10};
        bubbleSort(array);
        System.out.println(Arrays.toString(array));
    }
    public static void bubbleSort(int[] array) {
        //临时变量
        int temp = 0;
        // 记录最后一次交换的位置
        int lastExchangIndex = 0;
        // 无序数列的边界,每次比较只需要比到这里为止
        int sortBorder = array.length - 1;
        for (int i = 0; i < array.length; i++) {
            // 有序标记,每一轮的初始是true
            boolean isSorted = true;
            for (int j = 0; j < sortBorder; j++) {                
                if (array[j] > array[j+1]){
                    // 从小到大排序
                    temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;
                    // 有元素交换,所以不是有序,标记变为false
                    isSorted = false;
                    // 把无序数列的边界更新为最后一次交换元素的位置
                    lastExchangIndex = j;
                }
            }
            sortBorder = lastExchangIndex;
            if (isSorted){
                break;
            }
        }
    }
}

快速排序

public class Demo {
    public static void main(String[] args) {
        int[] array = new int[] {11,12,4,4,6,5,3,2,8,1,7,5,6,0,10};
        quickSort(array, 0, array.length-1);
        System.out.println(Arrays.toString(array));
    }
    /**
     * 快速排序
     * @param array
     * @param L 指向数组第一个元素
     * @param R 指向数组最后一个元素
     */
    public static void quickSort(int[] array, int L, int R) {
        // 递归结束条件 L 大于等于 R 的时候
        if (L >= R){
            return;
        }
        int i = L;
        int j = R;
        // 支点元素
        int pivot = array[(L + R)/2];
        // 左右两端进行扫描,只要还没有交替,就一直扫描
        while (i <= j){
            // 寻找直到比支点大的数
            while (pivot > array[i]){
                i++;
            }
            // 寻找直到比支点小的数
            while (pivot < array[j]){
                j--;
            }
            // 此时已经分别找到了比支点小的数、比支点大的数,它们进行交换
            if (i <= j){
                int temp = array[i];
                array[i] = array[j];
                array[j] = temp;
                i++;
                j--;
            }
            // 左边再做排序,直到左边剩下一个数
            if (L < j){
                quickSort(array, L, j);
            }
            // 右边再做排序,直到右边剩下一个数
            if (i < R){
                quickSort(array,i,R);
            }
        }
    }
}

希尔排序

public class Demo {
    public static void main(String[] args) {
        int[] array = new int[] {11,12,4,4,6,5,3,2,8,1,7,5,6,0,10};
        shellSort(array);
        System.out.println(Arrays.toString(array));
    }
    /**
     * 希尔排序
     * @param array 待排序数组
     */
    public static void shellSort(int[] array) {
        // 增量每次都除于2
        for (int step = array.length/2; step > 0; step/=2) {
            // 从增量那组开始进行插入排序,直至完毕
            for (int i = step; i < array.length; i++) {
                int j = i;
                int temp = array[j];
                // j - step 就是代表与它同组隔壁的元素
                while (j-step>=0 && array[j - step] > temp){
                    array[j] = array[j - step];
                    j = j - step;
                }
                array[j] = temp;
            }
        }
    }
}

计数排序

public class Demo {
    public static int[] countSort(int[] array) {
        //1.获取数列的最大值
        int max = array[0];
        for(int i=1; i<array.length; i++){
            if(array[i] > max){
                max = array[i];
            }
        }
        //2.根据数列最大值确定统计数组的长度
        int[] countArray = new int[max+1];
        //3.遍历数列,填充统计数组
        for(int i=0; i<array.length; i++){
            countArray[array[i]]++;
        }
        //4.遍历统计数组,输出结果
        int index = 0;
        int[] sortedArray = new int[array.length];
        for(int i=0; i<countArray.length; i++){
            for(int j=0; j<countArray[i]; j++){
                sortedArray[index++] = i;
            }
        }
        return sortedArray;
    }
    public static void main(String[] args) {
        int[] array = new int[] {4,4,6,5,3,2,8,1,7,5,6,0,10};
        System.out.println(Arrays.toString(countSort(array)));
    }
}

基数排序

public class Demo {
    //ascii码的取值范围
    public static final int ASCII_RANGE = 128;
    public static String[] redixSort(String[] array, int maxLength) {
        // 排序结果数组,用于存储每一次按位排序的临时结果
        String[] sortArray = new String[array.length];
        // 从个位开始比较,一直比较到最高位
        for (int k = maxLength-1; k>=0 ; k--) {
            //计数排序的过程,分成三步:
            //1.创建辅助排序的统计数组,并把待排序的字符对号入座,
            //这里为了代码简洁,直接使用ascii码范围作为数组长度
            int [] count = new int [ASCII_RANGE];
            for (int i = 0; i < array.length; i++) {
                int index = getCharIndex(array[i],k);
                count[index]++ ;
            }
            // 2.统计数组做变形,后面的元素等于前面的元素之和
            for (int i = 1; i < count.length; i++) {
                count[i] = count[i] + count[i-1];
            }
            // 3.倒序遍历原始数列,从统计数组找到正确位置,输出到结果数组
            for (int i = array.length - 1; i >=0 ; i--) {
                int index = getCharIndex(array[i], k);
                int sortedIndex = count[index] - 1;
                sortArray[sortedIndex] = array[i];
                count[index]--;
            }
            // 下一轮排序需要以上一轮的排序结果为基础,因此把结果复制给array
            array = sortArray.clone();
        }
        return array;
    }
    // 获取字符串第k位字符所对应的ascii码序号
    private static int getCharIndex(String str, int k) {
        // 如果字符串长度小于k,直接返回0,相当于给不存在的位置补0
        if (str.length() < k+1){
            return 0;
        }
        return str.charAt(k);
    }
    public static void main(String[] args) {
        String[] array = {"qd","abc","qwe","hhh","a","cwe","ope"};
        System.out.println(Arrays.toString(redixSort(array,3)));
    }
}

堆排序

/**
 * 1.把无序数组构建成二叉堆
 * 2.循环删除堆顶元素,移到集合尾部,调节堆产生新的堆顶
 */
public class Demo {
    public static void main(String[] args) {
        int[] array = new int[] {11,12,4,4,6,5,3,2,8,1,7,5,6,0,10};
        heapSort(array);
        System.out.println(Arrays.toString(array));
    }
    /**
     * 堆排序
     * @param array 待调整的堆
     */
    public static void heapSort(int[] array) {
        // 把无序数组构建成二叉堆
        for (int i = (array.length-2)/2; i >= 0; i--) {
            downAdjust(array, i, array.length);
        }
        for (int i = array.length - 1; i > 0; i--) {
            // 最后一个元素和第一个元素进行交换
            int temp = array[i];
            array[i] = array[0];
            array[0] = temp;
            // 下沉调整最大堆
            downAdjust(array,0, i);
        }
    }

    /**
     * 下沉调整
     * @param array 待调整的堆
     * @param parentIndex 要下沉的父节点
     * @param length 堆的有效大小
     */
    private static void downAdjust(int[] array, int parentIndex, int length) {
        // temp保存父节点值,用于最后的赋值
        int temp = array[parentIndex];
        int childIndex = 2*parentIndex + 1;
        while (childIndex < length){
            // 如果有右孩子,且右孩子大于左孩子的值,则定位到右孩子
            if (childIndex + 1 < length && array[childIndex + 1] > array[childIndex]){
                childIndex++;
            }
            // 如果父节点小于任何一个孩子的值,直接跳出
            if (temp >= array[childIndex]){
                break;
            }
            // 无需真正交换,单向赋值即可
            array[parentIndex] = array[childIndex];
            parentIndex = childIndex;
            childIndex = 2*childIndex + 1;
        }
        array[parentIndex] = temp;
    }
}

桶排序

public class Demo {
    public static void main(String[] args) {
        int[] array = new int[] {11,12,4,4,6,5,3,2,8,1,7,5,6,0,10};
        int[] sortedArray = bucketSort(array);
        System.out.println(Arrays.toString(sortedArray));
    }
    /**
     * 桶排序
     * @param array 待排序数组
     */
    public static int[] bucketSort(int[] array) {
        // 1.得到数列的最大值和最小值,并算出差值d
        int max = array[0];
        int min = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i] > max){
                max = array[i];
            }
            if (array[i] < min){
                min = array[i];
            }
        }
        int d = max - min;
        // 2.初始化桶
        int bucketNum = array.length;
        ArrayList<LinkedList<Integer>> bucketList = new ArrayList<>(bucketNum);
        for (int i = 0; i < bucketNum; i++) {
            bucketList.add(new LinkedList<Integer>());
        }
        // 3.遍历原始数组,将每个元素放入桶中
        for (int i = 0; i < array.length; i++) {
            int num = ((array[i] - min)) * ((bucketNum - 1) / d);
            bucketList.get(num).add(array[i]);
        }
        // 4.对每个桶内部进行排序
        for (int i = 0; i < bucketList.size(); i++) {
            // jdk底层采用了归并排序或归并排序的优化版本
            Collections.sort(bucketList.get(i));
        }
        // 5.输出全部元素
        int[] sortedArray = new int[array.length];
        int index = 0;
        for (LinkedList<Integer> list: bucketList) {
            for (int item: list) {
                sortedArray[index] = item;
                index++;
            }
        }
        return sortedArray;
    }
}

给定一个字符串,找出不含有重复字符的最长字串

public class Demo {
    public static int getLongSubString(String str) {
        int n = str.length();
        int res = 0;
        int end=0,start=0;
        Set<Character> set=new HashSet<>();
        while(start<n && end<n){
            if(set.contains(str.charAt(end))){
                set.remove(str.charAt(start++));
            }else{
                set.add(str.charAt(end++));
                res=Math.max(res,end-start);
            }
        }
        return res;
    }
    public static void main(String[] args) {
        System.out.println(getLongSubString("abcabcbb"));
    }
}

List如何一边遍历,一边删除

public class Demo {
    public static void main(String[] args) {
        List<String> platformList = new ArrayList<>();
        platformList.add("博客园");
        platformList.add("CSDN");
        platformList.add("掘金");
        delete1(platformList);
        System.out.println(platformList);
    }
    // 推荐使用(阿里)
    public static void delete1(List<String> list) {
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String platform = iterator.next();
            if (platform.equals("博客园")) {
                iterator.remove();
            }
        }
    }
    // for 循环正序遍历删除元素后,需要修正下标值
    public static void delete2(List<String> list) {
        for (int i = 0; i < list.size(); i++) {
            String item = list.get(i);
            if (item.equals("博客园")) {
                list.remove(i);
                // 删除元素后,要修正下下标的值
                i = i - 1;
            }
        }
    }
    // for 循环倒序遍历删除元素后,不需要修正下标值
    public static void delete3(List<String> list) {
        for (int i = platformList.size() - 1; i >= 0; i--) {
            String item = platformList.get(i);
            if (item.equals("掘金")) {
                platformList.remove(i);
            }
        }
    }
}

找出数组中的第K大元素

public class Demo {
    public static void main(String[] args) {
        int[] array = new int[] {11,12,4,4,6,5,3,2,8,1,7,5,6,0,10};
        System.out.println(findKthLargest(array, 3));
    }
    /**
     * 数组中的第K大元素
     * @param array 所有源数据
     * @param k 第几大元素
     * @return
     */
    public static int findKthLargest(int[] array, int k) {
        // PriorityQueue实现了数据结构堆
        // 定义一个优先队列,优先队列中元素默认排列顺序是升序排列
        PriorityQueue<Integer> minQueue = new PriorityQueue<>();
        // 扫描完所有元素
        for (int num: array){
            // 若大于堆顶,则入堆
            if (minQueue.size() < k || num > minQueue.peek()){
                // 通过offer添加元素可以排序
                minQueue.offer(num);
            }
            // 维护堆最小值 k
            if (minQueue.size()>k){
                // 删除堆顶
                minQueue.poll();
            }
        }
        // 返回堆顶, 即第K大元素
        return minQueue.peek();
    }
}

读取txt数据(大数据处理)

/**
 * TXT文本中如据形如:
 * 123|457|12|124
 * 456|457|12|117
 * 789
 */
public class Demo {
    public static void main(String[] args) {
        readTxtData("h:/20200323.txt");
    }
    /**
     * 读取TXT数据
     * @param filePath 文件路径
     */
    public static void readTxtData(String filePath) {
        // 定义存放数据的容器
        List<Integer> list = new ArrayList();
        try {
            FileInputStream fileInputStream = new FileInputStream(new File(filePath));
            InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
            BufferedReader br= new BufferedReader(inputStreamReader);
            String str=null;
            // 读取一行数据
            while((str=br.readLine())!=null)
            {
                // 数据处理,并根据"|"分割取数据
                dealData(str,"\\|",list);
            }
            inputStreamReader.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(list);
    }

    /**
     * 数据处理
     * @param str 待处理的数据
     * @param regex 分割符
     * @param list 存放数据的容器
     */
    private static void dealData(String str, String regex, List<Integer> list) {
        // 判断是否要根据数据分隔符拆分数据
        if (regex == null || regex.equals("")){
            // 把数据放入数组
            list.add(Integer.valueOf(str));
        }else {            
            // 根据数据分隔符拆分数据
            String[] strings = str.split(regex);
            for (String s: strings) {
                // 把数据放入数组
                list.add(Integer.valueOf(s));
            }
        }
    }
}

10亿个数字里里面找最小的10个

// 利用最小堆获取TopK
public class TopK {
    public static void main(String[] args) {
        // 源数据
        int[] data = {56,275,12,6,45,478,41,1236,456,12,546,45};
        // 获取Top5
        int[] top5 = topK(data, 5);
        for(int i=0;i<5;i++) {
            System.out.println(top5[i]);
        }
    }

    // 从data数组中获取最大的k个数
    private static int[] topK(int[] data,int k) {
        // 先取K个元素放入一个数组topk中
        int[] topk = new int[k]; 
        for(int i = 0;i< k;i++) {
            topk[i] = data[i];
        }
        // 转换成最小堆
        MinHeap heap = new MinHeap(topk);
        // 从k开始,遍历data
        for(int i= k;i<data.length;i++) {
            int root = heap.getRoot();
            // 当数据大于堆中最小的数(根节点)时,替换堆中的根节点,再转换成堆
            if(data[i] > root) {
                heap.setRoot(data[i]);
            }
        }
        return topk;
    }
}

// 最小堆的实现
public class MinHeap{
    // 堆的存储结构 - 数组
    private int[] data;

    // 将一个数组传入构造方法,并转换成一个小根堆
    public MinHeap(int[] data) {
        this.data = data;
        buildHeap();
    }

    // 将数组转换成最小堆
    private void buildHeap() {
        // 完全二叉树只有数组下标小于或等于 (data.length) / 2 - 1 的元素有孩子结点,遍历这些结点。
        // *比如上面的图中,数组有10个元素, (data.length) / 2 - 1的值为4,a[4]有孩子结点,但a[5]没有*
        for (int i = (data.length) / 2 - 1; i >= 0; i--)  {
            // 对有孩子结点的元素heapify
            heapify(i);
        }
    }

    private void heapify(int i) {
        // 获取左右结点的数组下标
        int l = left(i);  
        int r = right(i);

        // 这是一个临时变量,表示 跟结点、左结点、右结点中最小的值的结点的下标
        int smallest = i;

        // 存在左结点,且左结点的值小于根结点的值
        if (l < data.length && data[l] < data[i]) { 
            smallest = l;  
        }
        
        // 存在右结点,且右结点的值小于以上比较的较小值
        if (r < data.length && data[r] < data[smallest]) { 
            smallest = r;  
        }
        
        // 左右结点的值都大于根节点,直接return,不做任何操作
        if (i == smallest){  
            return;
        }  

        // 交换根节点和左右结点中最小的那个值,把根节点的值替换下去
        swap(i, smallest);

        // 由于替换后左右子树会被影响,所以要对受影响的子树再进行heapify
        heapify(smallest);
    }

    // 获取右结点的数组下标
    private int right(int i) {  
        return (i + 1) << 1;  
    }   

    // 获取左结点的数组下标
    private int left(int i) {  
        return ((i + 1) << 1) - 1;  
    }

    // 交换元素位置
    private void swap(int i, int j) {  
        int tmp = data[i];  
        data[i] = data[j];  
        data[j] = tmp;  
    }

    // 获取对中的最小的元素,根元素
    public int getRoot() {
            return data[0];
    }

    // 替换根元素,并重新heapify
    public void setRoot(int root) {
        data[0] = root;
        heapify(0);
    }
}

打印杨辉三角

public class Demo {
    public static void main(String[] args) {
        List<List<Integer>> lists = generateTriangleByRecursive(70);
        lists.forEach(item -> System.out.println(item));
        // printTriangle(lists);
    }
    /**
     * 构建杨辉三角
     * @param numRow 杨辉三角行数
     * @return
     */
    public static List<List<Integer>> generateTriangleByRecursive(int numRow) {
        // 存放杨辉三角数据的容器(二维数组)
        List<List<Integer>> list = new ArrayList<>();
        // 备忘录
        Map<Integer, Map<Integer, Integer>> cacheMap = new HashMap<>();
        for (int i = 0; i < numRow; i++) {
            // 存放杨辉三角每一行数据的容器
            List<Integer> subList = new ArrayList<>();
            for (int j = 0; j < i + 1; j++) {
                // 把数据添加到容器中
                subList.add(generateTriangleByRecursive(i, j, cacheMap));
            }
            // 把行数据容器添加到杨辉三角的容器中
            list.add(subList);
        }
        return list;
    }
    /**
     *
     * @param i 第几行
     * @param j 第几列
     * @param cacheMap 备忘录
     * @return
     */
    private static int generateTriangleByRecursive(int i, int j, Map<Integer, Map<Integer, Integer>> cacheMap) {
        // 备忘录是否存在该坐标值
        if (cacheMap.containsKey(i) && cacheMap.get(i).containsKey(j)) {
            return cacheMap.get(i).get(j);
        }
        int result;
        // 递归约束条件(是否是每行的第一个或最后一个数)
        if (j == 0 || j == i) {
            result = 1;
        } else {
            //计算当前坐标值 f(x,y) = f(x-1,y-1) + f(x-1,y)
            result = (generateTriangleByRecursive(i - 1, j - 1, cacheMap)
                    + generateTriangleByRecursive(i - 1, j, cacheMap));
        }
        // 判断备忘录是否存在当前行数据备忘录
        if (!cacheMap.containsKey(i)) {
            Map<Integer, Integer> currentRowCache = new HashMap<>();
            cacheMap.put(i, currentRowCache);
        }
        // 当前值添加到备忘录
        cacheMap.get(i).put(j, result);
        return result;
    }
     /**
     * 打印杨辉三角
     * @param lists 待打印的杨辉三角数据(二维数组)
     */
    private static void printTriangle(List<List<Integer>> lists) {
        // 杨辉三角高度
        int numRow = lists.size();
        for (int i = 0; i < numRow; i++) {
            // 左边打印空格,打印等腰三角形
            for (int j = 0; j < numRow-i; j++) {
                System.out.print(" ");
            }
            for (int j = 0; j < lists.get(i).size(); j++) {
                // 打印杨辉三角每一行的数据
                System.out.print(lists.get(i).get(j) + " ");
            }
            // 换行
            System.out.println();
        }
    }
}