如有问题大概率是我的理解比较片面,欢迎评论区或者私信指正。

 考察线性表,核心围绕其存储结构特性、核心操作实现、场景应用选型三大维度,重点检验对基础概念的理解、代码实现能力及问题分析能力,通常会结合算法设计、复杂度分析和实际场景进行考察。

一、基础概念与特性辨析

  1. 核心定义与逻辑结构

    • 问题:“什么是线性表?它与非线性结构(如树、图)的本质区别是什么?” (考察点:线性表 “一对一” 的逻辑关系,区别于树的 “一对多” 和图的 “多对多”)

  2. 两种存储结构的核心差异

    • 问题:“顺序表和链表在存储方式、访问效率、插入删除操作上有何本质区别?” (考察点:顺序表的连续空间与随机存取、链表的离散空间与指针依赖;插入删除时 “移动元素” 与 “修改指针” 的效率差异)

  3. 总结

  • 顺序表 vs 链表的本质区别

    • 顺序表:连续内存、随机访问快(O(1)),增删需移动元素(O(n)),扩容成本高。

    • 链表:离散内存、增删快(O(1)),查找需遍历(O(n)),无扩容问题但存储密度低。

    “数组和链表分别适合什么场景?为什么数据库索引常用B+树(基于数组)而非链表?”
    :数组适合读多写少(如索引),链表适合频繁增删(如操作历史记录)。

二、核心操作的代码实现(边界处理)

(一)顺序表操作

插入与删除

问题1:“编写函数,在顺序表的第 i 个位置插入元素 x,需处理越界、表满等异常。” (关键点:从后往前移动元素(避免覆盖),表长 + 1;时间复杂度分析)

public class SeqList {private int[] data;      // 存储元素的数组private int maxSize;     // 顺序表最大容量private int length;      // 当前元素个数// 初始化顺序表public SeqList(int capacity) {this.maxSize = capacity;this.data = new int[maxSize];this.length = 0;}/*** 在顺序表第i个位置插入元素x* @param i 插入位置(位序从1开始)* @param x 插入元素值* @return 操作是否成功*/
public boolean insert(int i, int x) {// 1. 检查位序有效性if (i < 1 || i > length + 1) {System.err.println("插入位置越界!有效范围:[1, " + (length + 1) + "]");return false;}// 2. 检查表满异常if (length >= maxSize) {System.err.println("顺序表已满!最大容量:" + maxSize);return false;}// 3. 元素后移操作(从后向前移动)for (int j = length-1; j >= i-1; j--) {data[j+1] = data[j];  // 将元素向后移动一位}// 4. 插入新元素data[i - 1] = x;  // 数组下标 = 位序 - 1length++;         // 更新表长return true;}
}

问题2:“删除顺序表中值为 x 的所有元素,要求时间复杂度O(n)、空间复杂度O(1)。” (思路:用 k 记录有效元素个数,遍历数组时将非 x 元素前移至 k 位置,最后更新表长)

public class OrderedList {private int[] data;      // 存储元素的数组private int length;      // 当前元素个数private int capacity;    // 列表容量// 初始化顺序表public OrderedList(int capacity) {this.capacity = capacity;this.data = new int[capacity];this.length = 0;}/*** 删除所有值为x的元素(时间复杂度O(n),空间复杂度O(1))* @param x 要删除的目标值* @return 删除的元素数量*/public int removeAll(int x) {int k = 0; // 有效元素指针int count = 0; // 删除计数// 双指针遍历for (int i = 0; i < length; i++) {if (data[i] != x) {// 保留非x元素data[k] = data[i];k++;} else {// 统计删除元素count++;}}// 更新表长length = k;return count;}// 辅助方法:添加元素(测试用)public void add(int value) {if (length < capacity) {data[length] = value;length++;}}// 辅助方法:打印当前列表(测试用)public void print() {System.out.print("[");for (int i = 0; i < length; i++) {System.out.print(data[i]);if (i < length - 1) System.out.print(", ");}System.out.println("]");}
}

查找与扩容

问题1:“在有序顺序表中实现折半查找,返回元素位置;若不存在,返回插入位置。” (考察点:二分法的边界控制(low <= high)、循环结束时 low 的含义)

/*** 在有序顺序表中执行折半查找* * @param arr 有序顺序表(升序排列)* @param target 目标元素值* @return 元素位置(从1开始计数),若不存在则返回应插入位置*/
public static int binarySearch(int[] arr, int target) {// 边界检查:空表直接返回插入位置1if (arr == null || arr.length == 0) {return 1;}int low = 0;int high = arr.length - 1;// 关键点1:循环条件必须包含等号(单个元素)while (low <= high) {// 关键点2:防止整数溢出的中间值计算int mid = low + ((high - low) >> 1);if (arr[mid] == target) {// 找到目标值,返回位序(索引+1)return mid + 1;} else if (arr[mid] < target) {// 目标在右半区low = mid + 1;} else {// 目标在左半区high = mid - 1;}}// 关键点3:循环结束时low指向第一个大于target的元素位置return low + 1;
}

问题2:“顺序表动态扩容的原理是什么?为什么扩容时通常选择扩大为原容量的 1.5 倍?” (考察点:避免频繁扩容导致的时间开销, amortized 复杂度优化,懒加载策略)

public class ArrayList<E> {/*** 默认初始化空间*/private static final int DEFAULT_CAPACITY = 10;/*** 空元素*/private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/*** ArrayList 元素数组缓存区*/transient Object[] elementData;private int size;//实际容量// 添加元素触发扩容public boolean add(E e) {// 步骤1:计算最小所需容量int minCapacity = size + 1;// 步骤2:处理首次添加的特殊情况if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}// 步骤3:检查并执行扩容if (minCapacity - elementData.length > 0) {// 3.1 记录当前容量int oldCapacity = elementData.length;// 3.2 计算新容量(1.5倍扩容)int newCapacity = oldCapacity + (oldCapacity >> 1);// 3.3 处理1.5倍不足的情况if (newCapacity - minCapacity < 0) {newCapacity = minCapacity;}// 3.4 创建新数组并复制数据elementData = Arrays.copyOf(elementData, newCapacity);}// 步骤4:安全添加元素elementData[size++] = e;return true;}

(二)链表操作

单链表基础操作

问题1:“带头结点的单链表中,实现第 i 个位置插入元素;若 i=1,操作和其他位置有何不同?” (关键点:头结点的作用 —— 统一插入逻辑,无需特殊处理 i=1;指针修改顺序:先连新结点,再断原链)

class LinkedListWithHead {private Node head; // 头指针始终指向头结点(哨兵结点)public LinkedListWithHead() {head = new Node(-1); // 创建头结点(哨兵结点),数据域通常无效}// 在位置 i 插入元素public boolean insert(int i, int data) {if (i < 1) return false; // 位置无效检查Node pre = head; // 从头结点开始// 寻找第 i-1 个结点(插入位置的前驱)for (int pos = 0; pos < i - 1; pos++) {if (pre.next == null) break; // 提前到达链表末尾pre = pre.next;}if (pre == null) return false; // 前驱结点无效Node newNode = new Node(data); // 创建新结点newNode.next = pre.next; // 新结点指向原位置结点pre.next = newNode;     // 前驱指向新结点return true;}// 删除位置 i 的元素public boolean delete(int i) {if (i < 1 || head.next == null) return false; // 位置无效或链表为空Node pre = head; // 从头结点开始// 寻找第 i-1 个结点(删除位置的前驱)for (int pos = 0; pos < i - 1; pos++) {if (pre.next == null) break; // 提前到达链表末尾pre = pre.next;}if (pre.next == null) return false; // 要删除的结点不存在pre.next = pre.next.next; // 跳过要删除的结点return true;}// 查找元素public int find(int data) {int position = 1;Node current = head.next; // 从第一个实际结点开始while (current != null) {if (current.data == data) {return position; // 找到元素,返回位置}current = current.next;position++;}return -1; // 未找到}// 获取链表字符串表示public String getListString() {StringBuilder sb = new StringBuilder("Head → ");Node current = head.next; // 跳过头结点while (current != null) {sb.append(current.data);if (current.next != null) sb.append(" → ");current = current.next;}sb.append(" → NULL");return sb.toString();}
}
class LinkedListWithoutHead {private Node head; // 直接指向第一个实际结点(可能为null)public LinkedListWithoutHead() {head = null; // 初始化为空链表}// 在位置 i 插入元素public boolean insert(int i, int data) {if (i < 1) return false; // 位置无效检查// 特殊处理 i=1 的情况if (i == 1) {Node newNode = new Node(data);newNode.next = head; // 新结点指向原头结点head = newNode;      // 更新头指针return true;}Node pre = head; // 从第一个结点开始// 寻找第 i-1 个结点for (int pos = 1; pos < i - 1; pos++) {if (pre == null) break; // 提前结束pre = pre.next;}if (pre == null) return false; // 前驱结点无效Node newNode = new Node(data);newNode.next = pre.next; // 新结点指向原位置结点pre.next = newNode;     // 前驱指向新结点return true;}// 删除位置 i 的元素public boolean delete(int i) {if (i < 1 || head == null) return false; // 位置无效或链表为空// 特殊处理 i=1 的情况if (i == 1) {head = head.next; // 头指针指向第二个结点return true;}Node pre = head; // 从第一个结点开始// 寻找第 i-1 个结点for (int pos = 1; pos < i - 1; pos++) {if (pre.next == null) break; // 提前结束pre = pre.next;}if (pre.next == null) return false; // 要删除的结点不存在pre.next = pre.next.next; // 跳过要删除的结点return true;}// 查找元素public int find(int data) {int position = 1;Node current = head; // 从第一个实际结点开始while (current != null) {if (current.data == data) return position;current = current.next;position++;}return -1; // 未找到}// 获取链表字符串表示public String getListString() {if (head == null) return "NULL"; // 空链表StringBuilder sb = new StringBuilder();Node current = head;while (current != null) {sb.append(current.data);if (current.next != null) sb.append(" → ");current = current.next;}sb.append(" → NULL");return sb.toString();}
}

 单链表有无头节点操作细节总结:

查找:

Node current = head.next; // 从第一个实际结点开始
Node current = head; // 从第一个实际结点开始

删除:

if (i < 1 || head.next == null) return false; // 位置无效或链表为空
if (i < 1 || head == null) return false; // 位置无效或链表为空
        Node pre = head; // 从头结点开始// 寻找第 i-1 个结点(插入位置的前驱)for (int pos = 0; pos < i - 1; pos++) {if (pre.next == null) break; // 提前到达链表末尾pre = pre.next;}if (pre == null) return false; // 前驱结点无效Node pre = head; // 从第一个结点开始// 寻找第 i-1 个结点for (int pos = 1; pos < i - 1; pos++) {if (pre == null) break; // 提前结束pre = pre.next;}if (pre == null) return false; // 前驱结点无效
 pre.next = pre.next.next; // 跳过要删除的结点// 特殊处理 i=1 的情况if (i == 1) {head = head.next; // 头指针指向第二个结点return true;}pre.next = pre.next.next; // 跳过要删除的结点

插入:

     if (i < 1) return false; // 位置无效检查Node pre = head; // 从头结点开始// 寻找第 i-1 个结点(插入位置的前驱)for (int pos = 0; pos < i - 1; pos++) {if (pre.next == null) break; // 提前到达链表末尾pre = pre.next;}if (pre == null) return false; // 前驱结点无效if (i < 1) return false; // 位置无效检查Node pre = head; // 从第一个结点开始// 寻找第 i-1 个结点for (int pos = 1; pos < i - 1; pos++) {if (pre == null) break; // 提前结束pre = pre.next;}if (pre == null) return false; // 前驱结点无效
  newNode.next = pre.next; // 新结点指向原位置结点pre.next = newNode;     // 前驱指向新结点// 特殊处理 i=1 的情况if (i == 1) {Node newNode = new Node(data);newNode.next = head; // 新结点指向原头结点head = newNode;      // 更新头指针return true;}newNode.next = pre.next; // 新结点指向原位置结点pre.next = newNode;     // 前驱指向新结点

问题2:“删除单链表中值为 x 的所有结点,如何避免内存泄漏?” (思路:用前驱指针 pre 遍历,找到目标结点后,pre->next 指向其后继,释放当前结点)

// 带头结点链表中删除所有值为 x 的结点
public void deleteAll(int x) {Node pre = head; // 前驱指针指向头结点Node cur = head.next; // 当前指针指向第一个实际结点while (cur != null) {if (cur.data == x) {// 找到目标结点pre.next = cur.next; // 前驱指针跳过当前结点// 释放内存(在Java中置为null帮助GC,在C++中需要delete)cur.next = null; // 断开引用cur = pre.next; // 移动当前指针到下一个结点} else {// 非目标结点,双指针一起移动pre = cur;cur = cur.next;}}
}
// 无头结点链表中删除所有值为 x 的结点
public void deleteAll(int x) {// 特殊情况:删除开头连续的目标结点while (head != null && head.data == x) {Node temp = head;head = head.next;temp.next = null; // 断开引用}if (head == null) return; // 链表为空Node pre = head;Node cur = head.next;while (cur != null) {if (cur.data == x) {pre.next = cur.next; // 前驱指针跳过当前结点cur.next = null; // 断开引用cur = pre.next; // 移动当前指针} else {pre = cur;cur = cur.next;}}
}

内存泄漏分析及避免方法

操作内存泄漏风险正确做法
未断开引用被删除结点仍被其他对象引用将被删除结点的 next 置为 null
未释放内存已删除结点仍占用内存空间在C++中使用 delete,在Java中依赖GC
指针处理错误链表断裂,无法访问后续结点使用双指针技术确保链表连续性
头指针处理不当删除头结点后未更新头指针单独处理链表开头连续的目标结点
双链表与循环链表

问题1:“双链表中,在 p 所指结点后插入 s 结点,需修改哪些指针?” (步骤:s->next = p->next;p->next->prior = s;s->prior = p;p->next = s)

public class DLinkedList<T> {// ... 省略其他代码/*** 在节点 p 后插入新节点* @param p 目标节点(不能为 null)* @param data 新节点的数据* @return 插入成功返回 true,失败返回 false*/public boolean insertAfter(DNode<T> p, T data) {if (p == null) return false;  // p 无效DNode<T> s = new DNode<>(data);  // 创建新节点 s// 核心步骤:s.next = p.next;     // 1. s 的 next 指向 p 的原后继if (p.next != null) {p.next.prior = s;  // 2. 如果 p 有后继,其 prior 指向 s(文档:p->next->prior=s)}s.prior = p;         // 3. s 的 prior 指向 pp.next = s;          // 4. p 的 next 指向 sreturn true;}
}
public class DLinkedList<T> {// ... 省略其他代码/*** 删除节点 p* @param p 要删除的节点(不能为 null 或头结点)* @return 删除成功返回 true,失败返回 false*/public boolean deleteNode(DNode<T> p) {if (p == null || p == head) return false;  // 禁止删除头结点或无效节点// 核心步骤:if (p.prior != null) {p.prior.next = p.next;  // 1. 前驱节点的 next 指向 p 的后继}if (p.next != null) {p.next.prior = p.prior;  // 2. 后继节点的 prior 指向 p 的前驱}// 清理引用,避免内存泄漏p.prior = null;p.next = null;return true;}
}
public class DLinkedList<T> {// ... 省略其他代码// 后向遍历(从头结点后的第一个节点开始)public void traverseForward() {DNode<T> p = head.next;  // 跳过头结点while (p != null) {System.out.print(p.data + " ");  // 处理节点(如打印)p = p.next;                      // 移动到后继}System.out.println();}// 前向遍历(需先定位到尾节点,然后从尾到头)public void traverseBackward() {// 先找到尾节点(需遍历整个链表)DNode<T> tail = head.next;while (tail != null && tail.next != null) {tail = tail.next;}// 从尾节点开始前向遍历(跳过头结点)DNode<T> p = tail;while (p != null && p != head) {  System.out.print(p.data + " ");p = p.prior;                  // 移动到前驱}System.out.println();}
}

问题2:“循环单链表相比单链表有何优势?如何判断循环链表为空或满?” (考察点:首尾相连,适合环形场景(如约瑟夫问题);判空:头结点 next 指向自身;判满:需额外标志或牺牲一个节点)

// 定义链表结点类,存储数据和下一个结点指针
class LNode<T> {T data;          // 结点数据LNode<T> next;   // 指向下一个结点的指针public LNode(T data) {this.data = data;this.next = null;  // 初始化时next为null,在链表中会被设置}
}// 定义循环单链表类
public class CircularLinkedList<T> {private LNode<T> head;     // 头结点(不存储数据,用于标记链表起始)private int size;          // 当前链表大小(额外标志,用于判满)private int maxSize;       // 链表最大容量(设置判满条件)// 初始化链表:创建头结点,并使其next指向自身(形成环)public CircularLinkedList(int maxSize) {this.head = new LNode<>(null);  // 头结点data为nullthis.head.next = this.head;     // 关键:next指向自身,表示空表this.size = 0;this.maxSize = maxSize;         // 设置最大容量(用于判满)}// 判断链表是否为空:头结点的next指向自身public boolean isEmpty() {return head.next == head;}// 判断链表是否为满:基于size变量(额外标志)public boolean isFull() {return size >= maxSize;  // size达到maxSize时为满}// 添加结点到链表尾部(示例操作,展示环形遍历)public void append(T data) {if (isFull()) {throw new IllegalStateException("LinkedList is full. Cannot append.");}LNode<T> newNode = new LNode<>(data);if (isEmpty()) {// 空表:新结点next指向头结点,头结点next指向新结点newNode.next = head;head.next = newNode;} else {// 非空表:找到尾结点(尾结点的next指向头结点),插入新结点LNode<T> current = head.next;while (current.next != head) {  // 遍历到尾结点current = current.next;}current.next = newNode;       // 尾结点指向新结点newNode.next = head;          // 新结点指向头结点,维持环状}size++;  // 更新大小}// 示例:打印链表(展示环形访问)public void printList() {if (isEmpty()) {System.out.println("LinkedList is empty.");return;}LNode<T> current = head.next;System.out.print("Circular List: ");do {System.out.print(current.data + " -> ");current = current.next;} while (current != head);  // 循环直到回到头结点System.out.println("(head)");  // 表示环闭合}// 主方法:测试判空、判满和环形场景public static void main(String[] args) {// 初始化链表,设置最大容量为3(用于判满)CircularLinkedList<Integer> list = new CircularLinkedList<>(3);// 测试判空System.out.println("Is empty? " + list.isEmpty());  // 输出: true// 添加结点list.append(1);list.append(2);list.append(3);list.printList();  // 输出: Circular List: 1 -> 2 -> 3 -> (head)// 测试判满System.out.println("Is full? " + list.isFull());  // 输出: true// 尝试添加更多结点(会抛异常)try {list.append(4);  // 抛出 IllegalStateException} catch (IllegalStateException e) {System.out.println("Error: " + e.getMessage());}}
}

背景:约瑟夫环问题(Josephus problem)是一个著名的理论和计算机科学问题:​​n个人围成一圈,从第一个人开始报数,每数到k的人被淘汰出局,然后从下一个人重新开始报数,如此循环,直到最后剩下一个人为止​​。这个幸存者在初始圆环中的位置即为约瑟夫问题的解。

 

class Node {int data;Node next;public Node(int data) {this.data = data;this.next = null;}
}public class JosephusProblem {public static int josephus(int n, int k) {if (n <= 0 || k <= 0) {throw new IllegalArgumentException("n and k must be positive integers");}// 创建循环链表Node head = new Node(1);Node prev = head;for (int i = 2; i <= n; i++) {prev.next = new Node(i);prev = prev.next;}prev.next = head; // 形成环Node current = head;Node previous = prev; // 前驱节点// 当链表中不止一个节点时while (current.next != current) {// 报数:移动k-1步(从1开始计数)for (int i = 0; i < k - 1; i++) {previous = current;current = current.next;}// 移除当前节点previous.next = current.next;current = previous.next; // 从下一个节点重新开始}return current.data; // 最后剩下的节点}public static void main(String[] args) {int n = 7;  // 总人数int k = 3;   // 报数到k的人出列int survivor = josephus(n, k);System.out.println("最后剩下的人的编号是: " + survivor);}
}

链表的特殊操作

问题1:“反转单链表(就地逆置),要求空间复杂度O(1)。” (思路:用 prev、curr、next 三个指针迭代反转,避免递归栈开销)

206. 反转链表 - 力扣(LeetCode)

class Solution {public ListNode reverseList(ListNode head) {ListNode cur=head;ListNode pre=null;ListNode next=null;while(cur!=null){next=cur.next;cur.next=pre;pre=cur;cur=next;}return pre;}
}

问题2:“判断单链表是否有环?若有环,如何找到环的入口?” (快慢指针法:快指针每次 2 步,慢指针 1 步,相遇则有环;再从表头和相遇点同步走,交点即为入口)

141. 环形链表 - 力扣(LeetCode)

//双指针法
public class Solution {public boolean hasCycle(ListNode head) {ListNode slow=head;ListNode fast=head;while(fast!=null && fast.next!=null){slow=slow.next;fast=fast.next.next;if(slow==fast)return true;}return false;}
}

142. 环形链表 II - 力扣(LeetCode)

ListNode detectCycle(ListNode head) {if (head == null) return null;// 步骤1:判断是否有环并记录相遇点ListNode slow = head;ListNode fast = head;boolean hasCycle = false;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;if (slow == fast) {hasCycle = true;break;}}// 无环则返回nullif (!hasCycle) return null;// 步骤2:重置慢指针到链表头slow = head;// 步骤3:同步移动直至相遇while (slow != fast) {slow = slow.next;fast = fast.next; // 快指针改为每次1步}return slow; // 相遇点即环入口
}

三、场景应用与选型

1.存储结构选型

问题:“当需要频繁按序号访问元素时,选顺序表还是链表?当需要频繁在中间位置插入删除时,选哪种?为什么?” (考察点:顺序表随机存取O(1)适合高频访问;链表插入删除O(1)已知前驱)适合高频修改)

实际场景设计

问题:“设计一个通讯录系统,支持添加、删除、查询联系人,应采用顺序表还是链表?说明理由。” (考察点:通讯录数据量动态变化(适合链表)、查询频率(若按姓名查询,两种结构差异不大;若按索引,顺序表更优))

2. 手写算法实现

考察目的:考察编码能力和边界处理。

  删除链表中重复元素(有序链表)83. 删除排序链表中的重复元素 - 力扣(LeetCode)

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode deleteDuplicates(ListNode head) {ListNode cur=head;while(cur!=null && cur.next!=null){if(cur.val==cur.next.val){cur.next=cur.next.next;}else{cur=cur.next;}}return head;}
}

关键点:双指针遍历。

3. 复杂问题与优化

考察目的:考察算法设计思维和优化能力。

(1) 合并两个有序链表(LeetCode 21)21. 合并两个有序链表 - 力扣(LeetCode)

思路:虚拟头节点简化边界处理。

class Solution {public ListNode mergeTwoLists(ListNode list1, ListNode list2) {//虚拟头节点ListNode head=new ListNode(-1);ListNode cur=head;while(list1!=null && list2!=null){if(list1.val<=list2.val){cur.next=list1;list1=list1.next;}else{cur.next=list2;list2=list2.next;}cur=cur.next;}cur.next=(list1==null)?list2:list1;return head.next;}
}



4. 场景应用题
考察目的
:将线性表应用于实际问题。

Q:设计一个支持高效插入、删除和随机访问的数据结构(LeetCode 380)。

A:使用 哈希表 + 动态数组

数组存储元素,支持O(1)随机访问。

哈希表存储元素到数组下标的映射,支持O(1)插入/删除。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode Hot 100:42. 接雨水

题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 解析 和题目 盛水最多的容器 类似&#xff0c; LeetCode Hot 100&#xff1a;11. 盛最多水的容器-CSDN博客 只是这里将每一个柱子视为一个宽度为…

【C语言入门级教学】字符指针变量

文章目录1.字符指针变量2. 数组指针变量2.1 数组指针变量初始化3.⼆维数组传参的本质1.字符指针变量 在指针的类型中我们知道有⼀种指针类型为字符指针 char* ; ⼀般使⽤: int main() { char ch w; char* pc &ch;//pc的类型是char**pcw;//对pc解引用 修改ch存放的内容…

【Shell脚本自动化编写——报警邮件,检查磁盘,web服务检测】

Shell脚本自动化编写Shell脚本自动化编写一、判断当前磁盘剩余空间是否有20G&#xff0c;如果小于20G&#xff0c;则将报警邮件发送给管理员&#xff0c;每天检查一次磁盘剩余空间。第一步&#xff1a;准备工作第二步&#xff1a;配置邮件信息第三步&#xff1a;检查磁盘的自动…

Java 接口(下)

三、接口的继承性【基础重点】 1. Java中的接口之间的继承关系是多继承&#xff0c;一个接口可以有多个父接口(1) 语法&#xff1a;interface 接口名 extends 父接口1,父接口2{} 2. 类和接口之间是多实现的关系&#xff1a;一个类可以同时实现多个接口(1) 语法&#xff1a;clas…

学习游戏制作记录(各种水晶能力以及多晶体)8.1

1.实现创建水晶并且能与水晶进行交换位置的能力创建好水晶的预制体&#xff0c;添加动画控制器&#xff0c;传入待机和爆炸的动画创建Crystal_Skill_Control脚本&#xff1a;挂载在水晶预制体上private float crystalExstTime;//水晶存在时间public void SetupCrystal(float _c…

在vscode 如何运行a.nut 程序(Squirrel语言)

在 VS Code 中运行 Squirrel 语言编写的 .nut 程序&#xff0c;需要先配置 Squirrel 运行环境并安装相关插件&#xff0c;具体步骤如下&#xff1a; 一、安装 Squirrel 解释器 Squirrel 程序需要通过其官方解释器 squirrel 或 sq 执行&#xff0c;首先需要安装解释器&#xf…

【数据结构】生活中的数据结构:从吃饭与编程看栈与队列思维

生活中的数据结构&#xff1a;从吃饭与编程看栈与队列思维 在软件开发的世界里&#xff0c;栈&#xff08;Stack&#xff09;和队列&#xff08;Queue&#xff09;是两种基础的数据结构&#xff0c;它们以不同的顺序管理数据&#xff1a;栈遵循后进先出&#xff08;LIFO&#x…

牛客——接头密匙

题目链接&#xff1a;牛客--接头密匙 该题是一个很显然的前缀树问题&#xff0c;只需要构建a中所有数组对应的前缀树&#xff0c;之后求b所处前缀个数即可。关于前缀树的构建&#xff0c;可以观看左老师算法讲解045的视频&#xff0c;简单来讲就是用特殊字符将实际数据隔开&…

【Linux基础知识系列】第六十三篇 - 文件编辑器基础:vim

在 Linux 系统中&#xff0c;文本编辑器是系统管理员和开发人员不可或缺的工具。vim 是一个功能强大的文本编辑器&#xff0c;广泛应用于 Linux 系统中。它支持多种编辑模式&#xff0c;提供了丰富的文本编辑功能&#xff0c;适用于编写代码、配置文件和文档。掌握 vim 的基本使…

音频驱动的视觉特效:粒子、动画与Shader的融合技术

音频驱动视觉效果的实现与应用1. 引言在互动媒体、游戏和数字艺术领域&#xff0c;音频数据实时控制视觉元素已成为核心技术&#xff0c;它能创造沉浸式体验&#xff0c;增强用户参与感。例如&#xff0c;音乐会可视化或VR游戏中&#xff0c;音频信号驱动粒子流动、动画变化和S…

机器学习环境配置

【终极指南】吃透机器学习环境配置&#xff1a;从Conda、CUDA到Docker容器化 大家好&#xff01;在机器学习的旅程中&#xff0c;一个稳定、可复现的环境是成功的基石。 第一部分&#xff1a;核心理念——为何环境配置如此重要&#xff1f; 任何机器学习模型的运行&#xff0c;…

【14】大恒相机SDK C#开发 ——Bitmap.UnlockBits()什么意思?有什么用?bmpData.Scan0;什么意思?有什么用?

文章目录1 Bitmap.UnlockBits()2 bmpData.Scan01 Bitmap.UnlockBits() 在 C# 中&#xff0c;Bitmap.UnlockBits() 方法的作用是解锁通过 Bitmap.LockBits() 方法锁定的位图数据&#xff0c;并释放相关的位图数据结构。 当你使用 Bitmap.LockBits() 方法锁定位图数据时&#x…

什么是doris

文章目录简介使用场景Apache Doris 主要应用于以下场景&#xff1a;实时数据分析&#xff1a;湖仓融合分析&#xff1a;半结构化数据分析&#xff1a;Apache Doris 的核心特性详细请看官方文档&#xff1a; Apache Doris介绍简介 Apache Doris 是一款基于 MPP 架构的高性能、实…

python+pyside6的简易画板

十分简单的一个画板程序&#xff0c;用QLabel控件作为画布&#xff0c;在画布上可以画出直线、矩形、填充矩形、园&#xff0c;椭园、随手画、文本等内容。将原先发布的画板程序中的画文本方法修改成了原位创建一编辑框&#xff0c;编辑框失去焦点后&#xff0c;即将文本画在画…

【数据可视化-76】从释永信被查,探索少林寺客流量深度分析:Python + Pyecharts 炫酷大屏可视化(含完整数据和代码)

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

WPF TreeView自带自定义滚动条

放在TreeView.Resources中&#xff1a;<Style TargetType"ScrollBar"><Setter Property"Stylus.IsPressAndHoldEnabled" Value"false"/><Setter Property"Stylus.IsFlicksEnabled" Value"false"/><Set…

MongoDB 详细用法与 Java 集成完整指南

MongoDB 详细用法与 Java 集成完整指南 目录 MongoDB 基础概念MongoDB 安装与配置MongoDB Shell 基本操作Java 环境准备Java MongoDB 驱动集成连接配置基本 CRUD 操作高级查询操作索引操作聚合管道事务处理Spring Boot 集成最佳实践 1. MongoDB 基础概念 1.1 核心概念对比 …

【Flutter3.8x】flutter从入门到实战基础教程(四):自定义实现一个自增的StatefulWidget组件

fluttet中实现一个自定义的StatefulWidget组件&#xff0c;可以在数据变化后&#xff0c;把最新的页面效果展示给客户 实现效果实现代码 pages文件夹下新加一个counter_page.dart文件 class CounterPage extends StatefulWidget {const CounterPage({super.key});overrideState…

[AI8051U入门第十三步]W5500实现MQTT通信

前言 学习目标: 1、学习MQTT协议 2、了解MQTT数据帧格式 3、自己编写MQTT程序 4、调试MQTT程序一、MQTT协议介绍 MQTT(Message Queuing Telemetry Transport) 是一种轻量级的 发布/订阅(Pub/Sub) 消息传输协议,专为 低带宽、高延迟或不可靠网络 环境设计,广泛应用于 物…

四、基于SpringBoot,MVC后端开发笔记

整合第三方技术&#xff1a; 1、整合Junit (1)名称&#xff1a;SpringBootTest (2)类型&#xff1b;测试类注解 (3)位置&#xff1a;测试类定义上方 (4)作用&#xff1a;设置Junit加载的SpringBoot启动类 (5)相关属性&#xff1a;classes&#xff1a;设置SpringBoot启动类 2、整…