【LeetCode】剑指 Offer Ⅱ 第4章:链表(9道题) -- Java Version
题库链接:https://leetcode.cn/problem-list/e8X3pBZi/
类型 | 题目 | 解决方案 |
---|---|---|
双指针 | 剑指 Offer II 021. 删除链表的倒数第 N 个结点 | 双指针 + 哨兵 ⭐ |
剑指 Offer II 022. 链表中环的入口节点(环形链表) | 双指针:二次相遇 ⭐ | |
剑指 Offer II 023. 两个链表的第1个重合节点(相交链表) | 双指针:链表拼接 ⭐ | |
反转链表 | 剑指 Offer II 024. 反转链表 | 迭代:模拟双向链表 ⭐ |
剑指 Offer II 025. 链表中的数字相加(2022.01.07 字节二面) | 模拟:辅助栈(ArrayDeque)⭐ | |
剑指 Offer II 026. 重排链表(2021.09.13 美团一面) | 找中点 + 反转链表 + 合并链表 ⭐ | |
剑指 Offer II 027. 回文链表 | 双指针 + 线性表 ⭐ | |
双向和循环链表 | 剑指 Offer II 028. 扁平化多级双向链表 | 深搜:递归 ⭐ |
剑指 Offer II 029. 排序的循环链表 | 模拟:一次遍历(分类讨论)⭐ |
本章题目包含知识点有:哨兵节点、双指针、反转链表、双向链表、循环链表
- 哨兵节点:如果一个操作可能产生新的头节点,则可以尝试在链表的最前面添加一个哨兵节点来简化代码逻辑,降低代码出现问题的可能性;
- 双指针: 前后双指针的思路是让一个指针提前走若干步,然后将第2个指针指向头节点,两个指针以相同的速度一起走;快慢双指针则是让快的指针每次走两步,而慢的指针每次只走一步;
- 反向链表:用于需要从后往前遍历链表的情况;
- 双向链表:要注意每个指针都指向了正确的位置;
- 循环链表:循环链表中的所有节点都在一个环中,因此要特别注意死循环问题,当遍历完链表中的所有节点就要及时停止,避免在环中绕圈子。
1. 剑指 Offer II 021. 删除链表的倒数第 N 个结点 – P50
给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
1.1 双指针 + 哨兵 – O(n)(⭐)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
/*** 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 removeNthFromEnd(ListNode head, int n) {ListNode sentinel = new ListNode(0,head); // 避免单独处理头节点ListNode fast = head;ListNode slow = sentinel;while (n -- > 0) { // fast 指针先走 n 步fast = fast.next;}while (fast != null) { // 然后 fast 与 slow 一起走fast = fast.next;slow = slow.next;}slow.next = slow.next.next;return sentinel.next;}
}
2. 剑指 Offer II 022. 链表中环的入口节点(环形链表) – P52
给定一个链表,返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next 指针进入环的第一个节点为环的入口节点。如果链表无环,则返回 null。
可参考图解:LCR 022. 环形链表 II - 力扣(LeetCode)
2.1 双指针 – O(n)(⭐)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
/*** Definition for singly-linked list.* class ListNode {* int val;* ListNode next;* ListNode(int x) {* val = x;* next = null;* }* }*/
public class Solution {public ListNode detectCycle(ListNode head) {ListNode fast = head;ListNode slow = head; while (true) {if (fast == null || fast.next == null) return null;fast = fast.next.next;slow = slow.next;if (fast == slow) break; // 第一次相遇}fast = head;while (fast != slow) { // 第二次相遇,即找到了入口节点fast = fast.next;slow = slow.next;}return fast;}
}
2.2 哈希表 – O(n)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)
/*** Definition for singly-linked list.* class ListNode {* int val;* ListNode next;* ListNode(int x) {* val = x;* next = null;* }* }*/
public class Solution {public ListNode detectCycle(ListNode head) {ListNode pos = head;Set<ListNode> visited = new HashSet<>();while (pos != null) {if (visited.contains(pos)) return pos;visited.add(pos);pos = pos.next;}return null;}
}
3. 剑指 Offer II 023. 两个链表的第1个重合节点(相交链表) – P55
给定两个单链表的头节点 headA 和 headB ,请找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
3.1 双指针:链表拼接 – O(m+n)(⭐)
时间复杂度 O ( m + n ) O(m+n) O(m+n),空间复杂度 O ( 1 ) O(1) O(1)
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode(int x) {* val = x;* next = null;* }* }*/
public class Solution {public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if (headA == null || headB == null) return null;ListNode pA = headA;ListNode pB = headB;while (pA != pB) {pA = pA == null ? headB : pA.next; // A 走完了接到 BpB = pB == null ? headA : pB.next; // B 走完了接到 A}return pA;}
}
4. 剑指 Offer II 024. 反转链表 – P59
给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点。
4.1 迭代:模拟双向链表 – O(n)(⭐)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
/*** 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 reverseList(ListNode head) {ListNode prev = null; // 记录当前节点的前一节点ListNode curr = head;while (curr != null) {ListNode next = curr.next; // 记录当前节点的后一节点curr.next = prev; // 反转指向:让当前节点的后一节点指向前一节点prev = curr;curr = next;}return prev;}
}
4.2 递归:head.next.next = head – O(n)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)
/*** 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 reverseList(ListNode head) {if (head == null || head.next == null) return head;ListNode curr = reverseList(head.next);head.next.next = head; // 反向:当前节点的下下指向是它本身head.next = null; // 切断原来的指向return curr;}
}
5. 剑指 Offer II 025. 链表中的数字相加 – P60
给定两个 非空链表 l1和 l2 来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
5.1 模拟:辅助栈(ArrayDeque)-- O(max(m,n))(⭐)
时间复杂度 O ( m a x ( m , n ) ) O(max(m,n)) O(max(m,n)),空间复杂度 O ( m + n ) O(m+n) O(m+n)
/*** 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 addTwoNumbers(ListNode l1, ListNode l2) {Deque<Integer> q1 = new ArrayDeque<>(); // ArrayDeque实现栈的功能Deque<Integer> q2 = new ArrayDeque<>();while (l1 != null) { // 将链表1存入栈q1中q1.addFirst(l1.val);l1 = l1.next;}while (l2 != null) { // 将链表2存入栈q2中q2.addFirst(l2.val);l2 = l2.next;}int carry = 0;ListNode res = null;while (!q1.isEmpty() || !q2.isEmpty() || carry != 0) {int a = q1.isEmpty() ? 0 : q1.poll(); // 后进先出int b = q2.isEmpty() ? 0 : q2.poll();int cur = a + b + carry;carry = cur / 10; // 计算进位cur = cur % 10;ListNode node = new ListNode(cur, res); // 构造新链表res = node;}return res;}
}
5.2 模拟:反转链表再相加 – O(max(m,n))
时间复杂度 O ( m a x ( m , n ) ) O(max(m,n)) O(max(m,n)),空间复杂度 O ( 1 ) O(1) O(1)
手写的一般是要比使用库函数来得快一些,但是使用库函数书写会更简洁,各有利弊。
/*** 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 addTwoNumbers(ListNode l1, ListNode l2) {ListNode h1 = reverseLinked(l1);ListNode h2 = reverseLinked(l2);ListNode res = addTwoLinked(h1, h2);return res;}public ListNode addTwoLinked(ListNode h1, ListNode h2) { // 链表按位相加int carry = 0;ListNode res = null;while (h1 != null || h2 != null || carry != 0) {int a = h1 == null ? 0 : h1.val;int b = h2 == null ? 0 : h2.val;int cur = a + b + carry;carry = cur / 10;cur = cur % 10;res = new ListNode(cur, res);h1 = h1 == null ? null : h1.next; // 防止空指针异常h2 = h2 == null ? null : h2.next;}return res;}public ListNode reverseLinked(ListNode head) { // 反转链表if (head == null || head.next == null) return head;ListNode cur = reverseLinked(head.next);head.next.next = head;head.next = null;return cur;}
}
PS:补充知识1 - 【ArrayDeque】
ArrayDeque 类继承 AbstractCollection 抽象类,并且实现了 Deque 接口
public class ArrayDeque<E> extends AbstractCollection<E>implements Deque<E>, Cloneable, Serializable
具体可参考:
[1] 一文详解 ArrayDeque 双端队列使用及实现原理 - 知乎
[2] 你真的了解环形队列吗?- yuyulovespicy的博客
[3] 集合框架之ArrayDeque类详解 - 妙乌的博客
① ArrayDeque的基本方法
具体内容可参考:Leetcode 工具箱 - MarcyTheLibrarian的博客
ArrayDeque 是 Java 中对双端队列的线性实现(数组)
……
特性:
- 来自JDK1.6,底层采用可变容量的环形数组实现一个双端队列,有序存放;
- 无容量大小限制,容量按需增长,默认大小为16;可指定大小,最小为8;容量不足时会进行扩容,每次扩容容量增加一倍;
- 非线程安全队列,无同步策略,不支持多线程安全访问;
- 当用作栈时,性能优于Stack,当用于队列时,性能优于LinkedList;
- 两端都可以操作,且同时支持栈和队列操作;
- 具有fail-fast特性,不能存储null值,支持双向迭代器遍历;
- 出队入队是通过头尾指针循环,利用数组实现的;没有实现list接口,不能通过索引操作元素。
……
方法列表:
类型 方法 说明 添加元素 public void addFirst(E e) 在数组前面添加元素 public void addLast(E e) 在数组后面添加元素 public boolean offerFirst(E e) 在数组前面添加元素,并返回是否添加成功 public boolean offerLast() 在数组后面添加元素,并返回是否添加成功 删除元素 public E pollFirst() 删除第一个元素,并返回删除元素的值,如果元素为null,将返回null public E pollLast() 删除最后一个元素,并返回删除元素的值,如果为null,将返回null public E removeFirst() 删除第一个元素,并返回删除元素的值,如果元素为null,将抛出异常 public E removeLast() 删除最后一个元素,并返回删除元素的值,如果为null,将抛出异常 public boolean removeFirstOccurrence(Object o) 删除第一次出现的指定元素 public boolean removeLastOccurrence(Object o) 删除最后一次出现的指定元素 获取元素 public E getFirst() 获取第一个元素,如果没有将抛出异常 public E getLast() 获取最后一个元素,如果没有将抛出异常 队列操作 public boolean add(E e) 在队列尾部添加一个元素 public boolean offer(E e) 在队列尾部添加一个元素,并返回是否成功 public E remove() 删除队列中第一个元素,并返回该元素的值,如果元素为null,将抛出异常(其实底层调用的是removeFirst() public E peek() 获取第一个元素,没有返回null 栈操作 public void push(E e) 栈顶添加一个元素 public E pop() 移除栈顶元素,如果栈顶没有元素将抛出异常 其他 public int size() 获取队列中元素个数 public boolean isEmpty() 判断队列是否为空 public Iterator iterator() 迭代器,从前向后迭代 public Iterator descendingIterator() 迭代器,从后向前迭代 public boolean contains(Object o) 判断队列中是否存在该元素 public Object[] toArray() 转成数组 public T[] toArray(T[] a) 转成a数组 public void clear() 清空队列 public ArrayDeque clone() 克隆(复制)
当作队列使用时:先进先出
Deque<E> queue = new ArrayDeque<>();
queue.size()
queue.addLast(E e)
queue.removeFirst()
当作栈使用时:后进先出
Deque<E> stack = new ArrayDeque<>();
stack.size()
stack.addLast(E e)
stack.removeLast()
② ArrayDeque的成员变量
// 用数组存放队列元素,长度为2的幂,默认长度为16
transient Object[] elements;
// 头指针,为当前头部的index
transient int head;
// 尾指针,下一个要添加到尾步的index (除tail=0时,当前的尾部为tail-1)
transient int tail;
// 用于新创建的双端队列的最小容量,必须是 2 的幂
private static final int MIN_INITIAL_CAPACITY = 8;
🎈 补充:transient 关键字一般在实现了 Serializable 接口的类中使用,被 transient 修饰的变量不参与序列化和反序列化。
……
更多内容可参考:
[1] Java中的关键字transient,这篇文章你再也不发愁了
[2] Java 关键字 transient 竟然还能这么用 - 知乎
💡 总结:ArrayDeque 中的数组本质上是一个环形数组,其通过数组+双指针的方式,实现双端队列,最小容量为8且必须为2的幂次方。
③ ArrayDeque的迭代器接口
在Java编程中,迭代器接口是一种用于遍历集合类对象的工具。ArrayDeque 通过 iterator()
和 descendingIterator()
方法实现了双向迭代器遍历。
🎈 注意:Iterator 迭代器遍历一般都是单向的,只能从前往后遍历,不支持逆向遍历。descendingIterator() 方法是 Deque 接口中定义的方法,仅适用于双端队列,可用于在双端队列的尾部向头部进行逆向迭代。而一般要想在 List 类型的集合中实现逆向迭代则需要使用 ListIterator 接口。
迭代器接口方法
迭代器接口包含以下几个核心方法:
- hasNext():判断集合中是否还有下一个元素;
- next():返回集合中的下一个元素;
- remove():从集合中移除上一次返回的元素(可选操作).
ArrayDeque的迭代器遍历
正向迭代器(iterator()
):从前往后遍历
import java.util.ArrayDeque;
import java.util.Iterator;public class ArrayDequeIteratorExample {public static void main(String[] args) {// 创建一个ArrayDeque并添加元素ArrayDeque<String> arrayDeque = new ArrayDeque<>();arrayDeque.add("A");arrayDeque.add("B");arrayDeque.add("C");// 获取迭代器并遍历元素Iterator<String> iterator = arrayDeque.iterator();while (iterator.hasNext()) {String element = iterator.next();System.out.println(element);}}
}
逆向迭代器(descendingIterator()
):从后往前遍历
import java.util.ArrayDeque;
import java.util.Iterator;public class ArrayDequeDescendingIteratorExample {public static void main(String[] args) {// 创建一个ArrayDeque并添加元素ArrayDeque<String> arrayDeque = new ArrayDeque<>();arrayDeque.add("A");arrayDeque.add("B");arrayDeque.add("C");// 获取逆向迭代器并逆向遍历元素Iterator<String> descendingIterator = arrayDeque.descendingIterator();while (descendingIterator.hasNext()) {String element = descendingIterator.next();System.out.println(element);}}
}
④ ListIterator接口与descendingIterator()方法的区别
💡 总结:
……
- ListIterator是一个专门用于列表(List)类型集合的迭代器接口,它是Iterator 接口的子接口,提供了更多的功能和灵活性。ListIterator 除了支持正向迭代(从前往后),还通过 hasPrevious()、previous() 等方法实现了逆向迭代(从后往前);此外,ListIterator 还支持在迭代过程中插入、替换和删除元素。
- descendingIterator() 方法是 Deque 接口中定义的方法,用于获取逆向迭代器。Deque 是双端队列的接口,包括 ArrayDeque 在内。
……
简言之:ListIterator 是 Iterator 的子接口,适用于对列表进行正向和逆向迭代,同时支持元素的插入、替换和删除操作;而 descendingIterator() 是独属于双端队列(Deque 以及 ArrayDeque)的方法,用于在双端队列中以逆向的方式访问元素。
ListIterator 实现迭代
正向迭代:与 Iterator
基本一致
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;public class ListIteratorExample {public static void main(String[] args) {// 创建一个列表并添加元素List<String> list = new ArrayList<>();list.add("A");list.add("B");list.add("C");// 获取ListIterator并正向遍历元素ListIterator<String> listIterator = list.listIterator();while (listIterator.hasNext()) {String element = listIterator.next();System.out.println(element);}}
}
逆向迭代:使用 hasPrevious()
方法判断是否存在前一个元素,然后通过 previous()
方法从当前位置向前移一个位置
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;public class ListIteratorExample {public static void main(String[] args) {// 创建一个列表并添加元素List<String> list = new ArrayList<>();list.add("A");list.add("B");list.add("C");// 获取ListIterator并逆向遍历元素ListIterator<String> listIterator = list.listIterator(list.size()); // 从最后一个元素开始while (listIterator.hasPrevious()) {String element = listIterator.previous();System.out.println(element);}}
}
⑤ Iterator与ListIterator的区别
🚩 迭代器相关内容可参考:
[1] Java 核心面试题全解析 - 油腻的程序猿啊的博客中的 31 ~ 34 问
💡 总结:
- Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
- Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
- ListIterator 从 Iterator 接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。
⑥ toArray()与toArray(T[] a)的区别
🚩 更多内容可参考:
[1] 深入理解ArrayList中 toArray(),toArray(T[])方法 - XRYMIBZ的博客
[2] Collection中 Object[] toArray()和 T[] toArray(T[])方法 - DimplesDimples.的博客
toArray()
方法
public Object[] toArray() {return Arrays.copyOf(this.elementData, this.size);
}
toArray(T[] a)
方法
public <T> T[] toArray(T[] var1) {if(var1.length < this.size) {return (Object[])Arrays.copyOf(this.elementData, this.size, var1.getClass());} else {System.arraycopy(this.elementData, 0, var1, 0, this.size);if(var1.length > this.size) {var1[this.size] = null;}return var1;}
}
💡 总结:
- toArray() 方法,底层实质上是调用了 Arrays.copyof() 方法,将ArrayList类型的对象转换为数组(copyof()方法生成的新数组);
- toArray(T[] a) 方法,其内部实现,则是先将 ArrayList 列表的长度和我们提供的数组 a 的长度进行比较:
- 如果是 ArrayList 列表长度更长,那么就调用 Arrays.copyOf() 方法,和 toArray() 方法一样,生成新的数组,然后依次将元素复制过去;
- 如果是 a 数组的长度更长,那么将直接使用 a 数组进行元素复制的操作,并将 ArrayList 对象长度的末尾置为null(
a[this.size] = null
;).
PS:补充知识2 - 【集合的遍历方式 4 & 7】
Java中遍历集合的方式总共有7种,但一般最常用的仅有4种;常用遍历方法将以 ⭐ 号标记。
🚩 具体内容可参考:
[1] Java - 集合遍历的7种方式详解(for、foreach、iterator、并行流等)
[2] Java遍历集合的三种方法 - RainbowCoder的博客
[3] 3种遍历集合的方法(原理,复杂度,适用场合)- 出处不详,经久不息的博客
① 基本的 for 循环 ⭐【性能要求不高的情况】
最简单,最基础的遍历方式。但该方式需要知道集合的长度,不适合所有集合。
List<String> list = Arrays.asList("hangge", "google", "baidu");
for (int i = 0; i < list.size(); i++){System.out.println(list.get(i));
}
② 使用迭代器遍历 ⭐【在遍历集合的同时对其进行修改】
(1)大多数的集合类(例如 java.util.List、java.util.Set 和 java.util.Map)都提供了 iterator() 方法来返回一个 java.util.Iterator 对象,用于遍历集合中的元素。下面是一个简单的样例:
🎈 注意:虽然 java.util.Enumeration 也可以用来遍历集合中的元素。不过,java.util.Enumeration 接口在 Java 的新版本中已经被认为是过时的,应该使用 java.util.Iterator 接口代替。
List<String> list = Arrays.asList("hangge", "google", "baidu");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {String element = iterator.next();System.out.println(element);
}
(2)与下面的 for-each 循环相比,使用 Iterator 的方式更加灵活,因为它允许手动控制迭代过程,例如在迭代过程中修改集合、跳过元素或在多个集合之间进行迭代。比如下面样例在迭代过程中修改集合:
🎈 注意:使用 for-each 循环时不能在循环内修改集合,否则会抛出 java.lang.UnsupportedOperationException 异常。
List<String> list = Arrays.asList("hangge", "google", "baidu");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {String s = iterator.next();if (s.equals("google")) {iterator.remove(); // 移除元素}
}
③ 使用 for-each 循环(也称为增强型 for 循环)⭐
(1)for-each 循环遍历使用 Iterator 的方式在语法上略有不同,但基本上是等价的。下面是一个使用样例:
List<String> list = Arrays.asList("hangge", "google", "baidu");
for (String s : list) {System.out.println(s);
}
(2)for-each 循环本质上是使用了迭代器模式,它将迭代器的实现细节隐藏在了语法层面。当使用 for-each 循环遍历集合时,编译器会将其转换为使用迭代器的方式。比如上面代码会被编译器转换为类似于以下代码,在底层实现上,for-each 循环和使用 Iterator 的方式是等价的:
List<String> list = Arrays.asList("hangge", "google", "baidu");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {String s = iterator.next();System.out.println(s);
}
④ 使用 Java 8 的 forEach 方法遍历 ⭐【使用 Lambda 表达式】
(1)从 Java 8 开始,我们可以使用 forEach() 方法来迭代列表的元素,这个方法在 Iterable 接口中定义。下面是一个简单样例:
🎈 注意:虽然 forEach 方法可以很方便地遍历任何实现了 Iterable 接口的集合(它本身就是基于 Iterator 实现的),但是它并不能用来遍历数组。
List<String> list = Arrays.asList("hangge", "google", "baidu");
list.forEach(element -> {System.out.println(element);
});
(2)该方式还可以配合方法引用来使用,下面代码效果通上面一样:
List<String> list = Arrays.asList("hangge", "google", "baidu");
list.forEach(System.out::println);
⑤ 使用 Stream API 的 forEach 方法遍历
(1)使用 Stream API 可以很方便地对集合进行各种操作,下面是一个简单的样例:
List<String> list = Arrays.asList("hangge", "google", "baidu");
list.stream().forEach(element -> {System.out.println(element);
});
(2)该方式同样可以配合方法引用来使用,下面代码效果通上面一样:
List<String> list = Arrays.asList("hangge", "google", "baidu");
list.stream().forEach(System.out::println);
(3)Stream API 的 forEach 方法出了可以遍历集合的,还可以用来遍历任何支持流的对象,包括集合、数组、文件、函数生成器等。
String[] array = {"hangge", "google", "baidu"};
Arrays.stream(array).forEach(element -> {System.out.println(element);
});
⑥ 使用 ListIterator 接口遍历集合【遍历 List 集合的同时进行修改】
(1)ListIterator 是 Iterator 接口的子接口。ListIterator 可以向前或向后遍历列表中的元素,并允许在列表中插入和替换元素。下面是一个简单的样例,可以看到向后遍历和 Iterator 用法是一样的:
List<String> list = Arrays.asList("hangge", "google", "baidu");
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {String name = iterator.next();System.out.println(name);
}
(2)我们还可以使用 ListIterator 向前遍历 List 中的元素:
🎈 注意:要向前遍历的话需要创建了一个从列表末尾开始的 ListIterator。
List<String> list = Arrays.asList("hangge", "google", "baidu");
//创建了一个从列表末尾开始的ListIterator
ListIterator<String> iterator = list.listIterator(list.size());
while (iterator.hasPrevious()) {String name = iterator.previous();System.out.println(name);
}
(3)我们还可以使用 ListIterator 来修改、添加或删除集合中的元素,比如下面样例我们将 hangge 修改为 hangge.com,在 baidu 之后添加了一个新元素 apple,并删除了所有其他元素。
//创建一个可变列表类型
List<String> list = new ArrayList<>(Arrays.asList("hangge", "google", "baidu"));
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {String name = iterator.next();if (name.equals("hangge")) {iterator.set("hangge.com"); // 修改元素} else if (name.equals("baidu")) {iterator.add("apple"); // 在 baidu 之后添加元素} else {iterator.remove(); // 删除元素}
}
System.out.println(list);
⑦ 使用并行流遍历【对大型集合进行并行处理】
(1)并行流使用多个线程来并行处理集合中的元素,可以提高处理速度。上面的代码使用了并行流来遍历集合 list 中的元素,并将它们打印出来。
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// 创建一个并行流(将parallel标志指定为true表示创建并行流)
Stream<Integer> parallelStream = list.parallelStream();
// 查看流是否支持并行遍历
System.out.println("流是否支持并行遍历: " + parallelStream.isParallel());
// 使用 forEach() 方法遍历并行流
parallelStream.forEach(element -> {System.out.println(element);
});
PS:补充知识3 - 【集合类图】
🚩 更多内容可参考:
[1] Java Collection Cheat Sheet
PS:补充知识4 - 【fail-fast 和 fail-safe】
🚩 更多内容可参考:
[1] 谈谈fail-fast与fail-safe是什么以及工作机制 - 知乎 (讲的比较透彻)
[2] fail-fast 和 fail-safe 快速学习 - 杨戬的博客(有代码调试示例)
[3] Java 中的 Fail-Fast 与 Fail-Safe - 码农汉子的博客
① fail-fast与fail-safe的区别
💡 总结:
- fail-fast,即快速失败机制,它是 Java 对 java.util 包下的所有集合类的是一种错误检测机制,当多个(或单个)线程在结构上对集合进行改变时(插入和删除,修改不算),就有可能会产生fail-fast机制,从而抛出 ConcurrentModificationException 异常(通常发生在迭代器元素遍历中);
- 实现原理:迭代器每当执行一次 next() / hasNext() 等方法时,都会调用一次 checkForComodification() 这个方法,查看modCount 与 expectedModCount是否保持一致,如果不一致,则说明集合元素的个数发生了改变,从而抛出异常;
- 处理方案:如果我们不希望在迭代器遍历的时候因为并发等原因,导致集合的结构被改变,进而可能抛出异常的话,我们可以在涉及到会影响到 modCount 值改变的地方,加上同步锁 (synchronized),或者直接使用 Collections.synchronizedList 来解决。
- fail-safe,安全失败机制,它是 Java 对 java.util.concurrent 包下的所有集合类的是一种错误检测机制,其在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历(不会抛出异常)。
- 实现原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
- 缺点:1. 复制集合时需要额外的空间和时间上的开销;2. 不能保证遍历的是最新内容(迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的)。
② java.util 与 java.util.concurrent 下的集合
🚩 更多内容可参考:
[1] Java集合框架总述(java.util包) | 羊驼之野望(java.util包中集合的简单介绍)
[2] Java并发——JUC包下的并发集合类 - 社会你鑫哥的博客(JUC集合相关介绍)
[3] 理清Java集合类(Util包和Concurrent包) - 灰信网(介绍的比较清晰)
[4] 集合类存放于java.util包下,主要的接口说明 - Zsiyuan的博客(图解)
[5] Java.util.concurrent包下集合类的特点与适用场景 - 梦幻朵颜 - 博客园(表格形式总结)
- java.util 包下的集合【fail-fast】
- java.util.concurrent(JUC)包下的集合
JUC 包中 Map 实现类
PS:补充知识5 - 【对象持久化】
Java的对象持久化是指将内存中的Java对象保存到持久存储介质(如磁盘、数据库等)中,以便在程序重新启动或数据传输时能够恢复这些对象的状态。
……
对象持久化是在应用程序中保留数据的重要方法之一,它允许数据在不同的执行环境中保持一致性,从而实现数据的长期存储和共享。
🚩 更多内容可参考:
[1] Java持久化详解(简单介绍)
[2] Java对象持久化保存的方法(表格形式)
[3] Java中的ORM框架有哪些,Hibernate使用讲解 - 计算机徐师兄的博客(Hibernate)
① 常见的对象持久化方法
💡 总结:在Java中,实现对象持久化的方法有多种,但根据存储方式的不同大致可以分为两类:文件存储 和 数据库存储。
- 文件存储:文件存储是最简单和最常见的持久化方式之一。通过将数据以文件的形式存储在磁盘上,可以实现数据的持久保存和读取。Java提供了多种文件操作API,如FileInputStream和FileOutputStream,可用于读写文件。由此,我们就可以通过 序列化\反序列化 或者 XML或JSON 来将对象存入文件。
……
- 序列化 (Serialization):Java提供了一个Serializable接口,让类可以将其对象序列化为字节流,然后在需要时反序列化回对象。
// 序列化对象 try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("object.dat"))) {outputStream.writeObject(myObject); } // 反序列化对象 try (ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("object.dat"))) {MyObject restoredObject = (MyObject) inputStream.readObject(); }
……
- XML或JSON格式:
// 使用Jackson库将对象保存为JSON ObjectMapper objectMapper = new ObjectMapper(); objectMapper.writeValue(new File("object.json"), myObject); // 从JSON中还原对象 MyObject restoredObject = objectMapper.readValue(new File("object.json"), MyObject.class);
……
2. 数据库存储:通过将数据存储在关系型数据库或非关系型数据库中,可以实现数据的持久保存、查询和更新。
- 传统的JDBC或ORM框架是关系型数据库的标准访问和映射工具,非关系型数据库(NoSQL数据库)通常采用不同的数据模型和访问方法。
- NoSQL数据库可以用于缓存存储(Redis),但不限于缓存存储;NoSQL数据库通常更适合用于长期数据存储和分析(MongoDB)。非关系型数据库可用于:文档型(Web应用)、key-value型(内容缓存)、列式数据库(分布式文件系统)、图形数据库(社交网络、推荐系统)。
② 关系型数据库和非关系型的区别
🚩 更多内容可参考:
[1] 关系型数据库和非关系型数据及其区别 - 乌梅子酱~的博客
[2] 关系型数据库和非关系型区别 - 一只IT攻城狮的博客
💡 总结:
- 数据存储方式不同:关系型数据天然就是表格式的,因此存储在数据表的行和列中,结构化存储;非关系型数据通常存储在数据集中,就像文档、键值对、列存储、图结构。
- 扩展方式不同:关系型数据库难以横向拓展,为支持更多并发量,SQL数据库是纵向扩展,也就是说提高处理能力;而NoSQL数据库是横向扩展的,非关系型数据存储天然就是分布式的,可以通过给资源池添加更多普通的数据库服务器(节点)来分担负载。
- 对事务性的支持不同:SQL数据库支持对事务原子性细粒度控制,并且易于回滚事务;而NoSQL数据库是最终一致性,一般不保证ACID的数据存储系统,但其具有极高的并发读写性能,真正闪亮的价值是在操作的扩展性和大数据量处理方面。
6. 剑指 Offer II 026. 重排链表 – P63
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln-1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …
6.1 双指针 + 线性表(List)-- O(n)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)
/*** 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 void reorderList(ListNode head) {if (head == null || head.next == null) return;List<ListNode> list = new ArrayList<>();while (head != null) { // 将链表存入线性表list.add(head);head = head.next;}int l = 0, r = list.size()-1;while (l < r) {list.get(l).next = list.get(r);l++;//if (l == r) break; // 偶数提前跳出,加不加都可list.get(r).next = list.get(l);r--;}list.get(l).next = null;}
}
6.2 找中点 + 反转链表 + 合并链表 – O(n)(⭐)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
/*** 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 {// 2. 找中点,反转中点后链表,重排链表public void reorderList(ListNode head) {if (head == null) {return;}ListNode mid = findMid(head); // 找中点ListNode l1 = head;ListNode l2 = mid.next;mid.next = null;l2 = reverseList(l2);mergeList(l1, l2);}// 合并重排链表public void mergeList(ListNode l1, ListNode l2) {ListNode l1_tmp;ListNode l2_tmp;while (l1 != null && l2 != null) {l1_tmp = l1.next;l2_tmp = l2.next;l1.next = l2;l1 = l1_tmp;l2.next = l1;l2 = l2_tmp;}}// 反转链表public ListNode reverseList(ListNode head) {if (head == null || head.next == null) return head;ListNode cur = reverseList(head.next);head.next.next = head;head.next = null;return cur;}// 找出链表中点public ListNode findMid(ListNode head) {ListNode fast = head;ListNode slow = head;while (fast.next != null && fast.next.next != null) {fast = fast.next.next;slow = slow.next;}return slow;}
}
PS:补充知识1 - 【线性表 List】
💡 说明:线性表(List)划分为顺序表(ArrayList)和链表(LinkList),需要强调的是,在数据结构中,线性表被设计成一个接口,顺序表和链表都继承实现了该接口内的方法
……
更多内容可参考:
[1] JAVA版数据结构-----线性表 - 一入猿门深似海的博客
7. 剑指 Offer II 027. 回文链表 – P65
给定一个链表的 头节点 head ,请判断其是否为回文链表。
如果一个链表是回文,那么链表节点序列从前往后看和从后往前看是相同的。
7.1 辅助栈(ArrayDeque)-- O(n)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)
/*** 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 boolean isPalindrome(ListNode head) {if (head == null) return false;Deque<ListNode> stack = new ArrayDeque<>();ListNode cur = head;while(cur != null) { // 将链表元素入栈stack.push(cur);cur = cur.next;}while(head != null){ // 依次比较if(head.val != stack.pop().val){return false;}head = head.next;}return true;}
}
7.2 双指针 + 线性表 – O(n)(⭐)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)
/*** 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 boolean isPalindrome(ListNode head) {if (head == null) return false;List<Integer> vals = new ArrayList<>();while (head != null) { // 将链表元素的值存入线性表vals.add(head.val);head = head.next;}int l = 0, r = vals.size()-1;while (l < r) { // 双指针折半判断是否回文if (vals.get(l) != vals.get(r)) return false;l++;r--;}return true;}
}
7.3 找中点 + 反转链表 + 判断回文 – O(n)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
该方法属于该题的最优解,但实际效果不佳,因此在实际情况下更推荐 7.2 双指针 + 线性表 的解题方法。
/*** 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 boolean isPalindrome(ListNode head) {if (head == null) return false;ListNode mid = findMid(head);ListNode l1 = head;ListNode l2 = reverseList(mid.next);mid.next = null;while (l1 != null && l2 != null) { // 折半判断回文if (l1.val != l2.val) return false;l1 = l1.next;l2 = l2.next;}return true;}public ListNode findMid(ListNode head) { // 找中点ListNode fast = head;ListNode slow = head;while (fast.next != null && fast.next.next != null) {fast = fast.next.next;slow = slow.next;}return slow;}public ListNode reverseList(ListNode head) { // 反转链表if (head == null || head.next == null) return head;ListNode cur = reverseList(head.next);head.next.next = head;head.next = null;return cur;}
}
8. 剑指 Offer II 028. 扁平化多级双向链表 – P67
多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构。
给定位于列表第一级的头节点,请扁平化列表,即将这样的多级双向链表展平成普通的双向链表,使所有结点出现在单级双链表中。
💡 提示:展平的规则是一个节点的子链展平之后将插入该节点和它的下一个节点之间。
8.1 深搜:递归 – O(n)(⭐)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)
/*
// Definition for a Node.
class Node {public int val;public Node prev;public Node next;public Node child;
};
*/class Solution {public Node flatten(Node head) {dfs(head);return head;}public Node dfs(Node head) { // 找到每一级的尾节点,并完成平展Node cur = head;Node tail = null;while (cur != null) {Node next = cur.next;if (cur.child != null) {Node child = cur.child;Node childTail = dfs(child); // 找到了平展的尾节点cur.child = null; // 平展该级子列表cur.next = child;child.prev = cur;childTail.next = next;if (next != null) {next.prev = childTail;}tail = childTail; // 不能忘记} else {tail = cur;}cur = next;}return tail;}
}
9. 剑指 Offer II 029. 排序的循环链表 – P69
给定循环单调非递减列表中的一个点,写一个函数向这个列表中插入一个新元素 insertVal ,使这个列表仍然是循环升序的。
9.1 模拟:一次遍历(分类讨论)-- O(n)(⭐)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
/*
// Definition for a Node.
class Node {public int val;public Node next;public Node() {}public Node(int _val) {val = _val;}public Node(int _val, Node _next) {val = _val;next = _next;}
};
*/
class Solution {public Node insert(Node head, int insertVal) {Node node = new Node(insertVal);if (head == null) { // 1. 空链表node.next = node;return node;}if (head.next == head) { // 2. 链表中只有一个节点head.next = node;node.next = head;return head;}Node curr = head, next = head.next; // 3. 多节点while (next != head) {if (insertVal >= curr.val && insertVal <= next.val) { // 3.1 插入值在链表范围内break;}if (curr.val > next.val) { // 此时curr是最大元素if (insertVal > curr.val || insertVal < next.val) { // 3.2 插入值不在链表范围内break;}}curr = curr.next;next = next.next;}curr.next = node;node.next = next;return head;}
}
10. 继续提升:加练题目
🎈 可参考:
- 链表 · SharingSource/LogicStack-LeetCode Wiki · GitHub
- DFS · SharingSource/LogicStack-LeetCode Wiki · GitHub
相关文章:
【LeetCode】剑指 Offer Ⅱ 第4章:链表(9道题) -- Java Version
题库链接:https://leetcode.cn/problem-list/e8X3pBZi/ 类型题目解决方案双指针剑指 Offer II 021. 删除链表的倒数第 N 个结点双指针 哨兵 ⭐剑指 Offer II 022. 链表中环的入口节点(环形链表)双指针:二次相遇 ⭐剑指 Offer I…...
Android SDK 上手指南|| 第三章 IDE:Android Studio速览
第三章 IDE:Android Studio速览 Android Studio是Google官方提供的IDE,它是基于IntelliJ IDEA开发而来,用来替代Eclipse。不过目前它还属于早期版本,目前的版本是0.4.2,每个3个月发布一个版本,最近的版本…...
Vue--》打造个性化医疗服务的医院预约系统(七)完结篇
今天开始使用 vue3 + ts 搭建一个医院预约系统的前台页面,因为文章会将项目的每一个地方代码的书写都会讲解到,所以本项目会分成好几篇文章进行讲解,我会在最后一篇文章中会将项目代码开源到我的GithHub上,大家可以自行去进行下载运行,希望本文章对有帮助的朋友们能多多关…...
点亮一颗LED灯
TOC LED0 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能APB2的外设时钟GPIO_InitTypeDef GPIO_Initstructure;GPIO_Initstructure.GPIO_Mode GPIO_Mode_Out_PP;//通用推挽输出GPIO_Initstructure.GPIO_Pin GPIO_Pin_5;GPIO_Initstructure.GPIO_Speed GPIO_S…...
SSH远程直连--------------Docker容器
文章目录 1. 下载docker镜像2. 安装ssh服务3. 本地局域网测试4. 安装cpolar5. 配置公网访问地址6. SSH公网远程连接测试7.固定连接公网地址8. SSH固定地址连接测试 在某些特殊需求下,我们想ssh直接远程连接docker 容器,下面我们介绍结合cpolar工具实现ssh远程直接连接docker容器…...
Python/Spring Cloud Alibaba开发--前端复习笔记(1)———— html5和css3.html基础
Python/Spring Cloud Alibaba开发–前端复习笔记(1)———— html5和css3.html基础 1)概述和基本结构 超文本标记语言。超文本指超链接,标记指的是标签。 基本结构: <!DOCTYPE html> 文档声明 <html lang”en”>…...
open cv学习 (十一)视频处理
视频处理 demo1 import cv2 # 打开笔记本内置摄像头 capture cv2.VideoCapture(0) # 笔记本内置摄像头被打开 while capture.isOpened():# 从摄像头中实时读取视频retval, image capture.read()# 在窗口中实时显示读取到的视频cv2.imshow("Video", image)# 等到用…...
函数栈帧理解
本文是从汇编角度来展示的函数调用,而且是在vs2013下根据调试展开的探究,其它平台在一些指令上会有点不同,指令不多,简单记忆一下即可,在我前些年的学习中,学的这几句汇编指令对我调试找错误起了不小的作用…...
【SA8295P 源码分析】70 - QAM8295P 原理图参考设计 之 DP、eDP 接口硬件原理分析
【SA8295P 源码分析】70 - QAM8295P 原理图参考设计 之 DP、eDP 接口硬件原理分析 一、DP 接口(Display Port)介绍二、高通参考硬件原理图分析2.1 高通 Display 接口框图介绍2.2 DP接口 Pin 定义介绍2.3 高通参考设计:DP2、DP3 硬件原理图2.4 高通参考设计:eDP0、eDP1 硬件…...
【CSS动画02--卡片旋转3D】
CSS动画02--卡片旋转3D 介绍代码HTMLCSS css动画02--旋转卡片3D 介绍 当鼠标移动到中间的卡片上会有随着中间的Y轴进行360的旋转,以下是几张图片的介绍,上面是鄙人自己录得一个供大家参考的小视频🤭 代码 HTML <!DOCTYPE html>…...
数据结构<树和二叉树>顺序表存储二叉树实现堆排
✨Blog:🥰不会敲代码的小张:)🥰 🉑推荐专栏:C语言🤪、Cpp😶🌫️、数据结构初阶💀 💽座右铭:“記住,每一天都是一個新的開始…...
理解docker命令
基础命令 帮助命令 docker --help(帮助命令) 用于获取某个命令的帮助信息 #命令帮助 docker 命令 --help 小技巧 换行符 \ 使用命令换符,可以让繁杂命令变得有条理 #命令换行,使用换行符 \ docker ... \... \ 镜像命令 d…...
【SA8295P 源码分析】16 - QNX侧 TouchScreen Panel (TP)线程函数 tp_recv_thread 源码分析
【SA8295P 源码分析】16 - QNX侧 TouchScreen Panel (TP)线程函数 tp_recv_thread 源码分析 一、TP 线程函数:tp_recv_thread()二、处理&上报 坐标数据 cypress_read_touch_data()系列文章汇总见:《【SA8295P 源码分析】00 - 系列文章链接汇总》 本文链接:《【SA8295P…...
第九章MyBatis的技巧
${}和#{}的区别 #{}给sql语句的占位符传值${}直接将值拼接到sql语句上,存在sql注入的现象 什么时候用${} 需要先对sql语句拼接,然后再编译。 字符串排序字段向SQL语句中拼接表名。比如根据日期生成日志表 批量删除 delete from car where in(${ids}…...
计算机技术与软件专业技术资格(水平)考试----系统架构设计师
【原文链接】计算机技术与软件专业技术资格(水平)考试----系统架构设计师 考试简介 计算机软件资格考试是由国家人力资源和社会保障部、工业和信息化部领导下的国家级考试。计算机软件资格考试既是职业资格考试,又是职称资格考试。考试合格…...
使用nrm快速切换npm源以及解决Method Not Implemented
文章目录 什么是nrm如何使用nrm查看本机目前使用的npm 源安装nrm查看可选源查看当前使用源切换源添加源删除源测试源的响应时间 如果你遇到这个报错,就可以采用这种方案解决哦解决方案:1. 切换为官方源2. 查看漏洞3. 修复漏洞4. 下面命令慎重使用&#x…...
NVIDIA Jetson 项目:机器人足球比赛
推荐:使用 NSDT场景编辑器 助你快速搭建可二次编辑器的3D应用场景 事实上,整个比赛都致力于这个想法。RoboCup小型联盟(SSL)视觉停电技术挑战赛鼓励团队“探索本地传感和处理,而不是非车载计算机和全球摄像机感知环境的…...
【论文解读】Hybrid-SORT: Weak Cues Matter for Online Multi-Object Tracking
因为Hybrid-SORT的baseline是基于OCSORT进行改进的,在这之前建议先了解byteTrack和【】的相关知识 1.介绍 1.1 基本框架 多目标跟踪(MOT)将问题分为两个子任务。第一个任务是检测每个帧中的对象。第二个任务是将它们在不同的框架中联系起来。关联任务主要通过显式…...
Microsoft 图像BERT,基于大规模图文数据的跨模态预训练
视觉语言任务是当今自然语言处理(NLP)和计算机视觉领域的热门话题。大多数现有方法都基于预训练模型,这些模型使用后期融合方法融合下游任务的多模态输入。然而,这种方法通常需要在训练期间进行特定的数据注释,并且对于…...
vue3+elementUI-plus实现select下拉框的虚拟滚动
网上查了几个方案,要不就是不兼容,要不就是不支持vue3, 最终找到一个合适的,并且已上线使用,需要修改一下样式: 代码如下: main.js里引用 import vue3-virtual-scroller/dist/vue3-virtual-scroller.css; …...
学C的第三十四天【程序环境和预处理】
相关代码gitee自取: C语言学习日记: 加油努力 (gitee.com) 接上期: 学C的第三十三天【C语言文件操作】_高高的胖子的博客-CSDN博客 1 . 程序的翻译环境和执行环境 在ANSI C(C语言标准)的任何一种实现中,存在两个不同的环境。 ࿰…...
微服务中间件--Ribbon负载均衡
Ribbon负载均衡 a.Ribbon负载均衡原理b.Ribbon负载均衡策略 (IRule)c.Ribbon的饥饿加载 a.Ribbon负载均衡原理 1.发起请求http://userservice/user/1,Ribbon拦截该请求 2.Ribbon通过EurekaServer拉取userservice 3.EurekaServer返回服务列表给Ribbon做负载均衡 …...
字符设备驱动实例(ADC驱动)
四、ADC驱动 ADC是将模拟信号转换为数字信号的转换器,在 Exynos4412 上有一个ADC,其主要的特性如下。 (1)量程为0~1.8V。 (2)精度有 10bit 和 12bit 可选。 (3)采样时钟最高为5MHz,转换速率最高为1MSPS (4)具有四路模拟输入,同一时…...
python基础5——正则、数据库操作
文章目录 一、数据库编程1.1 connect()函数1.2 命令参数1.3 常用语句 二、正则表达式2.1 匹配方式2.2 字符匹配2.3 数量匹配2.4 边界匹配2.5 分组匹配2.6 贪婪模式&非贪婪模式2.7 标志位 一、数据库编程 可以使用python脚本对数据库进行操作,比如获取数据库数据…...
SpringAOP原理:手写动态代理实现
0、基础知识 AOP我们知道,是在不修改源代码的情况下,为代码添加一些新功能的技术。通过动态代理,可以在不修改原始类代码的前提下,对方法进行拦截和增强。 动态代理常用于在不改变原有业务逻辑的情况下,对方法…...
【旅游度假】Axure酒店在线预订APP原型图 旅游度假子模块原型模板
作品概况 页面数量:共 10 页 兼容软件:Axure RP 9/10,不支持低版本 应用领域:旅游度假,生活服务 作品申明:页面内容仅用于功能演示,无实际功能 作品特色 本作品为「酒店在线预订」的移动端…...
Android JNI系列详解之CMake和ndk-build编译工具介绍
一、前提 CMake和ndk-build只是编译工具,本次主要介绍ndk-build和CMake的区别,下节课介绍他们的使用。 二、CMake工具介绍 CMake:cross platform make,是跨平台的编译工具 CMake是在AndroidStudio2.2之后引入(目前默认…...
【Linux取经路】解析环境变量,提升系统控制力
文章目录 一、进程优先级1.1 什么是优先级?1.2 为什么会有优先级?1.3 小结 二、Linux系统中的优先级2.1 查看进程优先级2.2 PRI and NI2.3 修改进程优先级2.4 进程优先级的实现原理2.5 一些名词解释 三、环境变量3.1 基本概念3.2 PATH:Linux系…...
TCP编程流程(补充)
目录 1、listen: 2、listen、tcp三次握手 3、 发送缓冲区和接收缓冲区: 4、tcp编程启用多线程 1、listen: 执行listen会创建一个监听队列 listen(sockfd,5) 2、listen、tcp三次握手 三次握手 3、 发送缓冲区和接收缓冲区:…...
每天一道leetcode:433. 最小基因变化(图论中等广度优先遍历)
今日份题目: 基因序列可以表示为一条由 8 个字符组成的字符串,其中每个字符都是 A、C、G 和 T 之一。 假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。 例如,&quo…...
连云港建设局官方网站/中国做网站的公司排名
MI(小米)正式推出了“小米无线键鼠套装”,定位不高针对主流办公用户,采用纤薄设计,支持2.4GHz无线连接,即插即用,还有省电休眠技术,能满足大多办公用户需求。该套装简约轻薄,纯黑配色࿰…...
做最精彩绳艺网站/营销推广是什么
往期热门文章:一份充电大全~开源了!Java Bean 转 Map 的巨坑,注意了!!!文章来源:https://juejin.cn/post/7123091045071454238目录一个优秀的Controller层逻辑从现状看问题改造 Controller 层逻…...
金乡网站建设/百度seo新规则
tengine2.2.2原先关配置为: client_max_body_size 10m; client_body_buffer_size 128k; tengine2.2.2 springmvc 采用http1.1可以上传10M以内的附件, 采用http2.0 可以上传100k以内的附件,上传大附件时MultipartFile file为空,没有…...
上海进博会?/廊坊自动seo
ASP.NET Core断点续传 在ASP.NET WebAPi写过完整的断点续传文章,目前我对ASP.NET Core仅止于整体上会用,对于原理还未去深入学习,由于有园友想看断点续传在ASP.NET Core中的具体实现,于是借助在家中休息时间看了下ASP.NET Core是否…...
外汇交易网站建设/长沙网络推广公司
plt.title() 是 matplotlib 库中用于设置图形标题的函数。 一、基本语法如下 plt.title(label, fontdictNone, locNone, padNone, **kwargs)其中: label 是要设置的标题文本,可以是字符串类型或者是数学表达式。fontdict 是一个可选的参数,…...
动态网站建设试题/青岛网站建设制作
寒假工作坊Python&Stata数据分析课寒假工作坊现在开始招生了,有兴趣的同学和老师可以戳进来了解课程安排 1月9-10日 Python爬虫&文本数据分析(模块Ⅰ) 1月11-16日 Stata 应用能力提升与实证前沿(模块Ⅱ) 地点浙江 杭州(浙江工…...