LeetCode刷题记录,腾讯T2大牛手把手教你

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Linux运维全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上运维知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注运维)
img

正文

2024/2/20

从前序与中序遍历序列构造二叉树
主要逻辑:
先序:根左右
中序:左根右
根据先序确定根,然后在中序中找到根,划分子树。根之前为左子树,根之后为右子树。
之后继续划分,直到无法细分为止。
我的做法:按照中序进行划分区间,找区间内谁第一个在先序中出现。
题解(更好的做法):先前序,后中序。这样可以一次找出结果,而不需要遍历。

//我的做法
class Solution {
    Map<Integer,Integer> preorderIndex;
    public TreeNode dfs(int l,int r,int[] preorder,int[] inorder){
        if(r - l == 0){
            return new TreeNode(inorder[l],null,null);
        }
        int minIndex = -1;
        int mins = 3005;
        for(int i = l;i <= r;i++){
            if(preorderIndex.get(inorder[i]) < mins){
                mins = preorderIndex.get(inorder[i]);
                minIndex = i;
            }
        }
        //此时已经找到根
        TreeNode root = new TreeNode(inorder[minIndex],null,null);
        if(minIndex != l){
            root.left = dfs(l,minIndex - 1,preorder,inorder);
        }
        if(minIndex != r){
            root.right = dfs(minIndex + 1,r,preorder,inorder);
        }
        return root;
    }
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        preorderIndex = new HashMap<>();
        int n = preorder.length;
        for(int i = 0;i < n;i++){
            preorderIndex.put(preorder[i],i);
        }
        TreeNode root = dfs(0,n - 1,preorder,inorder);
        return root;
    }
}
//题解的做法
class Solution {
    Map<Integer,Integer> inorderIndex;
    public TreeNode dfs(int pl,int pr,int il,int ir,int[] preorder,int[] inorder){
        if(pl > pr)return null;
        int rootIndex = inorderIndex.get(preorder[pl]);//找到了根的位置,可以计算出左子树的长度和右子树的长度
        //此时已经找到根
        TreeNode root = new TreeNode(inorder[rootIndex],null,null);
        //计算左子树长度
        int left_size = rootIndex - il;
        //构造左子树
        root.left = dfs(pl + 1,pl + left_size,il,rootIndex - 1,preorder,inorder);
        //计算右子树长度
        int right_size = ir - rootIndex;
        //构造右子树
        root.right = dfs(pl + left_size + 1,pr,rootIndex + 1,ir,preorder,inorder);
        return root;
    }
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        inorderIndex = new HashMap<>();
        int n = preorder.length;
        for(int i = 0;i < n;i++){//找到先序数据在inorder中的位置
            inorderIndex.put(inorder[i],i);
        }
        TreeNode root = dfs(0,n - 1,0,n - 1,preorder,inorder);
        return root;
    }
}

2024/2/21

从中序与后序遍历序列构造二叉树
中序:左中右
后序:左右中
[[左子树]根[右子树]]
[[左子树][右子树]根]
逆序遍历postorder数组,找到根,利用Map记录根在inorder中的index,完成操作。

class Solution {
    Map<Integer,Integer> map = new HashMap<>();
    TreeNode dfs(int il,int ir,int pl,int pr,int[] inorder,int[] postorder){
        if(pr < pl)return null;
        //获取根在中序中的位置
        int rootIndex = map.get(postorder[pr]);
        //创建根节点
        TreeNode root = new TreeNode(postorder[pr]);
        //获取左右子树长度
        int left_size = rootIndex - il;
        int right_size = ir - rootIndex;
        //遍历左右子树
        root.left = dfs(il,rootIndex - 1,pl,pl + left_size - 1,inorder,postorder);
        root.right = dfs(rootIndex + 1,ir,pr - right_size,pr - 1,inorder,postorder);
        return root;
    }
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        int n = postorder.length;
        for(int i = 0;i < n;i++){
            map.put(inorder[i],i);
        }
        return dfs(0,n - 1,0,n - 1,inorder,postorder);
    }
}

2024/2/22

根据前序和后序遍历构造二叉树
先序:根左右
后序:左右根
[根,左子树,右子树]
[左子树,右子树,根]
存在多个答案,即遍历合理即可。
根后的第一个元素,是左子树的根,可以找到对应于后续的index,然后算出左子树的长度。

class Solution {
    Map<Integer,Integer> map = new HashMap<>();
    TreeNode dfs(int preL,int preR,int postL,int postR,int[] preorder,int[] postorder){
        if(preR < preL) return null;
        //创建root
        TreeNode root = new TreeNode(preorder[preL]);
        //计算左子树长度
        int leftSize = preL + 1 < preR ? map.get(preorder[preL + 1]) - postL + 1: 0;
        //计算右子树长度
        int rightSize = preR - preL - leftSize;
        //算出左右子树
        root.left = dfs(preL + 1,preL + leftSize,postL,postL + leftSize - 1,preorder,postorder);
        root.right = dfs(preL + leftSize + 1,preR,postL + leftSize,postR - 1,preorder,postorder);
        return root;
    }
    public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {
        int n = preorder.length;
        for(int i = 0;i < n;i++){
            map.put(postorder[i],i);
        }
        return dfs(0,n-1,0,n-1,preorder,postorder);
    }
}

2024/2/23

二叉树中的第 K 大层和
自己的思路:深度遍历+map记录+sort排序。但是这样很浪费时间,不是最优。
应该的做法:BFS+双队列记录当前层次的所有节点+排序,节约时间。

//自己做法
class Solution {
    Map<Integer,Long> map = new HashMap<Integer,Long>();
    public void dfs(int index,TreeNode root){
        if(root == null)return;
        map.put(index,map.getOrDefault(index,0L) + root.val);
        dfs(index + 1,root.left);
        dfs(index + 1,root.right);
    }
    public long kthLargestLevelSum(TreeNode root, int k) {
        dfs(1,root);
        Set<Integer> set = map.keySet();
        List<Long> list = new ArrayList<>();
        for(Integer key:set){
            list.add(map.get(key));
        }
        if(k > list.size())return -1L;
        Collections.sort(list,Collections.reverseOrder());
        return list.get(k - 1);
    }
}
//题解
class Solution {
    public long kthLargestLevelSum(TreeNode root, int k) {
        List<Long> a = new ArrayList<>();
        List<TreeNode> q = List.of(root);
        while (!q.isEmpty()) {
            long sum = 0;
            List<TreeNode> tmp = q;
            q = new ArrayList<>();
            for (TreeNode node : tmp) {
                sum += node.val;
                if (node.left != null)  q.add(node.left);
                if (node.right != null) q.add(node.right);
            }
            a.add(sum);
        }
        int n = a.size();
        if (k > n) {
            return -1;
        }
        Collections.sort(a);
        return a.get(n - k);
    }
}

2024/2/24

二叉搜索树最近节点查询
BFS + 二分。
二分需要再学一学,模板有问题。

/\*
BFS + 二分
 \*/
class Solution {
    public List<List<Integer>> closestNodes(TreeNode root, List<Integer> queries) {
        Queue<TreeNode> pq = new LinkedList<>();
        List<Integer> querySum = new ArrayList<>();
        pq.offer(root);
        while(!pq.isEmpty()){
            Queue<TreeNode> lq = pq;
            pq = new LinkedList<>();
            for(TreeNode temp:lq){
                querySum.add(temp.val);
                if(temp.left != null)pq.offer(temp.left);
                if(temp.right != null)pq.offer(temp.right);
            }
        }
        Collections.sort(querySum);
        List<List<Integer>> ans = new ArrayList<>();
        // return ans;
        int n = querySum.size();
        int[] a = new int[n];
        for(int i = 0;i < n;i++)a[i] = querySum.get(i);
        for(Integer i:queries){
            int j = lowerBound(a, i);
            int mx = j == n ? -1 : a[j];
            if (j == n || a[j] != i) { // a[j]>i, a[j-1]<i
                j--;
            }
            int mn = j < 0 ? -1 : a[j];
            ans.add(List.of(mn, mx));
        }
        return ans;
    }
    private int lowerBound(int[] a, int target) {
        int left = -1, right = a.length; // 开区间 (left, right)
        while (left + 1 < right) { // 区间不为空
            int mid = (left + right) >>> 1; // 比 /2 快
            if (a[mid] >= target) {
                right = mid; // 范围缩小到 (left, mid)
            } else {
                left = mid; // 范围缩小到 (mid, right)
            }
        }
        return right;
    }
}

2024/2/25

二叉搜索树的最近公共祖先
我的做法:遍历每个结点,然后记录路径,最后使用双层循环进行匹配最近节点。
题解:p、q一起遍历,对于当前root如果是相同的比较结果,证明不是分岔点,如果有不同的比较结果,那么就是分岔点,当前root就为最终结果。

//我的做法
class Solution {
    List<TreeNode> list;
    public void dfs(TreeNode root,TreeNode x){
        if(root == null){
            return;
        }
        list.add(root);
        if(root.val == x.val)return;
        if(root.val > x.val)dfs(root.left,x);
        else dfs(root.right,x);
    }
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        list = new ArrayList<>();
        dfs(root,p);
        List<TreeNode> pList = list;
        list = new ArrayList<>();
        dfs(root,q);
        int n = pList.size();
        int m = list.size();
        for(int i = n - 1;i >=0;i--){
            for(int j = m - 1;j >= 0;j--){
                if(pList.get(i).val == list.get(j).val){
                    return pList.get(i);
                }
            }
        }
        return null;
    }
}
//题解:
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        TreeNode ancestor = root;
        while (true) {
            if (p.val < ancestor.val && q.val < ancestor.val) {
                ancestor = ancestor.left;
            } else if (p.val > ancestor.val && q.val > ancestor.val) {
                ancestor = ancestor.right;
            } else {
                break;
            }
        }
        return ancestor;
    }
}

2024/2/26

二叉搜索树的范围和
我的思路:中序遍历+遍历求解。比较浪费时间,其实可以dfs的时候就可以进行求解。
题解思路:判断是否遍历左右子树,直接求解。

//我的
class Solution {
    List<Integer> list = new ArrayList<>();
    public void dfs(TreeNode root){
        if(root == null)return;
        dfs(root.left);
        list.add(root.val);
        dfs(root.right);
    }
    public int rangeSumBST(TreeNode root, int low, int high) {
        //中序遍历,并求值
        dfs(root);
        int n = list.size();
        int ans = 0;
        for(int i = 0;i < n;i++){
            if(list.get(i) > high)break;
            if(list.get(i) >= low)ans+=list.get(i);
        }
        return ans;
    }
}
//题解
class Solution {
    public int dfs(TreeNode root,int low,int high){
        if(root == null)return 0;
        if(root.val > high){
            return dfs(root.left,low,high);
        }
        if(root.val < low){
            return dfs(root.right,low,high);
        }
        return root.val + dfs(root.left,low,high) + dfs(root.right,low,high);
    }
    public int rangeSumBST(TreeNode root, int low, int high) {
        return dfs(root,low,high);
    }
}

2024/2/27

统计树中的合法路径项目
冥思苦想1.30个小时,没找到正确的思路。被提示误导,以为就是深搜之后根据不同的类型进行动态规划。
正确的题解:
1.利用埃氏筛计算质数
2.枚举所有质数节点,对于当前的质数来说,会将整个树划分为多个连通块。对于每个连通块,记录所有合法的路径(即全是合数的路径,这样可以与当前质数节点形成一条合法的路径)。此时,不同连通块之间的合数路径,可以通过当前的质数节点进行相连,两者之间进行选择,此时为最终的结果。
在这里插入图片描述

class Solution {
    private final static int MX = (int)1e5;
    private final static boolean[] prime = new boolean[MX + 5];//质数为false,非质数为true
    static{
        prime[1] = true;
        for(int i = 2;i \* i <= MX;i++){
            if(prime[i])continue;
            for(int j = i \* i;j <= MX;j+=i){
                prime[j] = true;
            }
        }
    }
    public long countPaths(int n, int[][] edges) {
        //将树建为图
        List<Integer>[] g = new ArrayList[n + 1];
        Arrays.setAll(g,e -> new ArrayList<>());//建立邻接表
        for(int i = 0;i < n - 1;i++){
            int x = edges[i][0];
            int y = edges[i][1];
            g[x].add(y);
            g[y].add(x);
        }
        long ans = 0;
        int[] size = new int[n + 1];//一个trick,用于记录是否遍历过,以及记录
        List<Integer> nodes = new ArrayList<>();//用于记录当前连通块数量
        for(int x = 1;x <= n;x++){
            if(prime[x])continue;//跳过质数
            int sums = 0;
            for(int y:g[x]){//当前质数x,将整棵树划分为了多个子连通块
                if(!prime[y])continue;//如果当前为质数,则跳过
                if(size[y] == 0){//当前节点的连通合数并未计算
                    nodes.clear();//用于统计从当前点出发能遍历到的所有合数
                    dfs(y,-1,g,nodes);//遍历y所在的连通块,在不经过质数的情况,能有多少非质数。
                    for(int z:nodes){// 连通块中的节点能到达的合数数量是一致的,记录
                        size[z] = nodes.size();
                    }
                }
                //这size[y]个质数与之前遍历的sum个非质数,两两之间的路径只包含当前x
                ans += (long) size[y] \* sums;
                sums += size[y];
            }
            ans += sums;
        }
        return ans;
    }
    private void dfs(int x,int fa,List<Integer>[] g,List<Integer> nodes){
        //fa保证不往回走
        nodes.add(x);
        for(int y:g[x]){
            if(y != fa && prime[y]){
                dfs(y,x,g,nodes);
            }
        }
    }
}

2024/2/28

使二叉树所有路径值相等的最小代价
我的思路:1.只能增加,不能减少,因此只能向最大值进行靠近。2.若同属于一个根节点的两个子节点都需要更新,那么就更新两者最小值到父节点,递归进行。
题解思路:对于两个有相同根的叶子节点来说,除了自己以外其余所有路径都相同,因此只需要将小的往大的靠就可以实现路径相等。对于不是叶子的兄弟节点,从根到当前节点的路径,除了这两个兄弟节点不一样,其余节点都一样,所以把路径和从叶子往上传,这样就可以按照叶子节点的方式进行比较和更新了。

//我的做法:
class Solution {
    int[] addCost;
    public void dfs(int index,int[] pathCost,int maxs,int n){
        if(index > n)return;
        dfs(index << 1,pathCost,maxs,n);
        dfs(index << 1 | 1,pathCost,maxs,n);
        if(index \* 2 > n){//子节点
            if(pathCost[index] < maxs){
                addCost[index] = maxs - pathCost[index];
            }
            return;
        }
        //非子节点,判断子节点更新的次数
        int mins = Math.min(addCost[index \* 2],addCost[index \* 2 + 1]);
        addCost[index] += mins;
        addCost[index \* 2] -=mins;
        addCost[index \* 2 + 1] -= mins;
    }
    public int minIncrements(int n, int[] cost) {
        /\*
 1.只能增加,不能减少,因此只能向最大值进行靠近
 2.若同属于一个根节点的两个子节点都需要更新,那么就更新两者最小值到父节点,递归进行。
 \*/
        int[] pathCost = new int[n + 1];
        pathCost[0] = 0;
        int maxs = -1;//最大权值
        for(int i = 0;i < n;i++){
            int index = i + 1;
            pathCost[index] = pathCost[index / 2] + cost[i]; 
            maxs = Math.max(maxs,pathCost[index]);
        }
        addCost = new int[n + 1];
        Arrays.fill(addCost,0);
        dfs(1,pathCost,maxs,n);
        int ans = 0;
        for(int i = 1;i <= n;i++)ans+=addCost[i];
        return ans;
    }
}
//题解做法:
class Solution {
    public int minIncrements(int n, int[] cost) {
        int ans = 0;
        for (int i = n / 2; i > 0; i--) { // 从最后一个非叶节点开始算
            ans += Math.abs(cost[i \* 2 - 1] - cost[i \* 2]); // 两个子节点变成一样的
            cost[i - 1] += Math.max(cost[i \* 2 - 1], cost[i \* 2]); // 累加路径和
        }
        return ans;
    }
}

2024/2/29

统计可能出现的树根数目
未做出来的题:虽然有想到根与子树之间的换根,只需要记录交换根本身与子节点之间的序列变化,即可完成该题,但对于如何记录以及求得以root为根的树正确的猜测,还是有点无从下手。
该题是一个换根DP问题,前置知识为:树中距离之和
在这里插入图片描述
子树大小计算:后序遍历并统计。
保证每个节点只递归访问1次:对于图来说,使用vis数组记录每个点的访问次数。但是对于树来说,一直向下递归,就不会遇到之前访问的点,所以不需要数组,只需要避免重复访问父节点即可。
树中距离之和:

class Solution {
    private List<Integer>[] g;
    private int[] ans,size;
    public int[] sumOfDistancesInTree(int n, int[][] edges) {
        g = new ArrayList[n];
        Arrays.setAll(g,e -> new ArrayList<>());
        for(int[] e:edges){
            g[e[0]].add(e[1]);
            g[e[1]].add(e[0]);
        }
        ans = new int[n];
        size = new int[n];
        Arrays.fill(ans,0);
        dfs(0,-1,0);
        reboot(0,-1);
        return ans;
    }
    private void dfs(int index,int fa,int depth){
        ans[0] += depth;
        size[index] = 1;
        for(int y:g[index]){
            if(y != fa){
                dfs(y,index,depth + 1);
                size[index] += size[y];
            }
        }
    }
    private void reboot(int x,int fa){
        for(int y:g[x]){
            if(y!=fa){
                ans[y] = ans[x] + g.length - 2 \* size[y];//ans[y] = ans[x] - size[y] + (n - size[y])
                reboot(y,x);//以y为根,x为父节点
            }
        }
    }
}

对于本题来说:
如果节点x和y相邻,那么[以x为根的树]变为[以y为根的树],就只有x和y的父子关系改变了,其余相邻节点之间的父子关系没有改变。所以只有[x,y]和[y,x]这两个猜测的正确性变化了,其余猜测的正确性不变。
因此,在计算出以0为根的cnt之后,可以再次从0出发,DFS这颗树。从节点x递归到节点y时:
若有猜测[x,y],那么猜对的次数-1。
若有猜测[y,x],那么猜对的次数+1。
DFS的同时,统计猜对次数>=k的节点个数,即为答案。
这里我所担心的:可能出现的可行解但是次序相反的问题不会出现,所以建树即可。
统计可能出现的树根数目:

class Solution {
    private List<Integer>[] g;
    private Set<Long> set;
    int k,ans,cnt0;
    public int rootCount(int[][] edges, int[][] guesses, int k) {
        this.k = k;
        //建树
        g = new ArrayList[edges.length + 1];
        Arrays.setAll(g,e -> new ArrayList<>());
        for(int[] e:edges){
            g[e[0]].add(e[1]);
            g[e[1]].add(e[0]);
        }
        //将guesses映射到set中,将2个4字节映射为1个8字节。
        set = new HashSet<>();
        for(int[] guess:guesses){
            set.add((long)guess[0] << 32 | guess[1]);
        }
        cnt0 = 0;
        dfs(0,-1);
        ans = 0;
        reroot(0,-1,cnt0);
        return ans;
    }
    private void dfs(int x,int fa){
        for(int y:g[x]){
            if(y != fa){
                if(set.contains((long)x << 32 | y))cnt0++;
                dfs(y,x);
            }
        }
    }
    private void reroot(int x,int fa,int cnt){
        if(cnt >= k)ans++;
        for(int y:g[x]){
            if(y != fa){
                int c = cnt;
                if(set.contains((long)x << 32 | y))c--;
                if(set.contains((long)y << 32 | x))c++;
                reroot(y,x,c);
            }
        }
    }
}

2024/3/1

检查数组是否有效划分
dp问题,一开始自己是把模型都建立出来了,但是却错误纠结覆盖子数组中是否有被其它数组挪用的情况。对于f[i]来说,只要前2个或者3个是能划分的,那么就能判定当前的子数组是否能划分。
也就是说,这里我思考的:
dp[i]前i个数字是否能有效划分
对于i来说,若能划分,有这些情况:
1.dp[i - 1]能划分,但dp[i - 2]不能划分,且nums[i - 1] = nums[i - 2] = nums[i]
2.dp[i - 1]不能划分,但dp[i - 2]能划分,且nums[i - 1] = nums[i]
3.dp[i - 1]不能划分,且dp[i - 2]也不能划分,但dp[i - 3]能划分,且有nums[i] = nums[i - 1] + 1 = nums[i - 2] + 2
只需要考虑能划分,不需要思考中间的数据不被划分的情况。
此时可以进行递推,得到最终的结果。

class Solution {
    public boolean validPartition(int[] nums) {
        /\*
 dp[i]前i个数字是否能有效划分
 对于i来说,若能划分,有这些情况:
 1.dp[i - 1]能划分,但dp[i - 2]不能划分,且nums[i - 1] = nums[i - 2] = nums[i]
 2.dp[i - 1]不能划分,但dp[i - 2]能划分,且nums[i - 1] = nums[i]
 3.dp[i - 1]不能划分,且dp[i - 2]也不能划分,但dp[i - 3]能划分,且有nums[i] = nums[i - 1] + 1 = nums[i - 2] + 2
 会不会出现这种情况:
 4,4,4,4
 \*/
        int n = nums.length;
        boolean[] f = new boolean[n + 1];
        f[0] = true;
        for(int i = 1;i < n;i++){
            if (f[i - 1] && (nums[i] == nums[i - 1])||
                i > 1 && f[i - 2] && ((nums[i] == nums[i - 1] && nums[i - 1] == nums[i - 2]) ||
                nums[i] == nums[i - 1] + 1 && nums[i] == nums[i - 2] + 2))
                f[i + 1] = true;
        }
        return f[n];
    }
}

2024/3/2

受限条件下可到达节点的数量
DFS遍历,唯一注意点的就是不要遍历到父节点。

class Solution {
    int cnt = 0;

    public int reachableNodes(int n, int[][] edges, int[] restricted) {
        boolean[] isrestricted = new boolean[n];
        for (int x : restricted) {
            isrestricted[x] = true;
        }

        List<Integer>[] g = new List[n];
        for (int i = 0; i < n; i++) {
            g[i] = new ArrayList<Integer>();
        }
        for (int[] v : edges) {
            g[v[0]].add(v[1]);
            g[v[1]].add(v[0]);
        }
        dfs(0, -1, isrestricted, g);
        return cnt;
    }

    public void dfs(int x, int f, boolean[] isrestricted, List<Integer>[] g) {
        cnt++;
        for (int y : g[x]) {
            if (y != f && !isrestricted[y]) {
                dfs(y, x, isrestricted, g);
            }
        }
    }
}

2024/3/3

用队列实现栈
简单题,运用Deque即可。
offerLast,peekLast,pollLast,offerFirst,peekFirst,pollFirst,new LinkedList<>()

class MyStack {
    private Deque<Integer> deque;
    public MyStack() {
        this.deque = new LinkedList<>();
    }
    
    public void push(int x) {
        deque.offerLast(x);
    }
    
    public int pop() {
        return deque.pollLast();
    }
    
    public int top() {
        return deque.peekLast();
    }
    
    public boolean empty() {
        return deque.isEmpty();
    }
}

删除有序数组中的重复项
面试150题中的中等题。
我的思路就是纯模拟,时间复杂度略高,代码量也大一点。对于双指针运用有点不熟练,需要多练习。
题解思路:利用slowfast双指针并行操作,slow用于更新,fast用于遍历。由于数组以及排好序了,所以对当前的fast来说,只要当前值与slow - 2的值不重复,就代表满足条件,否则slow就更新。

//我的代码
class Solution {
    public int removeDuplicates(int[] nums) {
        //找需要替换的子数组开始和结束坐标
        int i = 0;
        int pre = -1;
        int ans = nums.length;
        int cnt = 0;
        int from,to;
        while(true){
            if(i >= ans)return ans;
            if(nums[i] != pre){
                cnt = 1;
                pre = nums[i];
                i++;
                continue;
            }
            cnt++;
            if(cnt > 2){
                from = i;
                while(i + 1 < ans && nums[++i] == pre){
                    cnt++;
                }
                int j = i;
                int k = from;
                while(j < ans){
                    nums[k++] = nums[j++];
                }
                ans -= (cnt - 2);
                i = from;
            }
            else i++;
        }
    }
}
//题解:
class Solution {
    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        if(n <= 2)return n;//对于数组长度为2的,直接返回
        int fast = 2,slow = 2;
        while(fast < n){
            if(nums[slow - 2] != nums[fast]){
                nums[slow++] = nums[fast];
            }
            fast++;
        }
        return slow;
    }
}

2024/3/4

栈实现队列
Deque操作即可。

class MyQueue {
    Deque<Integer> deque;
    public MyQueue() {
        this.deque = new LinkedList<Integer>();
    }
    
    public void push(int x) {
        deque.addLast(x);
    }
    
    public int pop() {
        return deque.pollFirst();
    }
    
    public int peek() {
        return deque.peekFirst();
    }
    
    public boolean empty() {
        return deque.isEmpty();
    }
}

轮转数组
第一种方法:使用额外数组存储。
第二种方法(题解):首先,将数组进行整体反转,此时后k % n位到前面来了。然后将前k位进行反转,得到正确的k % n位顺序,最后将后 n - (k % n)位进行反转,得到正确的前置顺序。

class Solution {
    public void rotate(int[] nums, int k) {
        // for(int i = 0;i < k;i++)rotateOneStep(nums);//这样O(nk)会超时
        //后k个数可以视为一个子数组,顺序是不变的,依次放入即可,空间复杂度为O(k)
        
        int[] kNums = new int[k];
        int n = nums.length;
        k = k % n;
        if(n == 1)return;
        for(int j = 0;j < k;j++){
            kNums[j] = nums[j + n - k];
        }
        for(int i = n - k - 1;i >= 0;i--){
            nums[i + k] = nums[i];
        }
        for(int i = 0;i < k;i++){
            nums[i] = kNums[i];
        }
    }
    public void rotateOneStep(int[] nums){
        int n = nums.length;
        int last = nums[n - 1];
        for(int i = n - 1;i > 0;i--){
            nums[i] = nums[i - 1];
        }
        nums[0] = last;
    }
}
//题解:
class Solution {
    public void rotate(int[] nums, int k) {
        /\*
 首先,将数组进行整体反转,此时后k % n位到前面来了
 然后将前k位进行反转,得到正确的k % n位顺序
 最后将后 n - (k % n)位进行反转,得到正确的前置顺序
 \*/
        int n = nums.length;
        k %= n;
        reverse(nums,0,n - 1);
        reverse(nums,0,k - 1);
        reverse(nums,k,n - 1);
    }
    public void reverse(int []nums,int s,int e){
        int temp;
        while(s < e){
            temp = nums[s];
            nums[s] = nums[e];
            nums[e] = temp;
            s++;
            e--;
        }
    }
}

买卖股票的最佳时机I
从前往后,记录直到现在的最小购入价格,同时计算最大利润。

class Solution {
    public int maxProfit(int[] prices) {
        int mins = 10005;
        int ans = 0;
        for(int i = 0;i < prices.length;i++){
            ans = Math.max(ans,prices[i] - mins);
            mins = Math.min(mins,prices[i]);
        }
        return ans;
    }
}

买卖股票的最佳时机II
我的思路:逆序递推,dp[i]从当天开始进行操作能获取到的最大利润。若当天选择不买入,则dp[i] = dp[i + 1],若当天选择买入,则枚举后续值是否有操作比当前还大。时间复杂度为(O(n2)),案例较弱,让我混过去了。
题解dp:dp[i][0]表示第i天交易完后手里没有股票的最大利润,dp[i][1]代表第i天交易完后手里持有一只股票的最大利润。
dp[i][0] = max(dp[i-1][0],dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i - 1][1],dp[i - 1][0] - prices[i])
dp[0][0] = 0
dp[0][1] = -prices[0]
最后答案为dp[n-1][0]
题解贪心:题目中给定的描述是,可以当天买入,当天卖出。所以即时第三天直接卖出的利润比第四天卖出的利润要小,但是由于这个设置,我们可以在第三天卖出之后再买入,再在第四天卖出,实际的利润也等于第四天直接卖出。所以只需要统计所有上升的窗口为2的数组即可。

//我的dp
class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int[] dp = new int[prices.length + 1];
        dp[n - 1] = 0;
        dp[n] = 0;
        int ans = 0;
        for(int i = n - 2;i >= 0;i--){
            dp[i] = dp[i + 1];//当前不买
            for(int j = i + 1;j < n;j++){
                dp[i] = Math.max(dp[i],prices[j] - prices[i] + dp[j + 1]);
            }
            ans = Math.max(ans,dp[i]);
        }
        return ans;
    }
}
//题解dp
class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int[][] dp = new int[n][2];
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        for (int i = 1; i < n; ++i) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
        }
        return dp[n - 1][0];
    }
}
//题解贪心
class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int dp0 = 0, dp1 = -prices[0];
        for (int i = 1; i < n; ++i) {
            int newDp0 = Math.max(dp0, dp1 + prices[i]);
            int newDp1 = Math.max(dp1, dp0 - prices[i]);
            dp0 = newDp0;
            dp1 = newDp1;
        }
        return dp0;
    }
}

2024/3/5

到达目的地的方案数
在用dij求最短路径时,利用dp记录方案数量。
dp[i] = 从起点0到达i的最少时间方案数量。
dp[0] = 1
如果dis[i] > dis[index] + g[index][i],代表最短路被更新,那么dp[i] = dp[index]
如果dis[i] == dis[index] + g[index][i],那么代表有另外的最短路方案,dp[i] += dp[index]
需要注意的点是,time < 1e9,而Integer.MAX_VALUE ≈ 1e9,若用其做最大值标注,可能出现溢出的情况,保险起见,还是用Long来记录图和dis。
由于是稠密图,所以无需使用堆进行优化。

class Solution {
    private int mod = (int)1e9 + 7;
    /\*
 dp[i]:从起点0出发到达i的最少时间的方案数量。
 dp[i][j]:从i到j的最少时间方案数量
 dis[i]:从起点0出发到达i的最少时间
 vis[i]:i是否已经被访问了
 \*/
    long[][] g;
    long[] dis;
    int[] dp;
    boolean[] vis;
    public int countPaths(int n, int[][] roads) {
        g = new long[n][n];
        dis = new long[n];
        dp = new int[n];
        vis = new boolean[n];
        for(int i = 0;i < n;i++)Arrays.fill(g[i],Long.MAX\_VALUE / 2);
        for(int i = 0;i < roads.length;i++){
            g[roads[i][0]][roads[i][1]] = roads[i][2];
            g[roads[i][1]][roads[i][0]] = roads[i][2];
        }
        Arrays.fill(dis,Long.MAX\_VALUE / 2);
        Arrays.fill(dp,0);
        Arrays.fill(vis,false);
        dij(n);
        return dp[n - 1] % mod;
    }
    public void dij(int n){
        dis[0] = 0;
        vis[0] = true;
        dp[0] = 1;
        //初始化
        for(int i = 1;i < n;i++){
            dis[i] = g[0][i];
            if(dis[i] != Long.MAX\_VALUE / 2)dp[i] = 1;
        }
        for(int i = 0;i < n;i++){
            //找n - 1条路径
            long mins = Long.MAX\_VALUE / 2;
            int index = 0;
            for(int k = 0;k < n;k++){
                if(!vis[k] && mins > dis[k]){
                    mins = dis[k];
                    index = k;
                }
            }
            //更新vis
            vis[index] = true;
            //遍历剩余节点,更新dis和dp
            for(int k = 0;k < n;k++){
                if(!vis[k] && dis[k] > dis[index] + g[index][k]){
                    dis[k] = dis[index] + g[index][k];
                    dp[k] = dp[index];//最短距离更新了,那么对应的方案也需要更新。
                }
                else if(!vis[k] && dis[k] == dis[index] + g[index][k]){
                    dp[k] += dp[index];
                    dp[k] %= mod;
                }
            }
        }
    }
}

跳跃游戏
贪心:策略是花更少的步数走的更远。
因此对于当前所在的位置i来说,停留的位置为max j + nums[i + j],其中0 < j <= nums[i].

class Solution {
    public int jump(int[] nums) {
        /\*
 [2,1,1,1,4]
 贪心策略,目的是花更少的步数走的更远
 因此,走到范围内和加起来最多的即可。
 \*/
        int n = nums.length;
        int ans = 0;
        int idx = 0;//当前所在的坐标
        while(true){
            if(idx >= n - 1)break;
            int maxs = 0;
            int index = -1;
            for(int i = 1;i <= nums[idx];i++){
                if(idx + i >= n - 1)return ans + 1;
                if(i + nums[idx + i] > maxs){
                    maxs = i + nums[idx + i];
                    index = idx + i;
                }
            }
            if(index != -1){
                ans++;//走到下一步。
                idx = index;
            }
        }
        return ans;
    }
}

H指数
二分搜索,在[0,n]中搜索K是否满足要求。

class Solution {
    public int hIndex(int[] citations) {
        int n = citations.length;
        /\*
 [0,n]搜索
 check一下
 \*/
        int left = 0,right = n;
        Arrays.sort(citations);
        while(left <= right){
            int mid = (left + right) >> 1;
            if(check(mid,n,citations)){
                left = mid + 1;
            }else{
                right = mid - 1;
            }
        }
        return left - 1;
    }
    boolean check(int h,int n,int[] citations){
        int i = n - 1;//检查是否>h
        while(i >= 0 && citations[i] >= h)i--;
        return (n - i - 1) >= h;
    }
}

2024/3/6

找出数组中的K-or值
用的map记录,但可以通过枚举每一个32个bit位判断,自己的方法有点浪费时间。

class Solution {
    public int findKOr(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>();
        for(int i = 0;i < nums.length;i++){
            int x = nums[i];
            int cnt = 0;
            while(x!=0){
                if((x & 1) == 1){
                    map.put(cnt,map.getOrDefault(cnt,0) + 1);
                }
                cnt++;
                x >>= 1;
            }
        }
        Set<Integer> set = map.keySet();
        int ans = 0;
        for(Integer i:set){
            if(map.get(i) >= k){
                ans += (1 << i);
            }
        }
        return ans;
    }
}
//题解:
class Solution {
    public int findKOr(int[] nums, int k) {
        int ans = 0;
        for (int i = 0; i < 31; ++i) {
            int cnt = 0;
            for (int num : nums) {
                if (((num >> i) & 1) != 0) {
                    ++cnt;
                }
            }
            if (cnt >= k) {
                ans |= 1 << i;
            }
        }
        return ans;
    }
}

O(1)时间插入、删除和获取随机元素值
我只用了一个set进行操作,但是这样在随机获取元素值时,就只能遍历,最坏情况为O(n)。
可以利用list同步记录操作,由于数字的顺序无关,可以将最后一个值放入需要remove的地方,然后移除掉最后一个值。

class RandomizedSet {
    Set<Integer> set;
    public RandomizedSet() {
        set = new HashSet();
    }
    
    public boolean insert(int val) {
        if(set.contains(val) == true)return false;
        set.add(val);
        return true;
    }
    
    public boolean remove(int val) {
        if(set.contains(val) == false) return false;
        set.remove(val);
        return true;
    }
    
    public int getRandom() {
        int idx = new Random().nextInt(set.size());
        int cnt = 0;
        for(Integer i:set){
            if(idx == cnt)return i;
            cnt++;
        }
        return 0;
    }
}
//题解:
class RandomizedSet {
    Random random;
    HashMap<Integer,Integer> map;
    ArrayList<Integer> list;

    public RandomizedSet() {
        map = new HashMap();
        list = new ArrayList();
        random = new Random();
    }
    
    public boolean insert(int val) {
        if(map.containsKey(val)){
            return false;
        }

        int index = list.size();
        list.add(val);
        map.put(val,index);
        return true;
    }
    
    public boolean remove(int val) {
        if(!map.containsKey(val)){
            return false;
        }
        int index = map.get(val);
        int last = list.get(list.size()-1);
        list.set(index,last);
        map.put(last,index);
        map.remove(val);
        list.remove(list.size()-1);
        return true;
    }
    
    public int getRandom() {
        return list.get(random.nextInt(list.size()));
    }
}

除自身以外的乘积
对前缀思想的利用。只记住前缀和是不行的。
除自身以外的乘积,是由左部分的乘积 * 右部分的乘积。
因此利用前缀的思想,将左部分的乘积和右部分的乘积单独进行记录。
最终答案 = L[i] * R[i]

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] L = new int[n];
        int[] R = new int[n];
        L[0] = 1;
        for(int i = 1;i<n;i++){//左边第一个元素没有左乘积
            L[i] = nums[i - 1] \* L[i - 1];
        }
        R[n - 1] = 1;
        for(int i = n - 2;i >=0 ;i--){//右边第一个元素没有右乘积
            R[i] = nums[i + 1] \* R[i + 1];
        }
        for(int i = 0;i < n;i++){
            nums[i] = L[i] \* R[i];
        }
        return nums;
    }
}

2024/3/7

2575. 找出字符串的可整除数组
看了提示,才想到可以由上一步的余数推导出当前的余数。
假设上一步的数为x,倍数为y,余数为z,则有
x = y * m + z
假设当前步增加h
x * 10 + h = 10 * (y * m + z) + h = 10 * y * m + 10 * z + h
余数应为:(10 * z + h) % m
需要注意的点:即时是上一步遗留的余数*10+h,有可能会爆int,因为m给的范围很大。

class Solution {
    public int[] divisibilityArray(String word, int m) {
        /\*
 卡大数据long也没法过。
 z = x % m;
 x = m \* y + z;
 x \* 10 + h = (m \* y + z) \* 10 + h = 10 \* y \* m + 10 \* z + h
 x \* 10 + h = 10y \* m + 10 \* z + h;
 10 \* z + h 就为x \* 10 + h的余数
 \*/
        int n = word.length();
        int[] div = new int[n];
        int[] dp = new int[n];
        long preMod = 0;
        Arrays.fill(div,0);
        for(int i = 0;i < n;i++){
            int c = word.charAt(i) - '0';
            preMod = (preMod \* 10 + c) % m;
            if(preMod == 0){
                div[i] = 1;
            }
        }
        return div;
    }
}

134. 加油站
看了题解,一目了然。
本质上是利用一个可以传递的性质,进行一次遍历。
对于从x出发,最多能到达的加油站y来说,对于所有在其中间的加油站j(x < j < y),其最远距离都只能到达y。因为从x到j,其油量必须>=0,对于=0来说,j能到达y,对于>0来说,可能连y都达不到。
因此,可以利用一次遍历,对于当前起点i来说,若走到y之后走不下去了,就换到起点y + 1进行遍历。

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int n = gas.length;
        int i = 0;//记录起点
        while(i < n){
            int cnt = 0;//记录当前的遍历次数
            int gasSum = 0;//剩余油量
            int idx = i;
            while(cnt < n){
                gasSum += gas[idx];
                gasSum -= cost[idx];
                if(gasSum < 0){
                    break;
                }
                idx = (idx + 1) % n;
                cnt++;
            }
            if(cnt == n){
                return i;
            }
            i = i + cnt + 1;
        }
        return -1;
    }
}

2024/3/8

2834. 找出美丽数组的最小和
贪心策略,若想放入值最小,那么就应该从1开始放入,但是又要避免不美丽的情况,对于1来说,target - 1就不能放入了,以此类推。
因此,假设[1,target)中填充m个数组,那么m = target / 2个,其中的数据分别为[1,2,…,m],对于>=target的数据来说,则挨着存即可,[target,target + n - m],等差数列求和。
需要注意的点是,存在有n远远小于target的情况,当满足条件target > 2 * n + 1,代表及时[1,n]全部填充,也不会到达target。

class Solution {
    public int mod = (int)1e9 + 7;
    public int minimumPossibleSum(int n, int target) {
        /\*
 填充规则:从1开始,如果当前值<target,那么,只填充i,不填充target - i。直到 >= target为止。
 分为两部分[1,target).length = m,[target,target + n - m]
 \*/
        long sums = 0;
        //1 + ... + target/2
        if(target > 2 \* n + 1){
            sums = n \* (n + 1) / 2 % mod;
            return (int)sums % mod;
        }
        long m = target / 2;
        sums += (m \* (m + 1) / 2) % mod;
        //target + ... + target + n - m
        long k = n - m;
        sums += (k \* (target + target + k - 1) / 2) % mod;
        return (int) sums % mod;
    }
}

42. 接雨水
我的思路:利用单调栈进行求解,从大到小记录高度,若当前高度<=栈顶高度,则加入。
若当前高度>栈顶高度,则开始循环判断,若前面的数据中存在落差(即height[j] > pre,pre = height[j]),则计算当前能存储的雨量,并将该落差填平。
因此当当前高度>栈顶高度,开始判断时,有以下情况:

  1. 存在落差(栈不为空,且有前面的数据 > 后面的数据的情况)
    此时需要计算填充的数量。利用idx记录左右两边的坐标,以最小的高度 * 长度,算出最大能填充的数量,并减去pre高度 * 长度(易见,在这种情况下,中间全是pre高度,否则早就形成了落差并填平)。
    算出后,将原有左端点继续放入栈中,代表填平。
    仍存在两种情况,一种当前高度<=左边高度,那么以当前高度为右的区域就不能再形成落差,那么就可以结束当前循环。
    若当前高度>左边的高度,那么可能还存在左边还有更高的高度能和当前形成落差,因此循环继续。
  2. 不存在落差,结束当次循环
    最后,将当前高度加入到栈顶。
class Solution {
    public int trap(int[] height) {
        /\*
 单调栈问题:
 从大到小记录高度,若当前高度<=栈顶高度,则加入。
 若当前高度>栈顶高度,则开始出栈,由于是需要形成一个水坝,所以需要找到落差。
 出栈终止条件为,栈顶>pre,出栈时记录出栈元素,计算出后,把当前计算的值填充并加入栈,再把当前元素加入到栈。
 \*/
        int n = height.length;
        class Node{
            int val;
            int idx;
            Node(int x,int y){
                this.val = x;
                this.idx = y;
            }
        }
        Deque<Node> dq = new LinkedList<>();


为了做好运维面试路上的助攻手,特整理了上百道 **【运维技术栈面试题集锦】** ,让你面试不慌心不跳,高薪offer怀里抱!

这次整理的面试题,**小到shell、MySQL,大到K8s等云原生技术栈,不仅适合运维新人入行面试需要,还适用于想提升进阶跳槽加薪的运维朋友。**

![](https://img-blog.csdnimg.cn/img_convert/2a70233c3a47685c2fe8414db05e4c2a.png)

本份面试集锦涵盖了

*   **174 道运维工程师面试题**
*   **128道k8s面试题**
*   **108道shell脚本面试题**
*   **200道Linux面试题**
*   **51道docker面试题**
*   **35道Jenkis面试题**
*   **78道MongoDB面试题**
*   **17道ansible面试题**
*   **60道dubbo面试题**
*   **53道kafka面试**
*   **18道mysql面试题**
*   **40道nginx面试题**
*   **77道redis面试题**
*   **28道zookeeper**

**总计 1000+ 道面试题, 内容 又全含金量又高**

*   **174道运维工程师面试题**

> 1、什么是运维?

> 2、在工作中,运维人员经常需要跟运营人员打交道,请问运营人员是做什么工作的?

> 3、现在给你三百台服务器,你怎么对他们进行管理?

> 4、简述raid0 raid1raid5二种工作模式的工作原理及特点

> 5、LVS、Nginx、HAproxy有什么区别?工作中你怎么选择?

> 6、Squid、Varinsh和Nginx有什么区别,工作中你怎么选择?

> 7、Tomcat和Resin有什么区别,工作中你怎么选择?

> 8、什么是中间件?什么是jdk?

> 9、讲述一下Tomcat8005、8009、8080三个端口的含义?

> 10、什么叫CDN?

> 11、什么叫网站灰度发布?

> 12、简述DNS进行域名解析的过程?

> 13、RabbitMQ是什么东西?

> 14、讲一下Keepalived的工作原理?

> 15、讲述一下LVS三种模式的工作过程?

> 16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?

> 17、如何重置mysql root密码?

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注运维)**
![img](https://img-blog.csdnimg.cn/img_convert/bb8a885f5611dfde65f1d7f137ba8dfc.jpeg)

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
de{
            int val;
            int idx;
            Node(int x,int y){
                this.val = x;
                this.idx = y;
            }
        }
        Deque<Node> dq = new LinkedList<>();


为了做好运维面试路上的助攻手,特整理了上百道 **【运维技术栈面试题集锦】** ,让你面试不慌心不跳,高薪offer怀里抱!

这次整理的面试题,**小到shell、MySQL,大到K8s等云原生技术栈,不仅适合运维新人入行面试需要,还适用于想提升进阶跳槽加薪的运维朋友。**

[外链图片转存中...(img-kOGRT1HT-1713345954584)]

本份面试集锦涵盖了

*   **174 道运维工程师面试题**
*   **128道k8s面试题**
*   **108道shell脚本面试题**
*   **200道Linux面试题**
*   **51道docker面试题**
*   **35道Jenkis面试题**
*   **78道MongoDB面试题**
*   **17道ansible面试题**
*   **60道dubbo面试题**
*   **53道kafka面试**
*   **18道mysql面试题**
*   **40道nginx面试题**
*   **77道redis面试题**
*   **28道zookeeper**

**总计 1000+ 道面试题, 内容 又全含金量又高**

*   **174道运维工程师面试题**

> 1、什么是运维?

> 2、在工作中,运维人员经常需要跟运营人员打交道,请问运营人员是做什么工作的?

> 3、现在给你三百台服务器,你怎么对他们进行管理?

> 4、简述raid0 raid1raid5二种工作模式的工作原理及特点

> 5、LVS、Nginx、HAproxy有什么区别?工作中你怎么选择?

> 6、Squid、Varinsh和Nginx有什么区别,工作中你怎么选择?

> 7、Tomcat和Resin有什么区别,工作中你怎么选择?

> 8、什么是中间件?什么是jdk?

> 9、讲述一下Tomcat8005、8009、8080三个端口的含义?

> 10、什么叫CDN?

> 11、什么叫网站灰度发布?

> 12、简述DNS进行域名解析的过程?

> 13、RabbitMQ是什么东西?

> 14、讲一下Keepalived的工作原理?

> 15、讲述一下LVS三种模式的工作过程?

> 16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?

> 17、如何重置mysql root密码?

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注运维)**
[外链图片转存中...(img-aPl07Urm-1713345954584)]

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值