程序员刷力扣算法的正确打开方式,学会解题思路,领会其中思想,刷完涨薪20k

1,570 阅读10分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

上周去朋友去某公司面试,结果在被面试官问到算法时,直接给整不会了,于是我特意整理出来一套大厂高频面试算法题,朋友拿着我这套题库刷了一星期的力扣算法题终于如愿拿到offer,事后朋友说好像算法也没那么难,主要是多理解就好,现在把这套题分享给大家,废话不多说,直接上干货

反转链表

反转一个单链表。

输入: 1->2->3->4->5

输出: 5->4->3->2->1

解法1:迭代,重复某一过程,每一次处理结果作为下一次处理的初始值,这些初始值类似于状态、每 次处理都会改变状态、直至到达最终状态

从前往后遍历链表,将当前节点的next指向上一个节点,因此需要一个变量存储上一个节点prev,当前节点处理完需要寻找下一个节点,因此需要一个变量保存当前节点curr,处理完后要将当前节点赋值给

prev,并将next指针赋值给curr,因此需要一个变量提前保存下一个节点的指针next

image.png  

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:埃氏筛

利用合数的概念(非素数),素数*n必然是合数,因此可以从2开始遍历,将所有的合数做上标记

public static int eratosthenes(int n) { boolean[] isPrime = new boolean[n]; int ans = 0;

for (int i = 2; i < n; i++) { if (!isPrime[i]) {

ans += 1;

for (int j = i * i; j < n; j += i) { isPrime[j] = true;

}

}

}

return ans;

}

将合数标记为true,j = i * i 从 2 * i 优化而来,系数2会随着遍历递增(j += i,相当于递增了系数2), 每一个合数都会有两个比本身要小的因子(0,1除外),2 * i 必然会遍历到这两个因子

当2递增到大于根号n时,其实后面的已经无需再判断(或者只需判断后面一段),而2到根号n、实际 上在 i 递增的过程中已经计算过了,i 实际上就相当于根号n

例如:n = 25 会计算以下

2 * 4 = 8

3 * 4 = 12

但实际上8和12已经标记过,在n = 17时已经计算了 3 * 4,2 * 4

寻找数组的中心索引

数组中某一个下标,左右两边的元素之后相等,该下标即为中心索引思路:先统计出整个数组的总和,然后从第一个元素开始叠加

总和递减当前元素,叠加递增当前元素,知道两个值相等

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]);

}

解法二:略

两数之和

给定一个升序排列的整数数组 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)

斐波那契数列

求取斐波那契数列第N位的值。

斐波那契数列:每一位的值等于他前两位数字之和。前两位固定 0,1,1,2,3,5,8。。。。

解法一:暴力递归

image.png

public static int calculate(int num){ if(num == 0 ){

return 0;

}

if(num == 1){

return 1;

}

return calculate(num-1) + calculate(num-2);

}

环形链表

给定一个链表,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达该节点,则链表中存在环如果链表中存在环,则返回 true 。 否则,返回 false 。

解法一:哈希表

public static boolean hasCycle(ListNode head) { Set seen = new HashSet(); while (head != null) {

if (!seen.add(head)) { return true;

}

head = head.next;

}

return false;

}

排列硬币

总共有 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)。

子数组最大平均数

给一个整数数组,找出平均数最大且长度为滑动窗口:

image.png 窗口移动时,窗口内的和等于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;

}

最长连续递增序列****

给定一个未经排序的整数数组,找到最长且连续递增的子序列,并返回该序列的长度。 序列的下标是连续的

贪心算法

从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;

}

三角形的最大周长

给定由一些正数(代表长度)组成的数组  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;

}

香槟塔

我们把玻璃杯摆成金字塔的形状,其中第一层有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]);

}

总结

当然算法面试题还有很多,肯定远远不止这些。由于文章限制分享就只能到这里了,创作不易,如果对正在面试的你有所帮助的话记得点赞收藏加关注哦!
更多面试题和学习资料也都整理好了!我们下期在见!