前言
上周去朋友去某公司面试,结果在被面试官问到算法时,直接给整不会了,于是我特意整理出来一套大厂高频面试算法题,于是朋友拿着我这套题库刷了一星期的力扣算法题终于如愿拿到offer,事后朋友说好像算法也没那么难,主要是多理解就好,看到能帮助到朋友我也很高兴,现在分享给大家,废话不多说,下面给大家看看力扣算法题及答案,需要的同学可以在文末领取
一、反转链表
反转一个单链表。
输入: 1->2->3->4->5
输出: 5->4->3->2->1
解法1:迭代,重复某一过程,每一次处理结果作为下一次处理的初始值,这些初始值类似于状态、每 次处理都会改变状态、直至到达最终状态
从前往后遍历链表,将当前节点的next指向上一个节点,因此需要一个变量存储上一个节点prev,当前节点处理完需要寻找下一个节点,因此需要一个变量保存当前节点curr,处理完后要将当前节点赋值给
prev,并将next指针赋值给curr,因此需要一个变量提前保存下一个节点的指针next
1、将下一个节点指针保存到next变量 next = curr.next
2、将下一个节点的指针指向prev,curr.next = prev
3、准备处理下一个节点,将curr赋值给prev
4、将下一个节点赋值为curr,处理一个节点
解法2:递归:略
二、统计N以内的素数
素数:只能被1和自身整除的数,0、1除外
解法一:暴力算法
直接从2开始遍历,判断是否能被2到自身之间的数整除
public int countPrimes(int n)
{ int ans = 0;
for (int i = 2; i < n; ++i)
{ ans += isPrime(i) ? 1 :
0;
}
return ans;
}
//i如果能被x整除,则x/i肯定能被x整除,因此只需判断i和根号x之中较小的即可
public boolean isPrime(int x) {
for (int i = 2; i * i <= x; ++i)
{ if (x % i == 0) {
return false;
}
}
return true;
}
解法2:埃氏筛
略
三、寻找数组的中心索引
数组中某一个下标,左右两边的元素之后相等,该下标即为中心索引思路:先统计出整个数组的总和,然后从第一个元素开始叠加
总和递减当前元素,叠加递增当前元素,知道两个值相等
public static int pivotIndex(int[] nums)
{ int sum1 =
Arrays.stream(nums).sum(); int sum2 =
0;
for(int i = 0; i<nums.length;
i++){ sum2 += nums[i];
if(sum1 ==
sum2){ return
i;
}
sum1 = sum1 - nums[i];
}
return -1;
}
四、删除排序数组中的重复项
一个有序数组 nums ,原地删除重复出现的元素,使每个元素只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
双指针算法:
数组完成排序后,我们可以放置两个指针 i 和 j,其中 i 是慢指针,而 j 是快指针。只要
nums[i]=nums[j],我们就增加 j 以跳过重复项。
当遇到 nums[j] != nums[i]时,跳过重复项的运行已经结束,必须把nums[j])的值复制到 nums[i +
1]。然后递增 i,接着将再次重复相同的过程,直到 j 到达数组的末尾为止。
public int removeDuplicates(int[] nums)
{ if (nums.length == 0) return 0;
int i = 0;
for (int j = 1; j < nums.length; j++)
{ if (nums[j] != nums[i]) {
i++;
nums[i] = nums[j];
}
}
return i + 1;
}
五、x的平方根
在不使用 sqrt(x) 函数的情况下,得到 x的平方根的整数部分解法一:二分查找
x的平方根肯定在0到x之间,使用二分查找定位该数字,该数字的平方一定是最接近x的,m平方值如果大于x、则往左边找,如果小于等于x则往右边找
找到0和X的最中间的数m,
如果m * m > x,则m取x/2到x的中间数字,直到m * m < x,m则为平方根的整数部分
如果m * m <= x,则取0到x/2的中间值,知道两边的界限重合,找到最大的整数,则为x平方根的整数部分
时间复杂度:O(logN)
public static int binarySearch(int x)
{ int l = 0, r = x, index = -1;
while (l <= r) {
int mid = l + (r - l) / 2;
if ((long) mid * mid <= x) {
index = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
return index;
}
解法二:牛顿迭代
略
六、 三个数的最大乘积
一个整型数组乘积不会越界,在数组中找出由三个数字组成的最大乘积,并输出这个乘积。
如果数组中全是非负数,则排序后最大的三个数相乘即为最大乘积;如果全是非正数,则最大的三个数 相乘同样也为最大乘积。
如果数组中有正数有负数,则最大乘积既可能是三个最大正数的乘积,也可能是两个最小负数(即绝对 值最大)与最大正数的乘积。
分别求出三个最大正数的乘积,以及两个最小负数与最大正数的乘积,二者之间的最大值即为所求答案。
解法一:排序
public static int sort(int[] nums)
{ Arrays.sort(nums);
int n = nums.length;
return Math.max(nums[0] * nums[1] * nums[n - 1], nums[n - 3] * nums[n - 2] * nums[n - 1]);
}
解法二:线性扫描
public static int getMaxMin(int[] nums) {
// 最小的和第二小的
int min1 = 0, min2 = 0;
// 最大的、第二大的和第三大的
int max1 = 0, max2 = 0, max3 = 0;
for (int x : nums)
{ if (x < min1) {
min2 = min1;
min1 = x;
} else if (x < min2)
{ min2 = x;
}
if (x > max1)
{ max3 =
max2; max2 =
max1; max1 =
x;
} else if (x > max2)
{ max3 = max2;
max2 = x;
} else if (x > max3)
{ max3 = x;
}
}
}
七、两数之和
给定一个升序排列的整数数组 numbers ,从数组中找出两个数满足相加之和等于目标数 target 。假设每个输入只对应唯一的答案,而且不可以重复使用相同的元素。
返回两数的下标值,以数组形式返回
暴力解法
public int[] twoSum(int[] nums, int target)
{ int n = nums.length;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (nums[i] + nums[j] == target)
{ return new int[]{i, j};
}
}
}
return new int[0];
}
时间复杂度:O(N的平方) 空间复杂度:O(1)
哈希表:将数组的值作为key存入map,target - num作为key
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; ++i) {
if (map.containsKey(target - nums[i])) {
return new int[]{map.get(target - nums[i]), i};
}
map.put(nums[i], i);
}
return new int[0];
}
时间复杂度:O(N)
空间复杂度:O(N)
解法一:二分查找
先固定一个值(从下标0开始),再用二分查找查另外一个值,找不到则固定值向右移动,继续二分查找
public int[] twoSearch(int[] numbers, int target)
{ for (int i = 0; i < numbers.length; ++i) {
int low = i, high = numbers.length -1;
while (low <= high) {
int mid = (high - low) / 2 + low;
if (numbers[mid] == target - numbers[i])
{ return new int[]{i, mid};
} else if (numbers[mid] > target - numbers[i])
{ high = mid - 1;
} else {
low = mid + 1;
}
}
}
}
时间复杂度:O(N * logN)
空间复杂度:O(1)
解法二:双指针
左指针指向数组head,右指针指向数组tail,head+tail > target 则tail 左移,否则head右移
public int[] twoPoint(int[] numbers, int target)
{ int low = 0, high = numbers.length - 1;
while (low < high) {
int sum = numbers[low] + numbers[high];
if (sum == target) {
return new int[]{low + 1, high + 1};
} else if (sum < target) {
++low;
} else {
--high;
}
}
return new int[]{-1, -1};
}
时间复杂度:O(N) 空间复杂度:O(1)
八、斐波那契数列
求取斐波那契数列第N位的值。
斐波那契数列:每一位的值等于他前两位数字之和。前两位固定 0,1,1,2,3,5,8。。。。解法一:暴力递归
public static int calculate(int
num){ if(num == 0 ){
return 0;
}
if(num == 1){
return 1;
}
return calculate(num-1) + calculate(num-2);
}
解法二:去重递归
递归得出具体数值之后、存储到一个集合(下标与数列下标一致),后面递归之前先到该集合查询一次, 如果查到则无需递归、直接取值。查不到再进行递归计算
public static int calculate2(int
num){ int[] arr = new int[num+1];
return recurse(arr,num);
}
private static int recurse(int[] arr, int num)
{ if(num == 0 ){
return 0;
}
if(num == 1){
return 1;
}
if(arr[num] !=
0){ return
arr[num];
}
arr[num] = recurse(arr,num-1) + recurse(arr,num-2);
return arr[num];
}
解法三:双指针迭代
略
九、 环形链表
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达该节点,则链表中存在环如果链表中存在环,则返回 true 。 否则,返回 false 。
解法一:哈希表
public static boolean hasCycle(ListNode head)
{ Set<ListNode> seen = new
HashSet<ListNode>(); while (head != null) {
if (!seen.add(head))
{ return true;
}
head = head.next;
}
return false;
}
解法二:双指针
public static boolean hasCycle2(ListNode head) {
if (head == null || head.next == null)
{ return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null)
{ return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
十、排列硬币
总共有 n 枚硬币,将它们摆成一个阶梯形状,第 k 行就必须正好有 k 枚硬币。给定一个数字 n,找出可形成完整阶梯行的总行数。
n 是一个非负整数,并且在32位有符号整型的范围内
解法一:迭代
从第一行开始排列,排完一列、计算剩余硬币数,排第二列,直至剩余硬币数小于或等于行数
public static int arrangeCoins(int n)
{ for(int i=1; i<=n; i++){
n = n-i;
if (n <= i){
return i;
}
}
return 0;
}
解法二:二分查找
略
十一、合并两个有序数组
两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。
解法一:合并后排序
public void merge(int[] nums1, int m, int[] nums2, int n)
{ System.arraycopy(nums2, 0, nums1, m, n);
Arrays.sort(nums1);
}
时间复杂度 : O((n+m)log(n+m))。空间复杂度 : O(1)。
解法二:双指针 从前往后
略
十二、子数组最大平均数
给一个整数数组,找出平均数最大且长度为滑动窗口:
窗口移动时,窗口内的和等于sum加上新加进来的值,减去出去的值
public double findMaxAverage(int[] nums, int k)
{ int sum = 0;
int n = nums.length;
for (int i = 0; i < k; i++)
{ sum += nums[i];
}
int maxSum = sum;
for (int i = k; i < n; i++) {
sum = sum - nums[i - k] + nums[i];
maxSum = Math.max(maxSum, sum);
}
return 1.0 * maxSum / k;
}
十三、二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。解法一:深度优先
遍历整颗数,找到每一个叶子节点,从叶子节点往上开始计算,左右子节点都为空则记录深度为1
左右子节点只有一边,深度记录为子节点深度+1
左右两边都有子节点,则记录左右子节点的深度较小值+1
public int minDepth(TreeNode root)
{ if (root == null) {
return 0;
}
if (root.left == null && root.right == null)
{ return 1;
}
int min_depth = Integer.MAX_VALUE;
if (root.left != null) {
min_depth = Math.min(minDepth(root.left), min_depth);
}
if (root.right != null) {
min_depth = Math.min(minDepth(root.right), min_depth);
}
return min_depth + 1;
}
时间复杂度:O(N)
空间复杂度:O(logN) 取决于树的高度
解法二:广度优先
略
十四、最长连续递增序列
给定一个未经排序的整数数组,找到最长且连续递增的子序列,并返回该序列的长度。 序列的下标是连续的
贪心算法
从0开始寻找递增序列,并将长度记录,记录递增序列的最后一个下标,然后从该下标继续寻找,记录 长度,取长度最大的即可
public static int findLength(int[] nums)
{ int ans = 0;
int start = 0;
for (int i = 0; i < nums.length; i++)
{ if (i > 0 && nums[i] <= nums[i -
1]) {
start = i;
}
ans = Math.max(ans, i - start + 1);
}
return ans;
}
十五、柠檬水找零
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。必须给每个顾客正确找零注意,一开始你手头没有任何零钱。
如果你能给每位顾客正确找零,返回 true ,否则返回 false 。
输入:[5,5,5,10,20]
输出:true
输入:[10,10]
输出:false
贪心:
public boolean lemonadeChange(int[] bills)
{ int five = 0, ten = 0;
for (int bill : bills)
{ if (bill == 5) {
five++;
} else if (bill == 10)
{ if (five == 0) {
return false;
}
five--;
ten++;
} else {
if (five > 0 && ten > 0)
{ five--;
ten--;
} else if (five >= 3)
{ five -= 3;
} else {
return false;
}
}
}
return true;
}
十六、三角形的最大周长
给定由一些正数(代表长度)组成的数组 A ,返回由其中三个长度组成的、面积不为零的三角形的最大周长。
如果不能形成任何面积不为零的三角形,返回 0 。贪心:
先小到大排序,假设最长边是最后下标,另外两条边是倒数第二和第三下标,则此时三角形周长最大
n < (n-1) + (n-2),如果不成立,意味着该数组中不可能有另外两个值之和大于n,此时将n左移,重新计算
public int largestPerimeter(int[] A)
{ Arrays.sort(A);
for (int i = A.length - 1; i >= 2; --i)
{ if (A[i - 2] + A[i - 1] > A[i]) {
return A[i - 2] + A[i - 1] + A[i];
}
}
return 0;
}
十七、省份数量
有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c
直接相连,那么城市 a 与城市 c 间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。亲戚问题、朋友圈问题
解法一:深度优先
获取一个城市,通过递归找到离该城市最远的城市,标记为已访问,然后逐个向内进行标记
public int findCircleNum(int[][] isConnected)
{ int provinces = isConnected.length;
boolean[] visited = new boolean[provinces];
int circles = 0;
for (int i = 0; i < provinces; i++)
{ if (!visited[i]) {
dfs(isConnected, visited, provinces, i);
circles++;
}
}
return circles;
}
public void dfs(int[][] isConnected, boolean[] visited, int provinces, int i)
{ for (int j = 0; j < provinces; j++) {
if (isConnected[i][j] == 1 && !visited[j])
{ visited[j] = true;
dfs(isConnected, visited, provinces, j);
}
}
}
解法二:广度优先
略
十八、香槟塔
我们把玻璃杯摆成金字塔的形状,其中第一层有1个玻璃杯,第二层有2个,依次类推到第100层,每个玻璃杯(250ml)将盛有香槟。
从顶层的第一个玻璃杯开始倾倒一些香槟,当顶层的杯子满了,任何溢出的香槟都会立刻等流量的流向 左右两侧的玻璃杯。当左右两边的杯子也满了,就会等流量的流向它们左右两边的杯子,依次类推。
(当最底层的玻璃杯满了,香槟会流到地板上)
例如,在倾倒一杯香槟后,最顶层的玻璃杯满了。倾倒了两杯香槟后,第二层的两个玻璃杯各自盛放一 半的香槟。在倒三杯香槟后,第二层的香槟满了 - 此时总共有三个满的玻璃杯。在倒第四杯后,第三层中间的玻璃杯盛放了一半的香槟,他两边的玻璃杯各自盛放了四分之一的香槟
现在当倾倒了非负整数杯香槟后,返回第 i 行 j 个玻璃杯所盛放的香槟占玻璃杯容积的比例(i 和 j都从0开始)。
public double champagneTower(int poured, int query_row, int query_glass)
{ double[][] A = new double[102][102];
A[0][0] = (double) poured;
for (int r = 0; r <= query_row; ++r)
{ for (int c = 0; c <= r; ++c) {
double q = (A[r][c] - 1.0) / 2.0;
if (q > 0) {
A[r+1][c] += q;
A[r+1][c+1] += q;
}
}
}
return Math.min(1, A[query_row][query_glass]);
}
十九、Dota2参议院
Dota2 的世界里有两个阵营:Radiant(天辉)和 Dire(夜魇)
Dota2 参议院由来自两派的参议员组成。现在参议院希望对一个 Dota2 游戏里的改变作出决定。他们以一个基于轮为过程的投票进行。在每一轮中,每一位参议员都可以行使两项权利中的一项:
禁止一名参议员的权利:参议员可以让另一位参议员在这一轮和随后的几轮中丧失所有的权利。
宣布胜利: 如果参议员发现有权利投票的参议员都是同一个阵营的,他可以宣布胜利并决定在游戏中的有关变化。
给定一个字符串代表每个参议员的阵营。字母 “R” 和 “D” 分别代表了 Radiant(天辉)和 Dire(夜魇)。然后,如果有 n 个参议员,给定字符串的大小将是 n。
以轮为基础的过程从给定顺序的第一个参议员开始到最后一个参议员结束。这一过程将持续到投票结 束。所有失去权利的参议员将在过程中被跳过。
假设每一位参议员都足够聪明,会为自己的政党做出最好的策略,你需要预测哪一方最终会宣布胜利并 在 Dota2 游戏中决定改变。输出应该是 Radiant 或 Dire。
public String predictPartyVictory(String senate)
{ int n = senate.length();
Queue<Integer> radiant = new LinkedList<Integer>();
Queue<Integer> dire = new LinkedList<Integer>();
for (int i = 0; i < n; ++i) {
if (senate.charAt(i) == 'R')
{ radiant.offer(i);
} else {
dire.offer(i);
}
}
while (!radiant.isEmpty() && !dire.isEmpty()) {
int radiantIndex = radiant.poll(), direIndex = dire.poll();
if (radiantIndex < direIndex) {
radiant.offer(radiantIndex + n);
} else {
dire.offer(direIndex + n);
}
}
return !radiant.isEmpty() ? "Radiant" : "Dire";
}
时间和空间:O(n)
二十、优势洗牌
给定两个大小相等的数组 A 和 B,A 相对于 B 的优势可以用满足 A[i] > B[i] 的索引 i 的数目来描述。返回 A 的任意排列,使其相对于 B 的优势最大化。
public static int[] advantageCount(int[] A, int[] B)
{ int[] sortedA = A.clone();
Arrays.sort(sortedA);//找一个代价最小的去匹配B中的,比B大,在A中又是最小的
int[] sortedB = B.clone();
Arrays.sort(sortedB);//避免比较时,A每次都要重头遍历
Map<Integer, Deque<Integer>> assigned = new HashMap();
for (int b: B)
assigned.put(b, new LinkedList());
Deque<Integer> remaining = new LinkedList();
int j = 0;
for (int a: sortedA) {
if (a > sortedB[j])
{ assigned.get(sortedB[j++]).add(a);
} else {
remaining.add(a);
}
}
int[] ans = new int[B.length];
for (int i = 0; i < B.length; ++i)
{ if (assigned.get(B[i]).size() >
0)
ans[i] = assigned.get(B[i]).removeLast();
else
ans[i] = remaining.removeLast();
}
return ans;
}
时间复杂度:O(N log N),其中 N 是A和B的长度
和空间复杂度:O(N)。
总结
当然算法面试题还有很多,肯定远远不止这些。由于文章限制分享就只能到这里了,创作不易,如果对正在面试的你有所帮助的话记得点赞收藏加关注哦!
需要更多面试题和学习资料也都整理好了私信我即可领取!我们下期在见!