class 034 链表高频题目和必备技巧

news2024/12/24 2:28:17

这篇文章是看了“左程云”老师在b站上的讲解之后写的, 自己感觉已经能理解了, 所以就将整个过程写下来了。

这个是“左程云”老师个人空间的b站的链接, 数据结构与算法讲的很好很好, 希望大家可以多多支持左程云老师, 真心推荐.

左程云的个人空间-左程云个人主页-哔哩哔哩视频 (bilibili.com)

在这里插入图片描述

1. 链表类问题的注意点

一般来说:链表类问题在笔试和面试中遇见, 都是和 LeetCode 一样的“填函数”风格的, 不是 ACM 风格

链表类问题可以使用容器做, 但是这样的空间复杂度会高一点, 但是我们这节课的重点是对于空间的优化, 尽量使用少的空间, 但是用容器做这样会大大降低难度, 所以看情况, 要是笔试和面试没有要求你不能使用容器就直接用, 要是给了你严格的空间要求, 就不能使用容器解决了.

所以在接下来的实现中, 并不给出用容器做的代码实例, 只是给出逻辑实现, 不用容器, 只是使用几个变量的方式, 既有逻辑实现, 又有代码实例.

2. 题目一:两个链表相交的第一个节点

2.1 逻辑实现

我们先判断一个两个链表如何才能相交:我们只要遍历完两个链表之后, 查看两个链表的最终节点的地址是不是相同就行了, 若是相同, 说明两个链表肯定是相交了的, 若是不相同, 那就说明这两个链表肯定是没有相交.

两个链表相交就是下面这样的情况,

在这里插入图片描述

2.1.1 用容器实现

另一种用容器实现的方法:可以设置一个哈希表, 然后将其中一条链表的所有节点都放到哈希表中, 然后将另一条链表中的所有节点遍历一遍, 每次遍历到一个节点的时候就和哈希表中的所有节点对比一下, 看看在不在其中, 到了相交的第一个节点, 肯定就在哈希表中, 这样肯定就能判断这个是第一个节点了.

2.1.2 不用容器实现

如何实现寻找它们相交的第一个节点

  1. 我们先进行统计两个链表的长度, 比如是一个长一个短 (也有可能相同长度, 但是无所谓),
  2. 我们先让长的链表的长度“减去”短的链表的长度, 这样有一个值:diff,
  3. 先让长的链表先走 diff 步 (先遍历 diff 次),
  4. 然后两个链表同时进行遍历, 只要最后 h1, h2 两个链表遍历到的节点的地址相同了, 那这个就是两个链表相交的第一个节点.

2.2 代码实例

所有的解释我都直接写到代码中了, 这个题目很简单, 所以就不多说了.

public static ListNode getIntersectionNode(ListNode h1, ListNode h2) {  
    if (h1 == null || h2 == null) {  
       return null;                // 若是两个传递进来的两个链表中有任意一个是null, 直接返回null.  
    }  
    ListNode a = h1, b = h2;  
    int diff = 0;              // diff的意义是:统计两个链表的长度的差值.  
    while (a.next != null) {  
       a = a.next;  
       diff++;                   // 先让“a”链表先遍历, 最后得出“a”链表的长度,  
    }  
    while (b.next != null) {  
       b = b.next;  
       diff--;                   // 然后让“b”链表后遍历, 最后得出“a链表长度 - b链表长度”.  
    }  
    if (a != b) {  
       return null;              // 如果最后“a链表的最后一个节点地址 != b链表的最后一个节点地址”  
    }                             // 就直接返回 null, 因为这样就说明两个链表肯定没有相交.  
    if (diff >= 0) {  
       a = h1;                   // 这段代码实现的意义是将“a链表设置为长链表, b链表设置为短链表.  
       b = h2;  
    } else {                      // 要是diff > 0, 说明“a链表长度 > b链表长度”.  
       a = h2;                   // 要是diff < 0, 说明“a链表长度 < b链表长度”.  
       b = h1;  
    }  
    diff = Math.abs(diff);     // diff的正负的意义就是选择一个长链表, 现在已经完成了, 所以直接取绝对值  
    while (diff-- != 0) {  
       a = a.next;            // 因为我们上面设置好了“a链表是长链表, 所以这里我们先让a遍历diff个节点”.  
    }  
    while (a != b) {  
       a = a.next;            // 最后, 让两个链表同时遍历, 每次都进行判断两个节点的地址是不是相同.  
       b = b.next;  
    }  
    return a; // 最后返回一个节点, 因为此时已经是到了同一个链表的第一个节点了, 所以返回“a 或者是 b都行”.  
}

3. 题目二:按组翻转链表

3.1 题目描述

在这里插入图片描述

3.2 逻辑实现

3.2.1 用容器实现

可以将所有链表节点都放到一个数组中, 然后将所有的数字都在数组中进行交换, 这样就能非常简单地实现了, 但是这样需要的空间复杂度是:O(n). 我们这里需要的是 O(1) 的时间复杂度.

3.2.2 不用容器实现

我们如今有这样一个链表:a -> b -> c -> d -> e -> f -> g -> h -> null. 并且设置 k = 3, 将这个链表按 k 个翻转.

  1. 首先我们需要对第一组进行单独调整, 因为我们需要将 c 这个节点作为头结点返回, 所以第一组是特殊的, 我们可以利用我们前面讲过的翻转链表的实现, 唯一有一个地方不用的是:不能将 a 指向 null, 需要将 a 指向 d, 效果是: a -> b -> c -> d 修改为:c -> b -> a - d.
  2. 然后继续对 d -> e -> f 这一组操作, 还是执行上述翻转链表的操作, 然后将 d -> g,
  3. 然后此时我们需要将 a -> f 因为此时第二组也已经被翻转过了, 所以此时 f 成了第二组的头结点,
  4. 最后按照上述的方式进行实现.

总结:这种做法实现了 0(1) 的空间复杂度实现, 但是还是有点绕的, 需要仔细思考才行.

3.2.3 代码实例

整个步骤我都在注释中写好了,

注意:这个的操作是有点复杂的, 步骤也是比较长, 边界问题也有, 但是不难理解, 所以最好是自己用电脑上的画图软件自己跟着代码走一遍, 这样会有助于理解.

// 不要提交这个类 
public static class ListNode {  
    public int val;  
    public ListNode next;  
}  
  
// 提交如下的方法  
public static ListNode reverseKGroup(ListNode head, int k) {  
    ListNode start = head;          // 设置start为head, 后续需要这个.
    ListNode end = teamEnd(start, k);  // end节点是这一组的最后一个节点.
    if (end == null) {  
       return head;   // 若是最后end == null, 说明这一组没有 k 个节点, 所以直接返回head就行了. 
    }  
    // 第一组很特殊因为牵扯到换头的问题  
    head = end;     // 此时说明end != null, 所以将head指向end, 因为需要翻转这组链表了.
    reverse(start, end);  // 翻转这组链表.
    // 翻转之后start变成了上一组的结尾节点  
    ListNode lastTeamEnd = start;   
    while (lastTeamEnd.next != null) {// 这段循环的终止条件是上一组的结尾节点的下一个节点是null.
       start = lastTeamEnd.next;  // 此时下一组的开始节点是上一组的结尾节点的下一个节点
       end = teamEnd(start, k);  // end是这一组的下一个节点.
       if (end == null) {    
          return head;            // 还是按照上述的方式, 此时说明这一组链表没有k个节点, 直接返回
       }  
       reverse(start, end);      // 翻转这个链表.
       lastTeamEnd.next = end;   // 这一组的最后一个节点的指向的下一个节点修改为end
       lastTeamEnd = start;      // 然后将这一组的最后一个节点修改为start.
    }  
    return head;  // 最后返回head.
}  
  
// 当前组的开始节点是s,往下数k个找到当前组的结束节点返回  
public static ListNode teamEnd(ListNode s, int k) {  
    while (--k != 0 && s != null) {  
       s = s.next;          // 这个就不多解释了, 直接看就能明白  
    }  
    return s;  
}  
  
// s -> a -> b -> c -> e -> 下一组的开始节点  
// 上面的链表通过如下的reverse方法调整成 : e -> c -> b -> a -> s -> 下一组的开始节点  
public static void reverse(ListNode start, ListNode end) {  
    end = end.next;  
    ListNode pre = null, cur = start, next = null;  
    while (cur != end) {  
       next = cur.next;  
       cur.next = pre;  
       pre = cur;                // 这个就是之前讲过的翻转链表的问题, 只是增加了指向下一个组的开始节点.  
       cur = next;  
    }  
    start.next = end;  
}

4. 题目三:复制带随机指针的链表

4.1 逻辑实现

4.1.1 用容器实现的方式

可以先设置一个哈希表, 然后遍历一遍原本的链表, 将其保存在 key, 但是不设置指向任何节点的指针, 然后设置对应的 1' 节点, 将其放到 value, 同样也不指向任何节点, 都放入之后, 从头结点 1 开始, 设置对应的 next指针和 random指针, 然后 1' 也能通过查找哈希表的方式找到所有对应的节点位置, 这样就能实现题目的要求了.
在这里插入图片描述

4.1.2 不用容器实现的方式

  1. 因为我们需要完全复制原来的一个链表, 所以我们将每一个节点写作:1', 2', 3', 我们先不管 random指针指向,
  2. 将原来的:1 -> 2 - > 3 -> null 修改为:1 -> 1' -> 2 -> 2' -> 3 -> 3' -> null,
  3. 然后遍历修改之后的链表, 根据 原来链表节点的:random指针 设置 新加入的链表节点的指针:random'
  4. 最后将这个链表连接好之后, 再将原来的链表和我们自己新加入的链表分离就行了, 而且对 random 指针没有影响.

4.2 代码实例

public static Node copyRandomList(Node head) {  
    if (head == null) {  
       return null;  
    }  
    Node cur = head;  
    Node next = null;  
    // 1 -> 2 -> 3 -> ...  
    // 变成 : 1 -> 1' -> 2 -> 2' -> 3 -> 3' -> ...    while (cur != null) {  
       next = cur.next;  
       cur.next = new Node(cur.val);  
       cur.next.next = next;  
       cur = next;  
    }  
    cur = head;  
    Node copy = null;  
    // 利用上面新老节点的结构关系,设置每一个新节点的random指针  
    while (cur != null) {     // 新的random指针肯定是原来的random指针指向的下一个
       next = cur.next.next;  // 所以直接设置就行了.
       copy = cur.next;  
       copy.random = cur.random != null ? cur.random.next : null;  
       cur = next;  
    }  
    Node ans = head.next;  
    cur = head;  
    // 新老链表分离 : 老链表重新连在一起,新链表重新连在一起  
    while (cur != null) {  
       next = cur.next.next;  
       copy = cur.next;       
       cur.next = next;  
       copy.next = next != null ? next.next : null;  
       cur = next;  
    }  
    // 返回新链表的头节点  
    return ans;  
}

5. 题目四:判断链表是否是回文结构

5.1 逻辑实现

5.1.1 用容器的实现

用栈实现, 先遍历所有节点, 将所有节点都放到栈中, 然后弹出和遍历同时执行, 并且要作对比, 只要有一个不一样的就直接返回 false.

5.1.2 不用容器的实现

  1. 设置一对“快慢指针”, 作用是寻找整个链表的中点,
    1. 若是一个奇数个数的链表, 那慢指针就会到达中间位置.
    2. 若是一个偶数个数的链表, 那慢指针就会到达中间两个节点的位置的左边的一个节点.
  2. 之后从慢指针的下一个链表节点开始, 将后续的所有链表节点翻转.
  3. 然后设置“左右两个指针”, 分别到最左边和最右边的位置, 从两边往中心靠近, 分别比较对应的值, 只要有一个不一样的就说明不是回文结构.
    1. 若是一个奇数个数的链表, 那慢指针就会到达中间位置, 最中间的位置不用比较.
    2. 若是一个偶数个数的链表, 那慢指针就会到达中间两个节点的位置的左边的一个节点. 直接比较中间的两个位置的大小就行了.

5.2 代码实例

该有的注意点都在代码中说明了, 直接看就行了.

public static class ListNode {  
    public int val;  
    public ListNode next;  
  
    public ListNode(int val) {  
        this.val = val;  
    }  
}  
  
public static boolean isPalindrome(ListNode head) {  
    if (head == null || head.next == null) {  
        return true;  
    }  
  
    ListNode slow = head;  
    ListNode fast = head;   // 将两个快慢指针同时指向head.
    while (fast.next != null && fast.next.next != null) {  
        slow = slow.next;  // 这段代码的唯一意义就是将“slow”指针放到链表的中间位置.
        fast = fast.next.next;  // fast指针后续真的不重要, 后续需要关注的是“slow”指针
    }  
  
    ListNode right = reverse(slow.next, slow);  // 将“slow”指针后面的链表都进行翻转.将“右指针”指向最右边的位置.
    ListNode left = head;                 // 将“左指针”指向head头结点位置
    while (left != null && right != null) {  // 左右两边, 若是有任何一个位置的为null就停止
        if (left.val != right.val) {  
            return false;  // 只要有任何一对数字不相同就直接返回false.
        }  
        left = left.next;   // 指向左指针的下一个
        right = right.next;  // 指向右指针的下一个.
    }  
  
    return true;  // 若是都相同, 最后就返回true.
}  

// 我单独实现了一个翻转链表的函数, 在这个题目中, 这个connectNode 对应的是 slow 节点  
// 若是不加入一个connectNode节点, 会将这个链表断开, 无论是奇数个数还是偶数个数的链表  
// 比如:不加的情况:1 -> 2 -> 3 -> 4 会变成:1 -> 2 -> null  3 <- 4 这样2, 3节点之间会断开,  
// 加入了这个connect之后:1 -> 2 -> 3 -> 4 会变成:1 -> 2 <- 3 <- 4, 2 -> null, 这样就不会断开了.  
public static ListNode reverse(ListNode head, ListNode connectNode) {  
    ListNode pre = connectNode;  
    ListNode next = null;  
  
    while (head != null) {  
        next = head.next;  
        head.next = pre;  
        pre = head;  
        head = next;  
    }  
    return pre;  
}

5.3 重排链表考研常见 (附加题)

左老师没有单独讲解这个题目:但是我找到了 LeetCode 的链接如下:
https://leetcode.cn/problems/reorder-list/.
将下面的代码直接提交就可以了, 能通过.
该有的注释我都在代码中写好了.

将这个链表 a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8 -> null
变成
a1 -> a8 -> a2 -> a7 -> a3 -> a6 -> a4 -> a5 -> null

这个题目和上面这个双指针找中点的方式是一样的.

我就不写什么流程了, 链表就是调整节点, 直接对着代码一步一步画一下就行了.

public static ListNode reorderList(ListNode head) {  
    if (head == null) {  
        return null;  
    }  
  
    ListNode slow = head;  
    ListNode fast = head;  
    while (fast.next != null && fast.next.next != null) {  
        slow = slow.next;  
        fast = fast.next.next;  
    }  
  
    ListNode right = reverse(slow.next, slow);  
    slow.next = null;  
    ListNode left = head;  
    ListNode next = null;  
    while (left != null && right != null) {  
        next = left.next;  
        left.next = right;  
        right = right.next;  
        left.next.next = next;  
        left = next;  
    }  
    return head;  
}  
  
  
// 我单独实现了一个翻转链表的函数, 在这个题目中, 这个connectNode 对应的是 slow 节点  
// 若是不加入一个connectNode节点, 会将这个链表断开, 无论是奇数个数还是偶数个数的链表  
// 比如:不加的情况:1 -> 2 -> 3 -> 4 会变成:1 -> 2 -> null  3 <- 4 这样2, 3节点之间会断开,  
// 加入了这个connect之后:1 -> 2 -> 3 -> 4 会变成:1 -> 2 <- 3 <- 4, 2 -> null, 这样就不会断开了.  
public static ListNode reverse(ListNode head, ListNode connectNode) {  
    ListNode pre = connectNode;  
    ListNode next = null;  
  
    while (head != null) {  
        next = head.next;  
        head.next = pre;  
        pre = head;  
        head = next;  
    }  
    return pre;  
}

6. 题目五:链表的第一个入环节点

这个就是一个有环链表

在这里插入图片描述

6.1 逻辑实现

6.1.1 用容器实现

直接利用一个哈希表, 然后遍历所有节点, 若是哈希表中没有这个节点就将这个节点放入到哈希表, 要是有说明这肯定是第一个入环节点.

6.1.2 不用容器实现

注意:这个方法直接记住就行了, 不用就纠结什么证明, 若是有兴趣自己去看一下证明就行了.

  1. 设置一对“快慢指针”, 然后还是设置成快指针移动两步的同时, 慢指针移动一步.
    1. 若是一个有环链表, 那么快慢指针一定会相遇.
    2. 若是一个无环链表, 那么快指针肯定会到 null. 直接返回 null 就行.
  2. 快慢指针相遇之后, 让快指针立即回到头节点, 慢指针停留在原地.
  3. 然后快指针移动一步, 慢指针同时也移动一步.
  4. 最后快慢指针一定会在入环节点位置相遇.

6.2 代码实例

这个实现起来非常简单, 没有什么需要注意的地方, 还是直接记住就行, 不用深究证明方式.

public static ListNode detectCycle(ListNode head) {  
    if (head == null || head.next == null || head.next.next == null) {  
       return null;  
    }  
    ListNode slow = head.next;  
    ListNode fast = head.next.next;  
    while (slow != fast) {  
       if (fast.next == null || fast.next.next == null) {  
          return null;  
       }  
       slow = slow.next;  
       fast = fast.next.next;  
    }  
    
    fast = head;  
    while (slow != fast) {  
       slow = slow.next;  
       fast = fast.next;  
    }  
    return slow;  
}

7. 题目六:链表排序

7.1 题目描述

在链表上进行排序, 而且要求时间复杂度是:O(n * log(n)), 空间复杂度:O(1), 具有稳定性

7.2 逻辑实现

7.2.1 用容器实现

直接用数组就能实现了, 没有什么可以说的.

7.2.2 不用容器实现

这个的思路非常好想, 可以参考数组排序的归并排序的思路, 正常用数组排序的话, 需要一个 help 辅助数组, 但是使用链表是不需要的, 链表本来就是一个跳转结构, 我们只需要找到那个小的节点, 然后一个一个串起来就行了.

还有一个需要注意的是, 我们不用设置 help 数组的同时也不能进行递归调用, 因为递归调用本身也是占用空间的, 空间复杂度是:O(log(n)) 所以我们需要使用的方法是:利用原来讲过的使用步长调整的方式来实现归并排序.

  1. 最开始设置步长 step == 1, 然后将两个数字进行 merge 过程, 当然 mrege 过程也是需要用链表进行实现的.
  2. 然后将 step <<= 1(就是乘以二), 然后继续将对应的 4 个数字进行 merge 过程,
  3. 一直重复上述过程.
  4. 最后直到所有数字都排好序了停止.

7.3 代码实例

这个代码写起来很难, 直接用左老师的代码了, 也比较清楚.

// 提交如下的方法  
// 时间复杂度O(n*logn),额外空间复杂度O(1),有稳定性  
// 注意为了额外空间复杂度O(1),所以不能使用递归  
// 因为mergeSort递归需要O(log n)的额外空间  
public static ListNode sortList(ListNode head) {  
    int n = 0;  
    ListNode cur = head;  
    while (cur != null) {  
       n++;  
       cur = cur.next;  
    }  
    // l1...r1 每组的左部分  
    // l2...r2 每组的右部分  
    // next 下一组的开头  
    // lastTeamEnd 上一组的结尾  
    ListNode l1, r1, l2, r2, next, lastTeamEnd;  
    for (int step = 1; step < n; step <<= 1) {  
       // 第一组很特殊,因为要决定整个链表的头,所以单独处理  
       l1 = head;  
       r1 = findEnd(l1, step);  
       l2 = r1.next;  
       r2 = findEnd(l2, step);  
       next = r2.next;  
       r1.next = null;  
       r2.next = null;  
       merge(l1, r1, l2, r2);  
       head = start;  
       lastTeamEnd = end;  
       while (next != null) {  
          l1 = next;  
          r1 = findEnd(l1, step);  
          l2 = r1.next;  
          if (l2 == null) {  
             lastTeamEnd.next = l1;  
             break;  
          }  
          r2 = findEnd(l2, step);  
          next = r2.next;  
          r1.next = null;  
          r2.next = null;  
          merge(l1, r1, l2, r2);  
          lastTeamEnd.next = start;  
          lastTeamEnd = end;  
       }  
    }  
    return head;  
}  
  
// 包括s在内,往下数k个节点返回  
// 如果不够,返回最后一个数到的非空节点  
public static ListNode findEnd(ListNode s, int k) {  
    while (s.next != null && --k != 0) {  
       s = s.next;  
    }  
    return s;  
}  
  
public static ListNode start;  
  
public static ListNode end;  
  
// l1...r1 -> null : 有序的左部分  
// l2...r2 -> null : 有序的右部分  
// 整体merge在一起,保证有序  
// 并且把全局变量start设置为整体的头,全局变量end设置为整体的尾  
public static void merge(ListNode l1, ListNode r1, ListNode l2, ListNode r2) {  
    ListNode pre;  
    if (l1.val <= l2.val) {  
       start = l1;  
       pre = l1;  
       l1 = l1.next;  
    } else {  
       start = l2;  
       pre = l2;  
       l2 = l2.next;  
    }  
    while (l1 != null && l2 != null) {  
       if (l1.val <= l2.val) {  
          pre.next = l1;  
          pre = l1;  
          l1 = l1.next;  
       } else {  
          pre.next = l2;  
          pre = l2;  
          l2 = l2.next;  
       }  
    }  
    if (l1 != null) {  
       pre.next = l1;  
       end = r1;  
    } else {  
       pre.next = l2;  
       end = r2;  
    }  
}

8. 总结

上述的这些题目使用容器做都是比较容易的, 但是链表训练的是我们的 coding 能力, 而且若是使用空间复杂度为:O(1), 的方法其实并不简单, 所以在我们训练的时候, 尽量不使用容器做, 这样能很好地锻炼我们的 coding 能力, 想要做到随意所欲的修改代码, 实现要求, 只有自己一步一步的来调整, 训练, 这个哪一个老师都教不了.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2211581.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis 典型应用之缓存

目录 1. 缓存的基本概念 2. 使用 Redis 作为缓存 3. 缓存的更新策略 3.1 定期生成 3.2 实时生成 3.2.1 内存淘汰策略 1. FIFO (First In First Out) 先进先出 2. LRU (Least Recently Used) 淘汰最久未使使用的 3. LFU (Least Frequently Used) 淘汰访问次数最少的 4…

用C++编写信息管理系统(歌单信息管理)

C语言是面向过程的编程语言&#xff0c;而C是面向对象的编程语言&#xff0c;在书写代码时风格有所不同&#xff08;也存在很多共性&#xff09;。 程序说明 本次系统程序使用的是C语言进行编写&#xff0c;主要考虑怎么实现面向对象的问题。 因为本次程序属于小型系统程序&…

【C++学习】核心编程之类和对象(上)黑马学习笔记—超详细

目录 &#xff08;一&#xff09;封装 1.1 封装的意义&#xff1a; 意义一&#xff1a;在设计类的时候&#xff0c;属性和行为写在一起&#xff0c;表现事物 意义二&#xff1a;类在设计时&#xff0c;可以把属性和行为放在不同的权限下&#xff0c;加以控制 1.2 struct和…

【vue】组件及组件化+组件⽣命周期

代码获取 04-组件及组件化组件⽣命周期 ⼀、组件及组件化 1. 为什么需要组件&#xff1f; 1.1 思考 以可折叠⾯板为例, 现要展⽰3个, 如何操作? 可折叠⾯板案例的代码 &#xff1a; <script setup> import { ref } from vue const visible ref(false) </scri…

Day14

std::string的底层实现 三种方式&#xff1a; 深拷贝 写时复制 短字符串优化 深拷贝 无论什么情况&#xff0c;都是采用拷贝字符串内容的方式解决。不需要改变字符串内容时&#xff0c;对字符串进行频繁复制。 用一个string对象初始化另一个string对象时&#xff0c;源对…

MySQL SELECT 查询(三):查询常用函数大全

MySQL SELECT 查询&#xff08;三&#xff09;&#xff1a;查询常用函数大全 1. 单行函数 单行函数是 SQL 中一类重要的函数&#xff0c;它们可以对单行数据进行处理&#xff0c;并返回单个结果。单行函数可以嵌套使用&#xff0c;并提供灵活的数据处理能力。 1.1 定义 只对单…

微知-Mellanox网卡如何导出firmware中的config文件以及文件中有些什么?(ini配置文件,8个区)

背景 Mellanox网卡早期版本以及Engineer simple的DPU支持导出配置文件&#xff0c;该配置文件就是用来告诉firmware的行为。但不是mlxconfig真正设置的文件(mlxconfig -d xxx -e -q应该就是把这个文件读取出来&#xff0c;并且有3个文件&#xff0c;包括默认的&#xff0c;当前…

攻防世界2

forgot 发现是32位文件 fgets(s, 32, stdin)限制读入32位字符&#xff0c;无法利用 __isoc99_scanf("%s", v2) 典型的栈溢出 发现cat flag 覆盖v2-v3&#xff0c;覆盖为cat flag的函数地址 exp&#xff1a; from pwn import * context(oslinux,archamd64,log_lev…

芋道快速开发平台学习笔记

1.接口文档配置 基础知识:SpringDoc注解的使用,它是基于OpenAPI 3和Swagger 3的现代化解决方案,相较于旧版的Swagger2即SpringFox,SpringDoc提供了更简洁、更直观的注解方式。 详见springboot集成springdoc-openapi(模拟前端请求)_springdoc-openapi-ui-CSDN博客 doc文档配置…

c++面向对象三大特性——多态详解与虚函数,虚函数底层

目录 前言&#xff1a; 1. 多态的概念 1.1 概念 2. 多态的定义及实现 2.1多态的构成条件 2.2 虚函数 2.3虚函数的重写 2.4 C11 override 和 final 2.5 重载、覆盖(重写)、隐藏(重定义)的对比 3. 抽象类 3.1 概念 3.2 接口继承和实现继承 4.多态的原理 4.1虚函数表 …

7.1-I2C的中断

I2C的中断与DMA 回顾 HAL_I2C_MASTER_Transmit(&hi2c1,ADRESS,PDate,Size,Time);HAL_I2C_MASTER_Receive(&hi2c1,ADRESS,PDate,Size,Time);通信具体过程如下&#xff1a; 在I2C的轮询模式中 发送时&#xff1a;CPU将以主机0x70 发送 从机 ACK 回复 主机0xAC发送 A…

⽂件的操作

1. 为什么使⽤⽂件&#xff1f; 如果没有⽂件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失了&#xff0c;等再次运⾏程序&#xff0c;是看不到上次程序的数据的&#xff0c;如果要将数据进⾏持久化…

深圳大学-Java程序设计-必实验2 类的高级应用

实验目的与要求&#xff1a; 实验目的&#xff1a;熟悉面向对象编程中类的编写。 实验要求&#xff1a; (1).请自行选择2023年成都大运会或2023杭州亚运会。大型运动会通常包括众多比赛项目。请通过分析&#xff0c;抽象它们所共有的性质&#xff0c;定义一个关于比赛项目的抽…

点评项目-6-缓存更新策略、缓存穿透、雪崩

缓存更新策略 使用 redis 缓存记录的信息&#xff0c;有可能在数据库被信息被修改导致信息不一致&#xff0c;使用缓存更新来解决这个问题 缓存更新策略主要有三种&#xff1a; 1.内存淘汰(redis默认开启) 2.超时剔除(给key添加TTL时间) 3.主动更新(编写业务逻辑) 主动更新策…

网络通信与并发编程(一)网络通信、osi五层模型、tcp协议的三次握手与四次挥手

网络通信、osi五层模型、tcp协议的三次握手与四次挥手 文章目录 网络通信、osi五层模型、tcp协议的三次握手与四次挥手一、网络通信二、osi五层模型1.物理层2.数据链路层3.网络层4.传输层5.应用层 三、tcp协议的三次握手与四次挥手 一、网络通信 网络通信是指在网络中的两个或…

Java ==> 数组(入门)

文章目录 前言一、一维数组1.入门操作2.何为null&#xff1f;3.数组可以作为函数的参数4.数组可以作为函数的返回值 二、二维数组1.基础操作2.不规则的二维数组 总结 前言 在Java语言当中&#xff0c;数组是一种基本的数据结构&#xff0c;它存储了固定大小的同类型元素的集合…

告别卡顿!五款Windows录屏工具,让每一帧都清晰流畅

小伙伴们&#xff0c;是不是在寻找一款好用、实用的Windows录屏工具呢&#xff1f;别担心&#xff0c;这次我给大家带来了一款热门录屏工具的详细评测和使用感受&#xff0c;包括福昕录屏、转转录屏、爱拍录屏、OBS录屏和EV录屏。快来看看哪款最适合你吧&#xff01; 一、福昕录…

反射的学习

1、什么是反射 反射允许对封装类的字段&#xff0c;方法和构造函数的信息进行编程访问。 也就是&#xff1a; 反射允许对成员变量&#xff0c;成员方法和构造方法的信息进行编程访问。 2、获取class对象 获取一个类的字节码文件对象&#xff1a; 方式1&#xff1a;Class.…

linux 环境运行 jenkins.war包,有可能会出现字体问题,jdk版本:11 jenkins 版本:2.420

jenkins的目录&#xff1a; /usr/jenkins 启动命令 java -Djava.awt.headlesstrue sudo timedatectl set-timezone Asia/Shanghai-Xmx1024m -jar jenkins.war --httpPort8090 任意目录启动&#xff1a; nohup java -Djava.awt.headlesstrue -Xms1024m -Xmx1024m -jar /usr/j…

基于opencv答题卡识别判卷

我们是一个深度学习领域的独立工作室。团队成员有&#xff1a;中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等&#xff0c;曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万粉丝&#xff0c;拥有2篇国家级人工智能发明专利。 社区特色&#xff1a;深度实…