【剑指OFFER】——Java实现(面试题39-52)

面试题39:数组中出现次数超过一半的数字
解题方案1:排序后统计出现次数。
解题方案2:利用快速排序中的分片,随机选择一个数分片后,若这个这数的位置恰好在n/2位置,那么就找到了中位数。若下标大于n/2,那么中位数在这个数的左边,然后在左边的数组中继续查找。
解题方案3:利用题目的特殊性,这个数字出现的次数比其他数字出现的总和还要多,使用两个变量,一个存数组中的一个数字,一个存次数,遍历一遍数组,若数字和存的数字相同则计数加一,若不等计数减一,计数为0的时候需要存下个数字。

面试题40:最小的K个数
解题方案1:分片函数,时间复杂度O(n),但是会修改原数组;
解题方案2:最小堆,时间复杂度O(nlogk)

面试题41:数据流中的中位数
解题方案:使用一个最小堆,和一个最大堆,若插入最小堆的数据大于最大堆的部分数据,则将这个数据插入到最大堆,并从最大堆中取出最大值插入到最小堆中,保证两个堆的数量一致。

面试题42:连续子数组的最大和
解决方案:动态规划,函数 f(i) 表示第i个数字结尾的子数组的最大和,那么我们需要求出 max[f(i)]
f ( i ) = { d a t a [ i ] i=0 或者 f(i-1)&lt;=0&amp;&amp;data[i]&gt;f(i-1) f ( i − 1 ) + d a t a [ i ] i&gt;0并且f(i-1)&gt;0 f(i) = \begin{cases} data[i] &amp; \text{i=0 或者 f(i-1)&lt;=0\&amp;\&amp;data[i]&gt;f(i-1)} \\[2ex] f(i-1)+data[i] &amp; \text{i&gt;0并且f(i-1)&gt;0} \end{cases} f(i)=data[i]f(i1)+data[i]i=0 或者 f(i-1)<=0&&data[i]>f(i-1)i>0并且f(i-1)>0

    public void fortySecond() {
        int[] array = {-2, -3, -4, -1};
        // int[] array = {1, -2, 3, 10, -4, 7, 2, -5};
        int sumOfSubArray = findGreatestSumOfSubArray(array);
        System.out.println(sumOfSubArray);
    }
    private int findGreatestSumOfSubArray(int[] array) {
        if (array == null || array.length == 0) {
            throw new RuntimeException("Invalid Input");
        }
        int curSum = array[0];
        int nGreatestSum = curSum;

        for (int i = 1; i < array.length; i++) {
            if (curSum <= 0 && array[i] > curSum) {
                curSum = array[i];
            }else if (curSum > 0) {
                curSum += array[i];
            }
            if (curSum > nGreatestSum) {
                nGreatestSum = curSum;
            }
        }
        return nGreatestSum;
    }

面试题43:1-n整数中1出现的次数
题目:输入一个整数n,求1-n这n个整数十进制表示中1出现的次数。
解题方案1:遍历1-n的n个整数,汇总每个数中1出现的次数,时间复杂度O(nlogn)
解题方案2:找规律,8171 可分为 1-171 和 172-8171,172-8171中出现在万位的次数为103,172-8171 可再分为 172-1171、1172-2171等,172-8171 中出现在除万位其他位的次数为 8*3*102,再递归找 1-171 中出现的次数。

    public void fortyThird() {
        System.out.println(getNumberOf1(1));
        System.out.println(getNumberOf1(10));
        System.out.println(getNumberOf1(8171));
    }

    private int getNumberOf1(int num) {
        if (num <= 0) {
            return 0;
        }
        // 8171 拆分为 1-171 和 172 - 8171
        String number = Integer.valueOf(num).toString();
        if (number.length() == 1) {
            return 1;
        }
        int numFirstDist = 0;
        int numOtherDigits = 0;
        int numRecursive = 0;
        int firstNum = number.charAt(0) - '0';
        int low = num % (powerBase10(number.length() - 1));
        // 1出现在最高位
        if (firstNum > 1) {
            numFirstDist = powerBase10(number.length() - 1);
        } else {
            numFirstDist = low + 1;
        }
        // 1出现在其他的低位 172-1171  1172-2171 2172-3173
        numOtherDigits = firstNum * (number.length() - 1) * powerBase10((number.length() - 2));
        numRecursive = getNumberOf1(low);
        return numFirstDist + numOtherDigits + numRecursive;
    }

    private int powerBase10(int n) {
        int result = 1;
        while (n > 0) {
            result *= 10;
            n--;
        }
        return result;
    }

面试题44:数字序列中某一位的数字
题目:数字以 012346789101112131415… 的格式序列化到一个字符串序列中。求任意第N位(从0开始计数)的数字。

    public void fortyForth() {
        System.out.print(digitAtIndex(1));
        System.out.print(digitAtIndex(2));
        System.out.print(digitAtIndex(3));
        System.out.print(digitAtIndex(10));
        System.out.print(digitAtIndex(11));
        System.out.print(digitAtIndex(12));
        System.out.println();
        System.out.print(digitAtIndex(1000));
        System.out.print(digitAtIndex(1001));
        System.out.print(digitAtIndex(1002));
        System.out.print(digitAtIndex(1003));
        System.out.print(digitAtIndex(1004));
        System.out.print(digitAtIndex(1005));
        System.out.print(digitAtIndex(1006));
    }
    private int digitAtIndex(int index) {
        if (index <= 0) {
            return 0;
        }
        int digit = 1;
        while (true) {
            int numbers = countOfInteger(digit);
            if (index <= numbers) {
                return digitAtIndex(index, digit);
            }
            index -= numbers;
            digit++;
        }
    }

    /**
     * digit 位有多少个数字
     *
     * @param digit 个位1、十位2、百位3...
     */
    private int countOfInteger(int digit) {
        if (digit <= 1) {
            return 9;
        }
        return 9 * (int) Math.pow(10, digit - 1) * digit;
    }

    /**
     * @param index 某个位对应的下标
     * @param digit 位
     * @return 结果
     */
    private static int digitAtIndex(int index, int digit) {
        if (digit == 1) {
            return index;
        }
        int begin = (int) Math.pow(10, digit - 1);
        int num = index / digit;
        int mod = index % digit;
        int number;
        if (mod == 0) {
            number = begin + num - 1;
            return number % 10;
        } else {
            number = begin + num;
            return (number / (int) Math.pow(10, digit - mod)) % 10;
        }
    }

面试题45:把数组排成最小的数
题目:给一个数组 {3, 32, 321, 324, 4, 42, 5},把数组中的数拼接起来使它最小。
解题方案:利用快速排序,小的值排在前面,怎么判断两个数值哪个小,比较合成的两个字符串字符串

    public void fortyFifth() {
        int[] arrays = {3, 32, 321, 324, 4, 42, 5};
        String[] stringArrays = new String[arrays.length];
        for (int i = 0; i < arrays.length; i++) {
            stringArrays[i] = String.valueOf(arrays[i]);
        }
        System.out.println(Arrays.deepToString(stringArrays));
        quickSort(stringArrays, 0, stringArrays.length - 1);
        System.out.println(Arrays.deepToString(stringArrays));
    }

    private void quickSort(String[] stringArrays, int beginIndex, int endIndex) {
        if (beginIndex < endIndex) {
            int partition = partition(stringArrays, beginIndex, endIndex);
            quickSort(stringArrays, beginIndex, partition - 1);
            quickSort(stringArrays, partition + 1, endIndex);
            Merge(stringArrays, beginIndex, partition, endIndex);
        }
    }

    private void Merge(String[] stringArrays, int beginIndex, int partition, int endIndex) {
        String[] temp = new String[endIndex - beginIndex + 1];
        int t = 0; //临时数组指针
        int i = beginIndex;
        int j = partition + 1;
        while (i <= partition && j <= endIndex) {
            if (compareString(stringArrays[i], stringArrays[j]) <= 0) {
                temp[t++] = stringArrays[i++];
            } else {
                temp[t++] = stringArrays[j++];
            }
        }
        while (i <= partition) {
            temp[t++] = stringArrays[i++];
        }
        while (j <= endIndex) {
            temp[t++] = stringArrays[j++];
        }
        for (int k = 0; k < t; k++) {
            stringArrays[beginIndex + k] = temp[k];
        }
    }

    private int partition(String[] stringArrays, int beginIndex, int endIndex) {
        String temp = stringArrays[beginIndex];
        while (beginIndex < endIndex) {
            while (beginIndex < endIndex && compareString(temp, stringArrays[endIndex]) <= 0) {
                endIndex--;
            }
            stringArrays[beginIndex] = stringArrays[endIndex];
            while (beginIndex < endIndex && compareString(stringArrays[beginIndex], temp) <= 0) {
                beginIndex++;
            }
            stringArrays[endIndex] = stringArrays[beginIndex];
        }
        stringArrays[beginIndex] = temp;
        return beginIndex;
    }

    private int compareString(String stringNumber1, String stringNumber2) {
        String stringCombine1 = stringNumber1 + stringNumber2;
        String stringCombine2 = stringNumber2 + stringNumber1;
        // stringCombine1<stringCombine2  , stringNumber1应该排在stringNumber2的前面
        return stringCombine1.compareTo(stringCombine2);
    }

面试题46:把数字翻译成字符串
题目:1 可翻译为 a,2 可翻译为b,11 可翻译为l……25 可翻译为z。
解题方案:自下而上消除重复子问题,从字符串后面往前遍历,存储每个位置可翻译的选择。

    public void fortySixth() {
        System.out.println(getTransactionCount(12258));
    }

    private int getTransactionCount(int number) {
        if (number < -0) {
            return 0;
        }
        String string = String.valueOf(number);
        char[] chars = string.toCharArray();
        int length = chars.length;
        int[] counts = new int[length];
        int count = 0;
        for (int i = length - 1; i >= 0; i--) {
            if (i < length - 1) {
                count = counts[i + 1];
                int digit1 = chars[i] - '0';
                int digit2 = chars[i + 1] - '0';
                int converted = digit1 * 10 + digit2;
                if (converted >= 10 && converted <= 25) {
                    if (i < length - 2) {
                        count += counts[i + 2];
                    } else {
                        count++;
                    }
                }
            } else {
                count = 1;
            }
            counts[i] = count;
        }
        return counts[0];
    }

面试题47:礼物的最大值
解题方案:动态规划,f(i,j) = max(f((i-1) , j ) , f(i , (j-1)) ) + value[i][j]。

面试题48:最长不含重复字符的子字符串
解题方案:动态规划,f(i)表示以第i个字符结尾的不含重复字符的子字符串的最大长度,另外使用一个数组存储每个字符在字符串中的下标。

    public void fortyEighth() {
        System.out.println(getLongestSubStringWithoutDuplication("arabcacfr"));
        System.out.println(getLongestSubStringWithoutDuplication("nuttertools"));
    }

    private int getLongestSubStringWithoutDuplication(String str) {
        int[] position = new int[26];
        for (int i = 0; i < 26; i++) {
            position[i] = -1;
        }
        int curLength = 0;
        int maxLength = 0;
        for (int i = 0; i < str.length(); i++) {
            int pervIndex = position[str.charAt(i) - 'a'];
            if (pervIndex == -1 || (i - pervIndex) > curLength) {
                curLength++;
            } else {
                if (curLength > maxLength) {
                    maxLength = curLength;
                }
                curLength = i - pervIndex;
            }
            position[str.charAt(i) - 'a'] = i;
        }
        if (curLength > maxLength) {
            maxLength = curLength;
        }
        return maxLength;
    }

面试题49:丑数
题目:只包含2、3、5因子的数称为丑树,求第N个丑树。
解题方案1:从1开始每次增加一往后找,每次都计算这个数是否是丑数。

    public void fortyNinth1() {
        System.out.println(getUglyDigit(10));
        System.out.println(getUglyDigit(1000));
    }

    private int getUglyDigit(int index) {
        if (index <= 0) {
            return 0;
        }
        int uglyNumIndex = 0;
        int num = 0;
        while (uglyNumIndex < index) {
            num++;
            if (isUgly(num)) {
                uglyNumIndex++;
            }
        }
        return num;
    }

    private boolean isUgly(int num) {
        while (num % 2 == 0) {
            num = num / 2;
        }
        while (num % 3 == 0) {
            num = num / 3;
        }
        while (num % 5 == 0) {
            num = num / 5;
        }
        return num == 1;
    }

解题方案2:从第一个丑数开始,计算并存储每个丑数。

    public void fortyNinth2() {
        System.out.println(getUglyDigit2(10));
        System.out.println(getUglyDigit2(1000));
    }

    private int getUglyDigit2(int index) {
        if (index <= 0) {
            return 0;
        }
        int[] arrays = new int[index];
        arrays[0] = 1;
        int index2 = 0;
        int index3 = 0;
        int index5 = 0;
        int nextUglyIndex = 1;
        while (nextUglyIndex < index) {
            int min = min(arrays[index2] * 2, arrays[index3] * 3, arrays[index5] * 5);
            arrays[nextUglyIndex] = min;
            while ((arrays[index2] * 2) <= min) {
                index2++;
            }
            while ((arrays[index3] * 3) <= min) {
                index3++;
            }
            while ((arrays[index5] * 5) <= min) {
                index5++;
            }
            nextUglyIndex++;
        }
        return arrays[index - 1];
    }

    public int min(int a, int b, int c) {
        if (b < a) {
            a = b;
        }
        if (c < a) {
            a = c;
        }
        return a;
    }

面试题50:第一个只出现一次的字符
题目:只包含2、3、5因子的数称为丑树,求第N个丑树。
解题方案:遍历字符,判断当前字符是否在后面出现,时间复杂度O(n2);利用一个哈希表存储字符和字符出现的次数。

面试题51:数组中的逆数对
解题方案:利用归并排序,先将数组拆分为一个个小元素,然后进行两两合并,合并的时候,将较小的数排在前面。对两个数组进行合并时,若左边数组中的最大数大于右边数组中的最大数,说明左边的这个数能和右边的任何的数组成一个逆数对,左边的数组指针减一继续判断;若小于,将右边数组的指针左移一位继续判断。

    public void fifthFirst() {
        int[] arrays = {7, 5, 6, 4};
        int count = inversePairsCore(arrays, new int[arrays.length], 0, arrays.length - 1);
        System.out.println(count);
        arrays = new int[]{5, 7, 5, 4};
        System.out.println(inversePairsCore(arrays, new int[arrays.length], 0, arrays.length - 1));
    }

    private int inversePairsCore(int[] arrays, int[] tempArrays, int beginIndex, int endIndex) {
        if (beginIndex >= endIndex) {
            return 0;
        }
        int mid = (endIndex + beginIndex) / 2;
        int left = inversePairsCore(arrays, tempArrays, beginIndex, mid);
        int right = inversePairsCore(arrays, tempArrays, mid + 1, endIndex);
        int merge = merge(arrays, tempArrays, beginIndex, mid, endIndex);
        return left + right + merge;
    }

    private int merge(int[] array, int[] temp, int low, int mid, int high) {
        int t = high; //临时数组指针
        int i = mid;
        int j = high;
        int count = 0;
        while (i >= low && j > mid) {
            if (array[i] > array[j]) {
                temp[t--] = array[i--];
                count += j - mid;
            } else {
                temp[t--] = array[j--];
            }
        }
        while (i >= low) {
            temp[t--] = array[i--];
        }
        while (j > mid) {
            temp[t--] = array[j--];
        }
        for (int k = high; k >= low; k--) {
            array[k] = temp[k];
        }
        return count;
    }

面试题52:两个链表的第一个公共节点
解决方案1:蛮力破解,把第一个链表中的每个元素放到第二个中去对比。
解决方案2:遍历两个链表并分别将元素存储到两个不同的栈中,再同时遍历两个栈。
解决方案3:遍历两个链表获取两个链表的长度,长度长的链表先走N和长度短的长度一致时,再同时遍历找相同节点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值