剑指Offer-算法题

138 阅读9分钟

剑指Offer算法题

小明想要换工作,但是又懒得去整理算法题,于是他找到了我。我这次打算将剑指offer中的算法题来进行一次梳理,主要还顺便可以复习和巩固。之后等全部更新完会整理成gitbook。

字符串

  1. 替换空格

请实现一个函数,把字符串中的每个空格替换成"%20"。例如输入“We are happy.”,则输出“We%20are%20happy.”。

这个题主要有两种思路,一种是正则写法,还有一种就是replace方法。

public class 替换空格 {

    public static void main(String[] args) {
        String str = "We are happy.";
        System.out.println(getResult(str));
    }

    private static String getResult(String str) {
        String res;
        if (str == null) {
            return null;
        }
//        res = str.replaceAll(" ", "20%");
        // 正则替换
        res = str.replaceAll("\\s+?", "20%");
        return res;
    }

}
  1. 字符串全排列

输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串 abc,则打印出由字符 a、b、c 所能排列出来的所有字符串 abc、acb、bac、bca、cab 和 cba。

这个题主要思路是将该字符串划分为两部分,第一部分是第一个元素,剩下的作为第二部分。所有的字符串都要与当前集合的第一个元素进行交换,这也就是一种情况。比如abc/acb和bac/bca等。然后进行了一次交换后,又可以分为两个部分,这接下来就是递归的思想了。

public class 字符串全排列 {

    public static void main(String[] args) {
        String str = "ab";
        String str1 = "abc";
        int index = 0;
        char[] cArr = str.toCharArray();
        getResult(cArr, index);
    }

    private static void getResult(char[] arr, int index) {
        if (arr == null || arr.length == index) {
            for (int i = 0; i < arr.length; i++) {
                System.out.print(arr[i]);
                if ((i + 1) % arr.length == 0) {
                    System.out.println();
                }
            }
            return;
        }
        for (int i = index; i < arr.length; i++) {
            if (!checkSame(arr, index, i)) {
                swap(arr, index, i);
                getResult(arr, index + 1);
                swap(arr, index, i);
            }
        }
    }

    private static void swap(char[] arr, int index, int i) {
        char temp = arr[index];
        arr[index] = arr[i];
        arr[i] = temp;
    }

    /**
     * 查询是否有和arr[i]相等的字符
     */
    private static boolean checkSame(char[] arr, int index, int i) {
        for (int j = index; j < i; j++) {
            if (arr[i] == arr[index]) {
                return true;
            }
        }
        return false;
    }

}
  1. 反转单词顺序

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。例如输入字符串"I am a student.",则输出"student. a am I"。注:标点符号和普通字母一样处理。

这个题只需要将句子按空格切分,然后再倒序输出即可。

public class 反转单词顺序 {

    public static void main(String[] args) {
        String str = "I am a student.";
        System.out.println(getResult(str));
    }

    private static String getResult(String str) {
        if (str == null || str.length() <= 0) {
            return null;
        }
        String[] arr = str.split(" ");
        StringBuilder res = new StringBuilder();
        for (int i = arr.length - 1; i > 0; i--) {
            res.append(arr[i]).append(" ");
        }
        res.append(arr[0]);
        return res.toString();
    }

}
  1. 字符串转整数

请你来实现一个 atoi 函数,使其能将字符串转换成整数

这个题不是很复杂,只需要把问题考虑全面即可。

  1. 找出第一个非空字符,判断是不是正负号或者数字
  2. 如果是正负号,那么先判断正负号
  3. 再判断符号后面是否是数字,如果不是则非法,返回 -1
  4. 确定连续数字字符的长度
  5. 计算数字字符的代表的数字大小,并且判断是否越界
public class 字符串转整数 {
    
    public static void main(String[] args) {
        String str = "-1234";
        String str1 = "-1a234";
        System.out.println(getResult(str));
        System.out.println(getResult(str1));
    }

    private static int getResult(String str) {
        if (str == null || str.length() == 0) {
            return -1;
        }
        int result = 0;
        char[] chs = str.toCharArray();
        int len = chs.length;
        for (int i = len - 1, j = 0; i > 0; i--, j++) {
            int c = (int) chs[i];
            if (c < 48 || c > 57) {
                return -1;
            } else {
                result += (c - 48) * Math.pow(10, j);
            }
        }
        int c = (int) chs[0];
        if (c <= 57 && c >= 48) {
            result += (c - 48) * Math.pow(10, len - 1);
        }
        if (result < Integer.MIN_VALUE || result > Integer.MAX_VALUE) {
            return -1;     //越界,如果真的越界,直接会报错,result本身没办法越界
        } else if (str.equals("2147483648")) {
            if (c == 45) {
                result = -2147483648;     //边界值
            }
        } else if (str.equals("-2147483648")) {
            result = -2147483648;         //边界值
        } else {
            if (c == 45) {
                result = -result;         //负号处理
            }
        }
        return result;
    }
}

数组

  1. 求最大连续子数组的和。

输入一个整型数组,数组中有正数也有负数。数组中一个或多个整数形成一个子数组,求所有连续子数组的和的最大值,要求时间复杂度为 O(n)。 比如输入 {1, -2, 3, 10, -4, 7, 2, -5},能产生子数组最大和的子数组为 {3,10,-4,7,2},最大和为 18。

O(n)的时间复杂度就决定了我们只能遍历一次,大致思想就是如果sum小于0,则直接将sum赋值为arr[i],否则就加上arr[i]。最后再与返回值进行比较即可。具体的逻辑叙述比较麻烦,可以跟着下面的代码走一遍就可以理解了。

public class 最大连续子数组的和 {
    public static void main(String[] args) {
        int[] arr = {1, -2, 3, 10, -4, 7, 2, -5};
        System.out.println(getResult(arr));
    }

    private static int getResult(int[] arr) {
        if (arr == null || arr.length <= 0) {
            return -1;
        }
        int res = Integer.MIN_VALUE;
        //sum保存当前累积和
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            // 如果小于 0,则直接说明不可能是从前面开始的。不加之前的值,直接算当前值
            if (sum < 0) {
                sum = arr[i];
            } else {
                // 如果大于 0,则相加
                sum += arr[i];
            }
            // 如果添加后的值大于之前存放的最大值,则更新最大值
            if (res < sum) {
                res = sum;
            }
        }
        return res;
    }
}
  1. 旋转数组中的最小数

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为 1。

这个题有两种解法,第一种就是遍历一遍找到最小的数,时间复杂度是O(n),另一种解法就是利用二分的思想,时间复杂度为O(logn)。实例代码为后一种方法。第二种方法的原理可以借助快速排序来进行理解,主要需要注意的是该旋转数组是一个递增排序的数组。

public class 旋转数组中最小的数 {

    public static void main(String[] args) {
        int[] arr = {4, 5, 6, 2, 3};
        System.out.println(getResult(arr));
    }

    private static int getResult(int[] arr) {
        if (arr == null || arr.length <= 0) {
            return -1;
        }
        if (arr.length == 1) {
            return arr[0];
        }
        int pre = 0, end = arr.length - 1, mid;
        while (arr[pre] >= arr[end]) {
            if (end - pre == 1) {
                return arr[end];
            }
            mid = pre + (end - pre) / 2;
            if (arr[mid] >= arr[pre]) {
                pre = mid;
            } else {
                end = mid;
            }

        }
        return -1;
    }
}
  1. 数字在排序数组中出现的次数

统计一个数字在排序数组中出现的次数。

因为数组是有序,因而只需要找到第一个和最后一个等于该数的位置做差即可。当然若是无序数组直接使用HashMap即可,时间复杂度为O(n);

public class 数字在排序数组中出现的次数 {

    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 3, 3, 3, 4, 5};
        int target = 3;
        System.out.println(getResult(arr, target));
    }

    private static int getResult(int[] arr, int target) {
        if (arr == null || arr.length <= 0) {
            return -1;
        }
        int left = searchLeft(arr, target);
        int right = searchRight(arr, target);
        if (left == -1 || right == -1) { //表示不存在
            return -1;
        }
        return right - left + 1;
    }

    /**
     * 从右向左找第一个
     */
    private static int searchRight(int[] arr, int target) {
        for (int i = arr.length - 1; i >= 0; i--) {
            if (arr[i] == target) {
                return i;
            }
        }
        return -1;
    }

    private static int searchLeft(int[] arr, int target) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == target) {
                return i;
            }
        }
        return -1;
    }
    
}
  1. 二维数组的查找

在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

利用好该数组的有序性进行遍历即可。

public class 二维数组的查找 {

    public static void main(String[] args) {
        int[][] arr = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}};
        int target = 18;
        System.out.println(getResult(arr, target) ? "存在" : "不存在");
    }

    private static boolean getResult(int[][] arr, int target) {
        if (arr == null || arr.length <= 0) {
            return false;
        }
        int p = 0, q = arr[0].length - 1;
        while (p < arr.length && q >= 0) {
            if (arr[p][q] == target) {
                return true;
            }

            if (arr[p][q] < target) {
                p++;
            } else {
                q--;
            }

        }
        return false;
    }

}
  1. 数组顺序调整

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

这题进一步抽象就是满足一定条件的元素都移动到数组的前面,不满足的移动到后面。所以,需要有一个参数用来传递判断函数。最优解法就是数组两头分别有一个指针,然后向中间靠拢。符合条件,就一直向中间移动;不符合条件,就停下来指针,交换两个元素;然后继续移动,直到两个指针相遇。

public class 数组顺序调整 {

    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 6, 5};
        getResult(arr);

        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
    }

    private static void getResult(int[] arr) {
        if (arr == null || arr.length <= 0) {
            return;
        }
        int p = 0, q = arr.length - 1;
        while (p < q) {
            if (arr[p] % 2 != 0) {//找到一个偶数
                p++;
            }
            if (arr[q] % 2 != 1) {//找到一个奇数
                q--;
            }
            if (arr[p] % 2 == 0 && arr[q] % 2 == 1) {
                swap(arr, p, q);
                p++;
                q--;
            }
        }
    }

    private static void swap(int[] arr, int p, int q) {
        int temp = arr[p];
        arr[p] = arr[q];
        arr[q] = temp;
    }

}
  1. 把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为 321323。

因为涉及拼接,所以可以将其看做字符串,同时规避了大数溢出的问题,而且字符串的比较规则和数字相同。借助自定义排序,可以快速比较两个数的大小。比如只看{3, 32}这两个数字。它们可以拼接成 332 和 323,按照题目要求,这里应该取 323。也就是说,此处自定义函数应该返回-1。

public class 把数组排成最小的数 {

    public static void main(String[] args) {
        Integer[] arr = {3, 32, 321};
        System.out.println(getResult(arr));
    }

    private static String getResult(Integer[] arr) {
        List<Integer> arrList = Arrays.asList(arr);
        arrList.sort((o1, o2) -> {
            String a = o1 + "" + o2;
            String b = o2 + "" + o1;
            if (Integer.valueOf(a) > Integer.valueOf(b)) {
                return 1;
            } else if (Integer.valueOf(a) < Integer.valueOf(b)) {
                return -1;
            }
            return 0;
        });
        StringBuilder res = new StringBuilder();
        for (Integer item : arrList) {
            res.append(item);
        }
        return res.toString();
    }

}