一定要是BAT吗?一线互联网大厂Java面试题分享,看完别说不会

本文转载自:一定要是BAT吗?一线互联网大厂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;
}

本文转载自:一定要是BAT吗?一线互联网大厂Java面试题分享,看完别说不会

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值