目前已经有了两场大厂复习了,分别是滴滴和字节,现在还在字节实习,回想自己找实习的注意的点,那就是算法超级重要,我把能力排一个序,算法>项目经历>八股,所以在互联网这样如此内卷的行业,LeetCode是一定要刷的,下面是我刷题记录,研一的时候刷的得多,今年四五月份找实习,大部分时间都在搞好项目和八股去了,算法这一块很忽视。我这一年实习面试大大小小的面了几十家,有些直接被挂简历,有些被kpi,还有些被捞起来的,总之面试那段时间很累很累。实习就和去年相比,真的是一个天上,一个地下,上一届的学长,人均好几个大厂实习offer,到了我们这,不仅门槛高了,实习的福利也少了(他们实习的时候600-700/day)
算法这一块,刷起来真的很迷糊,之前自己是按照剑指offer,hot一百刷的,后面发现每日一题好多不会,在面试的过程中还有好多题目不会写,于是我开始总结,自己总结算法的小技巧,感觉还是不全,于是子朋友的推荐下面,看了labuladong算法小抄,按照书中的算法思路,在重新去刷题目,总结算法的核心点,面对同样的问题,核心点会了,那这道题目也就会了。地址:labuladong 的算法小抄 :: labuladong的算法小抄,还有另外一个算法书籍,代码随想录,代码随想录,一定要分类去刷题,这样提高效率,多总结,变成自己的东西。下面是我总结几种常用的算法框架和模板,基本上是常考的类型,面试前背一背,基本不慌。在很多时候,面试完你经常会抱怨,那道算法题我之前都会,为什么手撕的时候写不出来,是因为写的少,没有完全背下来,所以你需要去回顾,默写。
快速排序是字节超级喜欢考的题目,不仅要快速写出来,parttion的方法要熟记于心,快速排序还可以解决求第k大的数问题,因为快排parttion找出点是最终点。
public class Main {
public static void main(String[] args) {
int []nums = new int[]{1,3,5,7,9,10,2,4,6,8};
quickSort(nums,0,nums.length-1);
System.out.println(Arrays.toString(nums));
}
public static void quickSort(int []nums , int left , int right){
if (left >= right)return;
int mid = partition(nums , left , right);
quickSort(nums,left,mid-1);
quickSort(nums,mid+1,right);
}
public static int partition(int []nums , int left , int right) {
int flag = left;
int index = left+1;
for (int i = index ; i <= right ; i++){
if(nums[flag] > nums[i]){
swap(nums,index,i);
index ++;
}
}
swap(nums,flag , index - 1);
return index - 1;
}
private static void swap(int[] nums, int i, int j) {
int tep = nums[i];
nums[i] = nums[j];
nums[j] = tep;
}
}
实习面试的时候,字节和腾讯都出过这道题目,背这道算法可以了解优先队列的原理。
public class Main {
public static void main(String[] args) {
int []nums = new int[]{1,3,5,7,9,10,2,4,6,8};
heapSort(nums);
System.out.println(Arrays.toString(nums));
}
public static void heapSort(int []nums){
int len = nums.length;
initHeap(nums,len);
int index = len -1;
for (int i = 0 ; i < len ; i++){
swap(nums, 0 ,index);
heapfying(nums , 0 , index);
index --;
}
}
private static void initHeap(int[] nums, int len) {
for (int i = len / 2 -1 ; i >= 0 ; i--){
heapfying(nums, i , len);
}
}
private static void heapfying(int[] nums, int i, int len) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int max_index = i;
if(left < len && nums[max_index] < nums[left]){
max_index = left;
}
if(right < len && nums[max_index] < nums[right]){
max_index = right;
}
if(max_index != i){
swap(nums,max_index,i);
heapfying(nums,max_index,len);
}
}
private static void swap(int[] nums, int i, int j) {
int tep = nums[i];
nums[i] = nums[j];
nums[j] = tep;
}
}
字节面试出现过,难度不难,要经常背一背。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int []nums = new int[]{1,3,5,7,9,10,2,4,6,8};
mergeSort(nums , 0 , nums.length-1);
System.out.println(Arrays.toString(nums));
// sort(new int []{1,3,2,4}, 0 , 1 , 3);
}
public static void mergeSort(int []nums , int left , int right){
if (left >= right)return;
int mid = left + (right - left) / 2;
mergeSort(nums , left , mid);
mergeSort(nums , mid+1 , right);
sort(nums , left , mid , right);
}
public static void sort(int[] nums, int left , int mid , int right) {
int tep [] = new int[right - left + 1];
int i = left ;
int j = mid + 1;
int index = 0;
while(i <= mid && j <= right){
if(nums[i] > nums[j]){
tep [index] = nums[j];
j ++;
index ++;
}else{
tep [index] = nums[i];
i ++;
index ++;
}
}
while(i <= mid ){
tep [index] = nums[i];
i ++;
index ++;
}
while(j <= right){
tep [index] = nums[j];
j ++;
index ++;
}
index = 0;
for (i = left ; i <= right; i++){
nums[i] = tep[index++];
}
}
}
快速幂和大数相乘是一种思想,将另外一个数转换为二进制的形式去处理,在用十进制和二进制处理的过程中可以考虑取模。
public static int quickPow(int a , int b){
final int INF = 1000000+1; // 超过范围对res取余
int res = 1;
a = a % INF;
while (b != 0){
if ((b & 1) == 1){
res = res * a;
res %= INF;
}
b = b >> 1;
a = a * a;
a = a %INF;
}
return res;
}
public static int bigNumberMultiply(int a , int b){
final int INF = 1000000+1; // 超过范围对res取余
int res = 0;
int tep = a;
while (b != 0){
if ((b & 1) == 1){
res += tep;
res %= INF;
}
b = b >> 1;
tep = tep * a;
}
return res;
}
质数分布规律可以快速判断一个数是否为质数,大于等于5的质数分布在6的倍数左右,但是六的倍数左右不一定是质数。
public static boolean isPrime(int n){
if (n == 2 || n == 3 || n == 5)return true;
if (n == 4 || n == 1)return false;
if (n % 6 != 5 && n % 6 != 1)return false;
for (int i = 5 ; i <= Math.sqrt(n) ; i += 6){
if (n % i == 0 || n % n + 2 == 0){
return false;
}
}
return true;
}
记住迭代公式:x1 = (x0 + n / x0) / 2;
public static double newDun(int n , double x ){
double x0 = n / 2 ;
if (n == 1) x0 = 1;
double x1 = (x0 + n / x0) / 2;
while(Math.abs(x1 - x0)>x){
x0 = x1;
x1 = (x0 + n / x0) / 2;
}
return x1;
}
水塘抽样可以面对变化的范围进行取随机。
public static int selectPond(int n){
int res = 0;
Random r = new Random();
for (int i = 1 ; i <= n ;i++){
if (r.nextInt(i) == 0){
res = i;
}
}
return res;
}
腾讯考过的一道题目,转换思想。
public static int rand10(){
Random r = new Random();
int res = 7 * (r.nextInt(7)) + (r.nextInt(7) + 1);
while(res >41){
res = 7 * (r.nextInt(7)) + (r.nextInt(7) + 1);
}
return res % 10 == 0?10:res % 10;
}
二分搜索可以灵活使用在答案成线性分布的情况,可以用来逼近答案,类似于求数的平方根,逐渐逼近答案。
public static int binarySearch(int []nums , int target){
int left = 0;
int right = nums.length -1;
while (left <= right){
int mid = left + (right - left) / 2;
if(target == nums[mid]){
return mid;
}else if (target > nums[mid]){
left = mid + 1;
}else if (target < nums[mid]){
right = mid - 1;
}
}
return -1;
}
注意写法,在特殊二分场景下需要使用。
public static int leftBound(int []nums , int target){
int left = 0;
int right = nums.length -1;
while (left <= right){
int mid = left + (right - left) / 2;
if(target == nums[mid]){
right = mid - 1;
}else if (target > nums[mid]){
left = mid + 1;
}else if (target < nums[mid]){
right = mid - 1;
}
}
if (left >= nums.length || nums[left] != target){
return -1;
}
return left;
}
与leftBound相对应的。
public static int rightBound(int []nums , int target){
int left = 0;
int right = nums.length -1;
while (left <= right){
int mid = left + (right - left) / 2;
if(target == nums[mid]){
left = mid + 1;
}else if (target > nums[mid]){
left = mid + 1;
}else if (target < nums[mid]){
right = mid - 1;
}
}
if (right < 0 || nums[right] != target){
return -1;
}
return right;
}
public void dfs(){
}
深度遍历是一个特别重要的算法思想,大部分涉及到全排列或者组合的问题,都能解决,类似于全排列,树的遍历,岛屿问题呀等等。在涉及到组合问题的时候可以采用数组有序,和选择项if(i > 0 &&dp[i] != dp[i-1])的方式实现组合去重复(例题:数组划分为k个相等的子数组)。
public static void bfs(){
LinkedList<> que = new LinkedList();
que.add(root)
while (!que.isEmpty()){
int size = que.size();
for (int i = 0 ; i < size ; i++){
int cv = que.getFirst();
que.removeFirst();
// 将cv的子节点加入que
}
}
}
import java.util.Random;
class unionFind{
int count; // 连通分量的个数
int parent[]; //指向父节点
int []size; // 节点重量
public unionFind (int n){
size = new int[n];
parent = new int[n];
for (int i = 0 ; i < n ; i++){
size[i] = 1;
parent[i] = i;
}
count = n;
}
public void union(int p , int q){
int r1 = findRoot(p);
int r2 = findRoot(q);
if (r1 == r2)return;
if (size[r1] > size[r2]){
parent[r2] = r1;
size[r1] += size[r2];
}else{
parent[r1] = r2;
size[r2] += size[r1];
}
count --;
}
public boolean isConnect(int p , int q){
return findRoot(p) == findRoot(q);
}
public int findRoot(int p){
int res = parent[p];
while (res!=parent[res]){
res = parent[res];
}
return res;
}
public int getCount(){
return count;
}
}
dijkstra算法不适用于边值为负的情况。
public class Main {
public static void main(String[] args) {
int [][] distance = new int[][]{{1,2,3},{2,3,1},{0,4,3},{2,4,4},{1,4,6}};
System.out.println(dijkstra(distance, 5, 1, 4));
}
/*
* 单源最短距离-dijkstra算法
* int[][] distance距离
* int n总结点数
* int src源点
* int dest目标点
*
* */
public static int dijkstra(int[][] distance, int n, int src, int dest) {
int []costs = new int[n];
boolean []visited = new boolean[n];
final int INF = 1000*1000+1;
Arrays.fill(costs,INF);
costs[src] = 0;
for (int []cv : distance){
int i = cv[0];
int j = cv[1];
int cost = cv[2];
if (i == src){
costs[j] = cost;
}
}
visited[src] = true;
for (int t = 1 ; t <= n - 1; t++){
int post = 0;
int min = Integer.MAX_VALUE;
for (int k = 0 ; k < n; k++){
if(!visited [k] && min > costs[k]){
min = costs[k];
post = k;
}
}
visited[post] = true;
for (int []cv : distance){
int i = cv[0];
int j = cv[1];
int cost = cv[2];
if (post == i && !visited[j] && costs[j] > costs[i] + cost){
costs[j] = costs[i] + cost;
}
}
}
return costs[dest] == INF ? -1 : costs[dest];
}
}
bellman ford算法可以适用于限定步数的最短路径,采用dp[distance][n]来保证。
public class Main {
public static void main(String[] args) {
int [][] distance = new int[][]{{1,2,3},{2,3,1},{0,4,3},{2,4,2},{1,4,6}};
System.out.println(bellmanFord(distance, 5, 1, 4));
}
/*
* 单源最短距离-bellman ford算法
* int[][] distance距离
* int n总结点数
* int src源点
* int dest目标点
*
* */
public static int bellmanFord(int[][] distance, int n, int src, int dest) {
int []costs = new int[n];
final int INF = 1000*1000+1;
Arrays.fill(costs,INF);
costs[src] = 0;
for (int t = 1 ; t <= n - 1; t++){
boolean flag = true;
for (int []cv : distance){
int i = cv[0];
int j = cv[1];
int cost = cv[2];
if (costs[j] > costs[i] + cost){
costs[j] = costs[i] + cost;
flag = false;
}
}
if (flag){
return costs[dest] == INF ? -1 : costs[dest];
}
}
for (int []cv : distance){
int i = cv[0];
int j = cv[1];
int cost = cv[2];
if (costs[j] > costs[i] + cost){
return -1;
}
}
return costs[dest] == INF ? -1 : costs[dest];
}
}
package alian;
import java.util.HashMap;
class Main{
public static void main(String[] args) {
LRU lru = new LRU(3);
lru.set("1",new Integer(1));
lru.set("2",new Integer(2));
System.out.println(lru.get("1") != null);
lru.set("3",new Integer(3));
System.out.println(lru.get("3") != null);
lru.set("4",new Integer(4));
System.out.println(lru.get("1") == null);
}
}
class LRU{
HashMap map ;
Node head,tail;
int n ;
int size ;
public LRU(int n) {
map = new HashMap<>();
head = new Node();
tail = new Node();
head.next = tail;
tail.pre = head;
size = 0;
this.n = n;
}
class Node{
String key;
Object val;
Node pre;
Node next;
}
public Node get(String key){
if (!map.containsKey(key)){
return null;
}
Node cv = map.get(key);
// cv从双链表中断开
Node pre1 = cv.pre;
Node next1 = cv.next;
pre1.next = next1;
next1.pre = pre1;
//插入到head之后
head.next.pre = cv;
cv.next = head.next;
head.next = cv;
cv.pre = head;
return cv;
}
public void set(String key , Object val){
if (!map.containsKey(key)){
Node cv = new Node();
cv.key = key;
cv.val = val;
size++;
head.next.pre = cv;
cv.next = head.next;
head.next = cv;
cv.pre = head;
map.put(key , cv);
if (size > n){
size --;
Node tep = tail.pre.pre;
map.remove(tail.pre.key);
tep.next = tail;
tail.pre = tep;
}
}else {
Node cv = map.get(key);
cv.val = val;
Node pre1 = cv.pre;
Node next1 = cv.next;
pre1.next = next1;
next1.pre = pre1;
head.next.pre = cv;
cv.next = head.next;
head.next = cv;
cv.pre = head;
}
}
}
大厂面试喜欢常考的,注意点是考虑到双链表操作。