就是想进大厂?一线互联网大厂Java面试题分享,看完别说还不行

为什么建议程序员一定要进大厂?

那年十八 母校舞会 站着如喽罗

那时候 我含泪 发誓各位 必须看到我

第一题:从1到n整数中1出现的次数

求出1~13的整数中 1 出现的次数,并算出 100~1300 的整数中1出现的次数?为此他特别数了一下 1~13 中包含1的数字有 1、10、11、12、13 因此共出现 6 次,但是对于后面问题他就没辙了。ACMer 希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

解题思路

  1. 假定$$n=21345$$将数字分为首位和非首位两个部分
  2. 对于首位为 1 的情况,如果首位$$>1$$那么$$sum=sum+10^{len(n)-1}$$,如果首位$$=1$$那么 $$sum=sum+1$$
  3. 对于非首位 1,指定其中一位为 1,根据排列组合有 $$10^{len(n)-2}\times(len(n)-1)$$个。那么非首位 1 总共有 $$2\times10^{len(n)-2}\times(len(n)-1)$$
public int NumberOf1Between1AndN_Solution(int n) {
    int[] res = {0};
    NumberOf1Between1AndN(res, n);
    return res[0];
}
private void NumberOf1Between1AndN(int[] res, int n) {
    //假设 num=21345
    String num = String.valueOf(n);
    int firstNum = num.charAt(0) - '0';
    if (num.length() == 1) {
        if (firstNum > 0) res[0]++;
        return;
    }
    String nextNum = num.substring(1);
    int nextN = Integer.valueOf(nextNum);
    //数字 10000 ~ 19999 的第一位中的个数
    if (firstNum > 1) {
        res[0] += Math.pow(10, num.length() - 1);
    } else if (firstNum == 1) {
        res[0] += nextN + 1;
    }
    //1346 ~ 21345 除第一位之外的数的个数
    res[0] += firstNum * (num.length() - 1) * Math.pow(10, num.length() - 2);
    NumberOf1Between1AndN(res, nextN);
}

第二题:把数组排成最小的数

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

解题思路

  1. 最直接的办法就是,找到数组中数字的所有排列组合,找到最小的
  2. 对于$$m,n$$,可以组成$$mn,nm$$这两个数,如果$$mn < nm$$那么,$$m$$应该在$$n$$之前
  3. 对于一组数,可以通过上述规则进行排序,依次打印出来就是最小的数
  4. 由于组合之后的数可能超出 int 的表示范围,注意使用字符串来处理大数问题
public String PrintMinNumber(int[] numbers) {
    List<String> nums = new ArrayList<>();
    for (int number : numbers) {
        nums.add(String.valueOf(number));
    }
    nums.sort(Comparator.comparing(s -> s, (o1, o2) -> (o1 + o2).compareTo(o2 + o1)));
    StringJoiner joiner = new StringJoiner("");
    nums.forEach(joiner::add);
    return joiner.toString();
}

第三题:丑数

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

解题思路

  1. 通过保存已有丑数的方式,用空间换时间
  2. 对于已有丑数$$M$$,那么下一个丑数 $$M=\min(M{2}\times2,M{3}\times3,M_{5}\times5)$$
    3.$$M{max}$$ 是目前最大的丑数,那么$$M{2}$$是已有丑数中$$M{2}\times2$$第一个大于$$M{max}$$的丑数
public int GetUglyNumber_Solution(int index) {
    if (index == 0) {
        return 0;
    }
    if (index == 1) {
        return 1;
    }
    ArrayList<Integer> list = new ArrayList<>(index);
    list.add(1);
    int preIndex2 = 0;
    int preIndex3 = 0;
    int preIndex5 = 0;
    for (int i = 0; i < index; i++) {
        int next2 = list.get(preIndex2) * 2;
        int next3 = list.get(preIndex3) * 3;
        int next5 = list.get(preIndex5) * 5;
        int nextV = Math.min(Math.min(next2, next3), next5);
        list.add(nextV);
        while (preIndex2 < list.size() - 1 && list.get(preIndex2) * 2 <= nextV) preIndex2++;
        while (preIndex3 < list.size() - 1 && list.get(preIndex3) * 3 <= nextV) preIndex3++;
        while (preIndex5 < list.size() - 1 && list.get(preIndex5) * 5 <= nextV) preIndex5++;
    }
    return list.get(index - 1);
}

第四题:第一个只出现一次的字符

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).

解题思路

  1. 通过 LinkedHashMap 记录数组顺序,然后计算字符出现的次数
  2. 遍历找到第一个只出现 1次 的字符
public int FirstNotRepeatingchar(String str) {
    LinkedHashMap<Character, Integer> data = new LinkedHashMap<>();
    char[] chars = str.toCharArray();
    for (char c : chars) {
        Integer count = data.getOrDefault(c, 0);
        data.put(c, count + 1);
    }
    Character res = null;
    for (Character c : data.keySet()) {
        if (data.get(c) == 1) {
            res = c;
            break;
        }
    }
    if (res == null) {
        return -1;
    }
    for (int i = 0; i < chars.length; i++) {
        if (chars[i] == res) {
            return i;
        }
    }
    return -1;
}

第五题:数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

输入描述: 题目保证输入的数组中没有的相同的数字

数据范围

    对于%50的数据,size<=10^4

    对于%75的数据,size<=10^5

    对于%100的数据,size<=2*10^5

解题思路

  1. 使用归并排序的方式,划分子数组
  2. 两个子数组进行对比,有两个分别指向两个数组末尾的指针 f,s,数组分割下标为 mid,如果 array[f] > array[s]那么,就有s - midarray[f] 的逆序
  3. 依此类推,最终将数组排序,并且获得结果
public int InversePairs(int[] array) {
    long[] sum = {0};
    if (array == null || array.length == 0) {
        return (int) sum[0];
    }
    int[] temp = new int[array.length];
    mergeSort(array, 0, array.length - 1, temp, sum);
    return (int) (sum[0] % 1000000007);
}
private void mergeSort(int[] array, int start, int end, int[] temp, long[] sum) {
    if (start == end) {
        return;
    }
    int mid = (start + end) / 2;
    mergeSort(array, start, mid, temp, sum);
    mergeSort(array, mid + 1, end, temp, sum);
    int f = mid, s = end;
    int t = end;
    while (f >= start && s >= mid + 1) {
        if (array[f] > array[s]) {
            temp[t--] = array[f--];
            sum[0] += s - mid;
        } else {
            temp[t--] = array[s--];
        }
    }
    while (f >= start) {
        temp[t--] = array[f--];
    }
    while (s >= mid + 1) {
        temp[t--] = array[s--];
    }
    for (int i = end, j = end; i >= start; ) {
        array[j--] = temp[i--];
    }
}

第六题:两个链表的第一个公共结点

输入两个链表,找出它们的第一个公共结点。

解决思路

空间复杂度 O(n) 的算法

  1. 使用辅助容器,保存第一个链表的所有元素
  2. 遍历第二个链表,并对比当前节点是否在辅助容器中
/**
* 空间 O(n)
*
* @param pHead1
* @param pHead2
* @return
*/
public ListNode FindFirstCommonNode_1(ListNode pHead1, ListNode pHead2) {
    Set<ListNode> node1s = new HashSet<>();
    while (pHead1 != null) {
        node1s.add(pHead1);
        pHead1 = pHead1.next;
    }
    while (pHead2 != null) {
        if (node1s.contains(pHead2)) {
            return pHead2;
        }
        pHead2 = pHead2.next;
    }
    return null;
}

空间复杂度 O(1) 的算法

  1. 由于两个链表有可能不一样长,首先通过遍历找到他们的长度
  2. 移动较长的那个链表,使得两个链表长度一致
  3. 同步遍历两个链表

原理:如果两个链表相交,那么它们一定有相同的尾节点

/**
 * 空间 O(1)
 *
 * @param pHead1
 * @param pHead2
 * @return
 */
public ListNode FindFirstCommonNode_2(ListNode pHead1, ListNode pHead2) {
    int len1 = 0, len2 = 0;
    ListNode cursor1 = pHead1, cursor2 = pHead2;
    while (cursor1 != null) {
        cursor1 = cursor1.next;
        len1++;
    }
    while (cursor2 != null) {
        cursor2 = cursor2.next;
        len2++;
    }
    cursor1 = pHead1;
    cursor2 = pHead2;
    if (len1 > len2) {
        int i = len1;
        while (i != len2) {
            cursor1 = cursor1.next;
            i--;
        }
    } else if (len1 < len2) {
        int i = len2;
        while (i != len1) {
            cursor2 = cursor2.next;
            i--;
        }
    }
    while (cursor1 != null && cursor2 != null) {
        if (cursor1 == cursor2) {
            return cursor1;
        }
        cursor1 = cursor1.next;
        cursor2 = cursor2.next;
    }
    return null;
}

第七题:数字在排序数组中出现的次数

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

解题思路

  1. 利用二分查找,找到任意一个 k
  2. 由于 k 有多个,并且当前找到的 k 可能在任意位置。所以,在当前 k 的前后进行遍历查找
public int GetNumberOfK(int[] array, int k) {
    if (array == null || array.length == 0) {
        return 0;
    }
    //二分查找
    int start = 0, end = array.length - 1;
    int t = -1;
    while (start < end) {
        int mid = (start + end) / 2;
        if (array[mid] == k) {
            t = mid;
            break;
        } else if (array[mid] > k) {
            end = mid - 1;
        } else {
            start = mid + 1;
        }
    }
    if (array[start] == k) {
        t = start;
    }
    if (t == -1) {
        return 0;
    }
    //左侧
    int sum = 0;
    int a = t;
    while (a >= 0 && array[a] == k) {
        sum++;
        a--;
    }
    //右侧
    a = t + 1;
    while (a < array.length && array[a] == k) {
        sum++;
        a++;
    }
    return sum;
}

第八题:二叉树的深度

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

解题思路

  1. 深度优先遍历
public int TreeDepth(TreeNode root) {
    int[] max = {0};
    depth(root, max, 1);
    return max[0];
}
private void depth(TreeNode root, int[] max, int curDepth) {
    if (root == null) return;
    if (curDepth > max[0]) max[0] = curDepth;
    depth(root.left, max, curDepth + 1);
    depth(root.right, max, curDepth + 1);
}

第九题:数组中只出现一次的数字

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

解题思路

  1. 两个相等的数字进行异或的结果为0
  2. 在这个特殊的数组中,重复出现的数字只能为2次,那么如果将所有数字异或 就等价与将两个不同的数字进行异或
  3. 异或的结果肯定有一位为1,那么这两个不同的数字,在这一位上不同。
  4. 找到第一个为1的位,并将第一位为1的位是否为1作为分组条件,相同的数字一定在同一个分组里,整个数组分组异或
  5. 得到两个结果,即为两个不同的数
/**
* num1,num2分别为长度为1的数组。传出参数。将num1[0],num2[0]设置为返回结果
* @param array
* @param num1
* @param num2
*/
public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) {
    if (array == null || array.length < 3) {
        return;
    }
    int result = array[0];
    for (int i = 1; i < array.length; i++) {
        result ^= array[i];
    }
    //找到第一个为1的位
    int indexOfFirstBit1 = 0;
    int temp = result;
    while (temp != 0) {
        indexOfFirstBit1++;
        temp >>>= 1;
    }
    int mask = 1;
    for (int i = 1; i < indexOfFirstBit1; i++) {
        mask <<= 1;
    }
    //将第一位为1的位是否为1作为分组条件,分组异或
    int n1 = -1, n2 = -1;
    for (int i : array) {
        if ((i & mask) == mask) {
            if (n1 == -1) n1 = i; else n1 ^= i;
        } else {
            if (n2 == -1) n2 = i; else n2 ^= i;
        }
    }
    num1[0] = n1;
    num2[0] = n2;
}

第十题:和为S的两个数字

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

对应每个测试案例,输出两个数,小的先输出。

解题思路

  1. 利用二分查找的思想,由于是排序数组,通过两个指针来进行遍历
public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) {
    ArrayList<Integer> res = new ArrayList<>();
    if (array == null || array.length == 1) {
        return res;
    }
    int start = 0, end = array.length - 1;
    int minMulti = Integer.MAX_VALUE;
    int a = -1, b = -1;
    while (start < end) {
        int t = array[start] + array[end];
        if (t == sum) {
            int multi = array[start] * array[end];
            if (multi < minMulti) {
                a = array[start];
                b = array[end];
                minMulti = multi;
            }
            start++;
            end--;
        } else if (t > sum) end--; else start++;
    }
    if (a == -1 || b == -1) {
        return res;
    }
    res.add(a);
    res.add(b);
    return res;
}

第十一题:和为S的连续正数序列

输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

解题思路

  1. 与上一个题目类似,需要确定的是序列的最大值,不超过 sum
  2. 使用窗口模式,两个指针定义一个窗口,和为 t
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
    ArrayList<ArrayList<Integer>> res = new ArrayList<>();
    if (sum == 1) {
        return res;
    }
    int start = 1, end = 2;
    int t = start + end;
    while (start < end) {
        if (t == sum) {
            ArrayList<Integer> ints = new ArrayList<>();
            for (int i = start; i <= end; i++) {
                ints.add(i);
            }
            res.add(ints);
            t -= start;
            start++;
        } else if (t > sum) {
            t -= start;
            start++;
        } else {
            if (end >= sum) break;
            end++;
            t += end;
        }
    }
    return res;
}

第十二题:翻转单词顺序列

牛客最近来了一个新员工 Fish ,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

解题思路

public String ReverseSentence(String str) {
    if(str == null || str.trim().equals("")) return str;
    String[] split = str.split(" ");
    StringBuilder builder = new StringBuilder();
    for (int i = split.length - 1; i >= 0; i--) {
        builder.append(split[i]);
        if (i != 0) builder.append(" ");
    }
    return builder.toString();
}

第十三题:左旋转字符串

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

解题思路

  1. 对于 abcXYZdef 左移 3位,可以将字符串分为两个部分:abc & XYZdef
  2. 分别将两个部分进行反转得到:cba & fedZYX
  3. 将两部分和在一起再进行反转:XYZdefabc
public String LeftRotateString(String str, int n) {
    if (str == null || str.trim().equals("")) return str;
    String res = revert(str, 0, n - 1);
    res = revert(res, n, str.length() - 1);
    res = revert(res, 0, str.length() - 1);
    return res;
}
private String revert(String str, int start, int end) {
    char[] chars = str.toCharArray();
    while (start < end) {
        char t = chars[start];
        chars[start] = chars[end];
        chars[end] = t;
        start++;
        end--;
    }
    return new String(chars);
}

第十四题:n个骰子的点数

把 n 个骰子扔在地上,所有骰子朝上一面的和为 s,输入 n,打印 s 所有可能值的概率

解题思路

  1. 首先考虑一个骰子的情况,那么有 1~6 出现的次数均为 1
  2. 再增加一个骰子时,由于各个点数出现的概率一致。用$$f(n,s)=f(n-1,s-1)+f(n-1,s-2)+f(n-1,s-3)+f(n-1,s-4)+f(n-1,s-5)+f(n-1,s-6)$$
  3. 使用两个数组循环求解
public void SumOfNDice(int n) {
    if (n < 1) {
        return;
    }
    int[][] nums = new int[2][n * 6 + 1];
    int flag = 0;
    //初始化第一个骰子各总和出现的次数
    int maxLen = nums[0].length;
    for (int i = 1; i < maxLen; i++) {
        nums[flag][i] = 1;
    }
    for (int i = 2; i <= n; i++) {
        int newFlag = flag ^ 0x01;
        Arrays.fill(nums[newFlag], 0);
        for (int j = i; j < maxLen; j++) {
            int sum = 0;
            for (int k = 1; k <= 6 && (j - k >= 0); k++) {
                sum += nums[flag][j - k];
            }
            nums[newFlag][j] = sum;
        }
        flag = newFlag;
    }
    //debug out
    System.out.println(Arrays.toString(nums[flag]));
    int sum = 0;
    for (int i : nums[flag]) {
        sum += i;
    }
    for (int i = 0; i < nums[flag].length; i++) {
        System.out.println(i + ":" + nums[flag][i] * 1.0 / sum);
    }
}

第十五题:扑克牌顺子

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有 2 个大王, 2 个小王(一副牌原本是 54 张)…他随机从中抽出了 5 张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…..LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们 LL 的运气如何, 如果牌能组成顺子就输出 true,否则就输出 false。为了方便起见,你可以认为大小王是0。

解题思路

  1. 对数组进行排序
  2. 计算非0元素之间的间隔总和
  3. 如果有相同元素则直接认为失败
  4. 如果间隔大于0,那么间隔的总个数等于0的总个数,即为成功
public Boolean isContinuous(int[] numbers) {
    if (numbers == null || numbers.length < 5) return false;
    Arrays.sort(numbers);
    int count = 0;
    int zeroCount = 0;
    int pre = -1;
    for (int number : numbers) {
        if (number == 0) {
            zeroCount++;
            continue;
        }
        if (pre == -1) pre = number; else {
            int t = number - pre - 1;
            if (t > 0) {
                count += t;
            } else if (t < 0) return false;
            pre = number;
        }
    }
    if (count == 0) return true; else return count == zeroCount;
}

写在最后

文章限于篇幅,还有很多面试题,我就不在这里一一讲解,面试题我已经整理成一份完整的PDF文件,需要的朋友,请关注下方公众号领取

部分面试题截图,总有你想要的的或者你需要的!

猜你喜欢

转载自blog.csdn.net/yunduo1/article/details/108586333