【LeetCode】剑指 Offer Ⅱ 第1章:整数(5道题) -- Java Version
题库链接:https://leetcode.cn/problem-list/e8X3pBZi/
题目 | 解决方案 |
---|---|
剑指 Offer II 001. 整数除法 | 快速除 ⭐ |
剑指 Offer II 002. 二进制加法 | 模拟:StringBuilder ⭐ |
剑指 Offer II 003. 前 n 个数字二进制中 1 的个数 | 动规:res[i] = res[i & (i-1)] + 1 ⭐ |
剑指 Offer II 004. 只出现一次的数字 | 位运算:按位取模 ⭐ |
剑指 Offer II 005. 单词长度的最大乘积 | 位运算 + 模拟 ⭐ |
1. 剑指 Offer II 001. 整数除法 – P1
相似题目:
[1] 程序员面试金典(第6版) 08.05. 递归乘法【不使用 * 运算符, 实现两个正整数的相乘,快速乘】
[2] 剑指 Offer 65. 不用加减乘除做加法 【位运算】
[3] AcWing 875. 快速幂 【模板题】
[4] AcWing 876. 快速幂求逆元 【若 p 为质数,则 a 的逆元,即为 ap-2】
1.1 整活版:a/b
时间复杂度 O ( 1 ) O(1) O(1),空间复杂度 O ( 1 ) O(1) O(1)
class Solution {public int divide(int a, int b) {if(a == -2147483648 &&b == -1) return 2147483647;else return a/b;}
}
1.2 快速除(⭐)
时间复杂度 O ( l o g n ) O(logn) O(logn),空间复杂度 O ( n ) O(n) O(n)
类似于快速幂的思想,将被除数拆成除数的倍次形式,例如 a=15, b=2 就可以被拆为:15 = 23 + 22 + 21 + 20 = 8 + 4 + 2 + 1 = 24 + 22 + 2*1 + 1 = 商7,余1
class Solution {public int divide(int a, int b) {// 溢出处理if (a == Integer.MIN_VALUE && b == -1) return Integer.MAX_VALUE;// 正负号处理int flag = 2;if (a > 0) {flag--;a = -a;}if (b > 0) {flag--;b = -b;}// 除法变减法int ans = process(a, b);// 返回结果return flag == 1 ? -ans : ans;}public int process(int a, int b) {int res = 0; while (a <= b) {int val = b; // 每次要减去的值(b的某次幂)int quot = 1; // 商while (val >= Integer.MIN_VALUE/2 && a <= val + val) { // 防止 2*val 会越界quot += quot;val += val;}res += quot;a -= val;}return res;}
}
其它优秀写法:
[1] 位运算(快速相减法)
PS : 补充知识1 – 【快速乘 & 快速幂】
快速幂和快速乘法都是利用了二进制的思想,是时间复杂度为O(logN)级别的算法。
该算法由两大优点:1. 便于取模操作(避免溢出);2. 时间复杂度较低
① 快速幂
快速幂经常被用来求解下列式子:
a b m o d c a^{b} \ \ mod \ \ c ab mod c
例如:a156, 而 156(10) = 1001 1100(2)
那么
我们就按照这个公式来求解 a156,原来要进行 156-1=155 次乘法运算,现在的差不多运算次数就是他 二进制的长度二进制中1的个数=84=24次.
快速幂的原理就是需要我们先不断的降低a,b的规模,然后再进行计算:
- 降低a的规模:只要让 a变成a*a
- 降低b的规模:如果b是偶数,那么每次a=a*a之后,b=b/2;
如果b是奇数,那么我们就先把这个多出来的数先挑出来,然后再用偶数的处理方法.
快速幂模板:
// C形式
long long quickpow(long long base,long long power){long long ret=1;while(power){if(power & 1) ret = ret * base;base = base * base;power>>1;}return ret;
} // Java形式
// step1:循环判断当前指数是否已经移位到0;
// step2:如果当前指数的二进制第0位是1,说明此时可以累乘;
// step3:更新底数;
// step4:右移指数;
// 循环结束后,即可返回结果.// a 是底数, b 是指数, mod 是模
long ksm(long a, long b, long c) {int res = 0;while (b > 0) // {if ((b & 1) == 1) res = res * a % c;a = a * a;b >> 1;}return res % c;
}
② 快速乘
快速乘法相比快速幂使用频率小很多,因为语言本身就有乘法这个运算符。因此最大的用途便是取模时避免溢出(还有一个好处是:加法运算要比乘法运算快)。
例如:在计算 M ∗ N % P M*N \ \% \ P M∗N % P 时,为了避免溢出,我们常常采用的方法是利用分配律将式子转换为: ( M % P ) ∗ ( N % P ) % P (M\%P)∗(N\%P)\%P (M%P)∗(N%P)%P,因为一个值模P后其范围为0到P-1,只要P*P不超过数据上限即可。但是如果P的值在 1018 这个级别,那显然这种做法还是会爆数据上限。
而用快速乘法可以避免这个烦恼,就像快速幂本质上是乘法,并利用二进制思想减少运算次数。快速乘法本质上是加法,并利用了二进制思想来减少运算次数。因为本质是在做加法运算,所以只要P+P不超过数据上限即可。
实际例子:
以 13 ∗ 27 13*27 13∗27 为例:
乘法的本质是加法,13*27可以转化为27个13相加(或13个27相加)
将27转化为二进制:
式子即可转化为:
快速乘模板:
和快速幂大同小异,只需要修改符号即可,将乘法换为加法
// C形式
long long quickmul(long long a,long long b){long long ret=0;while(b){if(b%2) ret=ret+a;a=a+a;b/=2;}return ret;
}
③ 快速乘与快速幂结合
在计算快速幂时需要用到乘法,在取模对象P很大时乘法就会爆数据上限,所以需要同时使用快速幂和快速乘法:
long long quickmul(long long a,long long b){long long ret=0;while(b){if(b%2) ret=(ret+a)%p;a=(a+a)%p;b/=2;}return ret;
}
// key:将快速幂中的乘法运算用快速幂来替代
long long quickpow(long long base,long long power){long long ret=1;while(power){if(power%2) ret=quickmul(ret,base)%p;base=quickmul(base,base)%p;power/=2;}return ret;
}
PS : 补充知识2 – 【Java运算符优先级】
赋值运算符包括: =、+=、-=、*=、/=、%=、|=、^=、~=、<<=、>>=、>>>=
更多细节可参考:Java语言中运算符号优先级
PS : 相似题目
① 程序员面试金典(第6版) 08.05. 递归乘法 【快速乘】
Ⅰ. 快速乘循环实现:时间复杂度 O ( l o g n ) O(logn) O(logn),空间复杂度 O ( 1 ) O(1) O(1) ⭐
class Solution {public int multiply(int A, int B) {// 正负号处理int sign = 2;if (A < 0) {sign--;A = -A;}if (B < 0) {sign --;B = -B;}int res = ksc(A,B);return sign == 1 ? -res : res;}// 快速乘算法public int ksc(int a, int b) {int ans = 0;while (b > 0) {if ((b & 1) == 1) ans += a; // == 的优先级比 & 高a += a;b >>= 1;}return ans;}
}
Ⅱ. 暴力递归写法:时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
class Solution {// 递归写法:相当于一共进行了 n-1 次的加A操作,仅适用于正整数public int multiply(int A, int B) {if (A == 0 || B == 0){return 0;}if (B == 1){return A;}if (A == 1){return B;}return multiply(A,B-1)+A;}
}
Ⅲ. 类快速乘递归:时间复杂度 O ( l o g n ) O(logn) O(logn),空间复杂度 O ( 1 ) O(1) O(1)
根据乘法的性质可知: A ∗ B = A ∗ ( B / 2 ) ∗ 2 = A ∗ ( B / 2 ) + A ∗ ( B / 2 ) A * B = A * (B/2) * 2 = A * (B/2) + A * (B/2) A∗B=A∗(B/2)∗2=A∗(B/2)+A∗(B/2),则
- 当B为偶数: A ∗ B = A ∗ ( B / 2 ) + A ∗ ( B / 2 ) A * B = A * (B/2) + A * (B/2) A∗B=A∗(B/2)+A∗(B/2)
- 当B为奇数: A ∗ B = A ∗ ( B / 2 ) + A ∗ ( B / 2 ) + A A * B = A * (B/2) + A * (B/2) + A A∗B=A∗(B/2)+A∗(B/2)+A
class Solution {// 类快速乘递归public int multiply(int A, int B) {if (B == 0) return 0;if (B == 1) return A;return (multiply(A, B >> 1) << 1) + ((B & 1) == 0 ? 0 : A);}
}
② 剑指 Offer 65. 不用加减乘除做加法 【位运算】
Ⅰ. 位运算:两数之和 = 无进位和 + 进位 (⭐)
// 递归写法
class Solution {// s = a + b = n + c;public int add(int a, int b) {return b == 0 ? a : add(a ^ b, (a & b) << 1);}
}// 循环写法:通过不断循环迭代进位与非进位和,直到进位为0,就得到了最终的两数加和
// 进位 carry = (a & b) << 1;
// 非进位和 non-carry = a ^ b;
// a + b = carry + non-carry;
class Solution {public int add(int a, int b) {while(b != 0) { // 直到进位为0,循环才会结束int c = (a & b) << 1; // c = 进位a ^= b; // a = 非进位和b = c; // b = 进位}return a;}
}
Ⅱ. 库函数:Integer.sum()
严格意义上讲,使用库函数属于作弊行为,Integer.sum()底层使用的还是加法运算符:
class Solution {public int add(int a, int b) {return Integer.sum(a,b);}
}
2. 剑指 Offer II 002. 二进制加法 – P5
相似题目:
[1] AcWing 791. 高精度加法【大整数类加法 A.add(B)】
[2] AcWing 792. 高精度减法【大整数类减法 A.subtract(B)】
[3] AcWing 793. 高精度乘法【大整数类乘法 A.multiply(b)】
[4] AcWing 794. 高精度除法【大整数类除法 A.divide(b) / 大整数类取余 A.mod(b)】
2.1 朴素做法:库函数(BigInteger)
时间复杂度 O ( n + m ) O(n+m) O(n+m),空间复杂度 O ( 1 ) O(1) O(1)
import java.math.*; // 需要额外引入 math 工具类
class Solution {public String addBinary(String a, String b) {BigInteger x = new BigInteger(a, 2);BigInteger y = new BigInteger(b, 2);BigInteger sum = x.add(y);String res = sum.toString(2);return res;}
}
2.2 字符串模拟(StringBuilder)⭐
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
class Solution {public String addBinary(String a, String b) {StringBuilder sb = new StringBuilder();int alen = a.length() - 1;int blen = b.length() - 1;int carry = 0; // 初始进位为0while (alen >= 0 || blen >= 0) {int ai = alen >= 0 ? a.charAt(alen--) - '0' : 0; // 从最右边出发int bi = blen >= 0 ? b.charAt(blen--) - '0' : 0;int sum = ai + bi + carry;carry = sum >= 2 ? 1 : 0; // 逢二进一,根据当前位的加和判断进位sum = sum >= 2 ? sum-2 : sum;sb.append(sum); // 把当前位的结果记录到 sb 中}if (carry == 1) sb.append(carry); // 检查最后一次操作是否产生了进位,如果有则加入return sb.reverse().toString(); // 返回反转结果}
}
PS : 补充知识1 – 【二进制字符串与整型】
在 Java 语言中,基本的整型数据类型有 byte、short、int、long 四种类型,用于需要不同存储空间的数据使用。(Java中的整型全都是有符号整型,即区分正负号)
- byte:一个 byte 类型整数在内存里占8位,数据范围:-27 ~ 27 -1
- short:一个 short 类型整数在内存里占16位,数据范围:-215 ~ 215 -1
- int:一个 int 类型整数在内存里占32位,数据范围:-231 ~ 231 -1
- long:一个long类型整数在内存里占64位,数据范围:-263 ~ 263 -1
整型与二进制字符串之间的转换方式:
💡 需要注意的是:
- 诸如 valueOf(String,int radix)、parseInt(String,int radix) 、new BigInteger(String, 2) 之类的方法是无法解释二进制的二进制补码(radix = 2)的,如果你想让它解释一个负数,那么就需要加该数的原码前加上一个‘-’号.
- 例如:
Integer.parseInt(“1011”,2); // 11
Integer.parseInt(“-1011”,2); // -11
String a = "11010010";
// int
int x = Integer.parseInt(a, 2); // 将二进制字符串 a 转为 int 型数据
String res = Integer.toBinaryString(x); // 将 int 型数据转为二进制字符串// long
long x = Long.parseLong(a, 2); // 将二进制字符串 a 转为 long 型数据
String res = Long.toBinaryString(x); // 将 long 型数据转为二进制字符串// BigInteger
BigInteger x = new BigInteger(a, 2); // 将将二进制字符串 a 转为BigInteger类对象
String res = x.toString(2); // 将BigInteger类对象 x 转为二进制字符串
// BigInteger的四则运算
BigInteger num1 = new BigInteger("12345678901234567890");
BigInteger num2 = new BigInteger("98765432109876543210");
BigInteger sum = num1.add(num2); // 加
BigInteger difference = num1.subtract(num2); // 减
BigInteger product = num1.multiply(num2); // 乘
BigInteger quotient = num1.divide(num2); // 除
BigInteger remainder = num1.mod(num2); // 取余
实际例子:
// Integer:如果字符串超过 33 位,则不能转化为 Integer
int x = Integer.parseInt(a, 2);
int y = Integer.parseInt(b, 2);
String res = Integer.toBinaryString(x + y);// Long:如果字符串超过 65 位,则不能转化为 Long
long x = Long.parseLong(a, 2);
long y = Long.parseLong(b, 2);
String res = Long.toBinaryString(x + y);// BigInteger:如果字符串超过 500000001 位,则不能转化为 BigInteger
BigInteger x = new BigInteger(a, 2);
BigInteger y = new BigInteger(b, 2);
BigInteger sum = x.add(y);
String res = sum.toString(2);
PS : 补充知识2 – 【StringBuilder与StringBuffer的区别】
补充资料:
[1] String、StringBuilder、StringBuffer的区别,优缺点
[2] String、StringBuffer和StringBuilder类的区别
Ⅰ. String 与 StringBuffer 和 StringBuilder
💡 String 与 StringBuffer 和 StringBuilder之间最大的区别在于 底层是否是 final 修饰的 char 数组.
String
:字符串常量,是不可变字符串对象(底层是固定的char数组),这就导致每次对String的操作都会生成新的String对象,这样不仅效率低下,而且大量浪费有限的内存空间。StringBuffer
和StringBuilder
都是字符串变量,其底层也都是char数组,其与String的区别就在于没有被 final 修饰,因此StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
Ⅱ. StringBuffer 与 StringBuilder的区别
💡 StringBuffer 与 StringBuilder之间最大的区别在于 StringBuffer 对几乎所有的成员方法都使用了 synchronized 关键字 来实现同步,而 StringBuilder 没有实现同步.
StringBuilder
:是为了解决大量拼接字符串时产生很多中间对象问题而提供的一个类. StringBuilder类不是线程安全的,但其在单线程中的性能比StringBuffer高.StringBuffer
:具备 StringBuilder 的所有特性,唯一加强的是它用于多线程编程时能够保证线程的安全性 【 StringBuffer进行了加锁处理(用synchronized修饰)】.
PS : 补充知识3 – 【synchronized 关键字是什】
补充资料:
[1] synchronized关键字详解 - 夏屿 - CSDN博客
[2] synchronized 关键字 - zq231 - 博客园
[3] JMM(Java内存模型)详解 - 渣娃-小晴晴 - CSDN博客 【补充资料2中描述的synchronized 关键字的三个作用正好是 JMM 的三大特性(原子性、可见性、有序性)】
[4] Java关键字synchronized - 哔哩哔哩【图片比较形象】
Ⅰ. synchronized关键字有什么用?
💡
synchronized
关键字解决的是多线程访问资源的同步性问题。synchronized可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。一旦一个线程尝试获取给定对象的Synchronized锁,则其他线程就不能访问被保护的代码。
当一个方法被 synchronized 关键字修饰时,代表这个方法加锁,相当于不管哪一个线程,运行到这个方法时,都要检查有没有其它线程正在使用这个方法(或者该类的其他同步方法),如果有的话则需要等待正在使用 synchronized 方法的线程运行完这个方法后再运行该线程,而如果没有的话,则在锁定调用者之后直接运行。
3. 剑指 Offer II 003. 前 n 个数字二进制中 1 的个数 – P6
相似题目:
[1] AcWing 801. 二进制中1的个数【使用的 return x & -x 的方法,每次返回的是 x 中从右数第1个为1的数】
举个例子:x = 5(10) = 0101(2),那么 -x = 1011(2),x & -x = 0001(2) = 1(10)
💡 关于 x & -x 可参考:如何理解x&(-x)和x&(x-1) - 一只小阿狗 - 博客园
3.1 朴素做法:库函数(Integer.bitCount())
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
class Solution {public int[] countBits(int n) {int[] res = new int[n+1];for (int i = 0; i <= n; i++) { // 顺序遍历 0 ~ nres[i] = Integer.bitCount(i); // 返回 i 的二进制形式中1的个数}return res;}
}
Integer.bitCount(i) 底层源码:
运行结果:
3.2 朴素做法:手写位运算
时间复杂度 O ( 32 ∗ n ) O(32*n) O(32∗n),空间复杂度 O ( 1 ) O(1) O(1)
// 纯朴素写法,遍历完所有的 32 bit
class Solution {public int[] countBits(int n) {int[] res = new int[n+1];res[0] = 0; // 0 就是 0,这一步可以不写,因为默认值为0for (int i = 1; i <= n; i++) { // 顺序遍历 1 ~ nint cnt = 0; // 用于记录二进制形式中 1 的个数int t = i;while (t > 0) {if ((t & 1) == 1) cnt++; // 遇 1 则加1t >>= 1; // 右移 t}res[i] = cnt;}return res;}
}// i & (i - 1) 优化:i & (i - 1) 会将i的二进制形式中最右边的1变成0
// 因为时间复杂度从 32 * n 优化成了 i 中1的个数 * n
class Solution {public int[] countBits(int n) {int[] res = new int[n+1];for (int i = 0; i <= n; i++) {int t = i;while (t != 0) {res[i]++;t = t & (t-1);}}return res;}
}
3.3 动规思想:res[i] = res[i & (i-1)] + 1 (⭐)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
根据 i & (i - 1)
每次会把当前数的二进制形式最右边的1变为0的规律,我们可以得到如下递推公式:
d p [ i ] = d p [ i & ( i − 1 ) ] + 1 dp\big[i \big] = dp\big[ i \& \big( i-1\big)\big]+1 dp[i]=dp[i&(i−1)]+1
// 1. i & (i-1)方法,递推公式:
// res[i] = res[i & (i-1)] + 1
class Solution {public int[] countBits(int n) {int[] res = new int[n+1];for (int i = 1; i <= n; i++) {res[i] = res[i & (i-1)] + 1; // dp 递推公式}return res;}
}// 2. i /2 方法,递推公式:
// 偶数:res[i] = res[(i/2)]
// 奇数:res[i] = res[(i/2)]+1
// 类似于题目1中相似题目①程序员面试金典(第6版) 08.05. 递归乘法的Ⅲ类快速乘递归解法
class Solution {public int[] countBits(int n) {int[] res = new int[n+1];for (int i = 1; i <= n; i++) {res[i] = res[i >> 1] + (i & 1); // i & 1 == i % 2,用来判断奇偶数}return res;}
}
4. 剑指 Offer II 004. 只出现一次的数字 – P8
补充资料:
[1] 只出现一次的数字(共四种)- 2021dragon的博客 - CSDN博客
相似题目:
[1] LeetCode 136. 只出现一次的数字 – (除一个数字之外,其他数字都出现了两次)【位运算:异或 res ^= num
】
[2] LeetCode 260. 只出现一次的数字 III – (除两个数字之外,其他数字都出现了两次)【位运算:分治异或 + lowbit】
[3] LeetCode 540. 排序数组中只出现一次的数字 – (有序数组,除一个数字之外,其他数字都出现了两次,要求 O ( l o g n ) O(logn) O(logn)的时间复杂度和 O ( 1 ) O(1) O(1)的空间复杂度)【奇偶二分:mid 与 mid ^ 1】
[4] 自拟 只出现一次的数字 Ⅳ – (除一个数字之外,其他数字都恰好出现了 k 次)【位运算:按位取模再相加】
[5] 剑指 Offer 03. 数组中重复的数字 – (数组中某些数字是重复的,但不知道有几个数字重复,找出任意一个重复数字)【离散化:原地交换】
4.1 位运算:按位取模(⭐)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
class Solution {// 按位取模public int singleNumber(int[] nums) {// int型数据总共 32 bitint[] bitSum = new int[32]; // 将数组中的所有数字按位相加,记录每一位的个数for (int num : nums) { for (int i = 0; i < 32; i++) {bitSum[i] += (num >> i) & 1; }}int res = 0;// 因为除某个元素外,其它元素都出现了3次,因此只需对位模3,剩下的余数移位相加后就是我们要找的数了for (int i = 31; i >= 0; i--) {res += (bitSum[i] % 3) << i;}return res;}
}
4.2 哈希表:找value为1的数
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)
class Solution {// 哈希表public int singleNumber(int[] nums) {Map<Integer, Integer> map = new HashMap<>();// getOrDefault(key, default)// 如果存在key, 则返回其对应的value, 否则返回给定的默认值 defaultfor (int num : nums) {map.put(num, map.getOrDefault(num,0)+1);}// 遍历哈希for (Map.Entry<Integer,Integer> entry : map.entrySet()) {int key = entry.getKey();int val = entry.getValue();if (val == 1) return key; }return -1;}
}
PS : 补充知识1 – 【HashMap底层原理】
更多内容可参考:
[1] HashMap(底层实现原理)、红黑树(平衡二叉树)_entry数组和node数组【通俗易懂】
[2] 深入理解JDK8 HashMap-腾讯云开发者社区-腾讯云 【源码解读】
[3] HashMap解读_hashmap bucket - CSDN博客【源码解读】
[4] HashMap初步面试题 - 51CTO博客 【快速背诵】
Ⅰ. HashMap 中的 Bucket(桶)是个什么东西?
💡 如下图所示,bucket 指的是 HashMap 底层数组中的每个元素(这个元素在 1.8 中可能是一个 LinkedList 或者是一个 TreeMap);在 1.7 中是 Entry 数组,在 1.8 中则是 Node 数组。
因此,这篇博客《还不了解HashMap底层实现原理?看着一篇就够了》中所说的:HashMap使用 key 的 hashcode 确定 bucket 位置,其实指的是确定底层数组的索引位置(也可以称为数组下标)。
Ⅱ. HashMap 中 Entry数组 与 Node数组的区别?
💡 HashMap里面底层数据结构实现是:Entry 数组、Node 数组、链表/红黑树,其中,Node 和 Entry 都是 HashMap 中的内部类,用于表示键值对,两者都含有 key、value、hash、以及 next 属性,底层上 Node<K,V> 和 Entry<K,V> 类都实现了 Map.Entry<K,V> 接口,两者在实现上的区别并不大,仅仅是名称和一些细节上的变化。
Ⅲ. HashMap 的内部数据结构 ⭐
💡 HashMap的内部存储结构其实就是数组与链表的结合(1.8后引入了红黑树,由 O ( n ) O(n) O(n) 的时间开销变为了 O ( l o g n ) O(logn) O(logn))。当对 HashMap 进行实例化操作时(即,new 一个 HashMap),系统会创建一个长度为 Capacity 的 Entry 数组,在这个数组中用来存放元素的位置就被称之为 “桶”,每个桶都有自己的索引,系统可以根据索引快速的查找桶中的元素。每个桶中都存储着一个元素,即一个 Entry 对象,但每一个 Entry 对象都带有一个引用变量,用于指向下一个元素。因此,在一个桶中,就有可能生成一个 Entry 链。所以说 Entry 是 HashMap 的基本组成单元(Entry 是 HashMap 中的一个静态内部类),每一个 Entry 都包含一个 key-value 键值对。
🎈 问题引申1:什么是静态内部类?
- 在一个类中创建另外一个类,叫做成员内部类,这个成员内部类可以静态的(利用static关键字修饰),也可以是非静态的。【总结:静态内部类其实就是被 static 关键字修饰的成员内部类】
……
🎈 问题引申2:静态内部类的使用目的?
- 静态内部类的的创建不依赖于外部类,创建内部类的实例不需要像普通内部类一样先创建外部类实例之后,才能创建自己的实例;但同时静态内部类它也不能像普通内部类一样无限制的访问外部类的方法和成员变量,只能访问静态成员变量和静态方法。
- 因此,静态内部类的使用场景是:当外部类需要使用内部类,而内部类无需外部类资源,并且内部类可以单独创建的时候会考虑采用静态内部类的设计,例如:用于程序的代码测试(主方法:public static void main(String[] args))。
- Note:在 Java 中,如果一个类要被声明为 static 的,只有一种情况,就是静态内部类。如果在外部类声明为 static,程序则会编译不通过。
……
参考:
[1] Java内部类——静态内部类 - 哔哩哔哩
[2] Java静态类 - Facilitate - 博客园
PS : 补充知识2 – 【Map集合中的遍历方式】
Map的遍历大体有3种:
- 遍历
Map.entrySet()
:它的每一个元素都是 Map.Entry 对象,这个对象中,放着的就是 Map 中的某一对key-value; - 遍历
Map.keySet()
:它是 Map 中 key 值的集合,我们可以通过遍历这个集合来读取 Map 中的元素; - 遍历
Map.values()
:它是 Map 中 value 的集合,我们可以直接通过这个集合遍历 Map 中的值,却不能读取 key。
PS : 补充知识3 – 【ConcurrentHashMap 实现原理】
补充资料:
[1] ConcurrentHashMap实现原理
[2] ConcurrentHashMap原理详解
[3] ConcurrentHashMap的实现原理
[4] 深入浅出ConcurrentHashMap详解
[5] Java中synchronized和volatile有什么区别?
Ⅰ. ConcurrentHashMap和HashMap以及Hashtable的区别
HashMap
是非线程安全的,因为在HashMap中的操作都没有加锁,因此在多线程环境下容易导致数据覆盖之类的问题(比如经典的扩容并发问题,在put操作时,易出现死循环,即:链表会形成环形 (next元素指向前一个元素) );- 而
HashTable
与ConcurrentHashMap
都属于是线程安全的,区别在于HashTable只是单纯的在成员方法上添加了synchronized关键字,这样做的话,虽然安全,但是效率低下; ConcurrentHashMap
做的就并非锁住整个方法,而是通过原子操作和局部加锁的方法保证了多线程的线程安全,且尽可能减少了性能损耗.
Ⅱ. ConcurrentHashMap的原理 ⭐
1.7
版本中的 ConcurrentHashMap 采用的是分段锁的技术实现的高效的并发操作,它是由一个Segment数组和多个HashEntry数组组成的(Segment数组的意义就是将一个大的table分成很多小table来进行加锁,每一个Segment元素存储的是HashEntry数组+链表)。它不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel(Segment 数组数量)级别的线程并发。每当一个线程持有锁访问一个 Segment 时,不会影响到其它的 Segment。1.8
版本中的 ConcurrentHashMap 是通过 synchronized 和 CAS 操作来保证并发的安全性,采用的是 Node数组 + 链表/红黑树的形式,采用红黑树之后可以保证查询效率 O ( l o g n ) O(logn) O(logn),另外它还取消了 1.7 中的 ReentrantLock,改为了 synchronized进行加锁。
🎈 问题引申1:volatile 和 synchronized 的区别?
- volatile 关键字是线程同步的轻量级实现,所以 volatile 的性能要比 synchronized 的好一些;
- 作用的位置不同:volatile 是修饰变量,而synchronized 则是修饰方法和代码块;
- 作用不同:
- synchronized,可以保证变量修改的可见性和原子性,但可能会造成线程的阻塞;(synchronized在锁释放的时候会将数据写入主存,以此保证可见性)
- 而 volatile 仅能实现变量修改的可见性,无法保证原子性,但其不会造成线程的阻塞;(volatile修饰变量后,每次读取都是去主存进行读取,以此保证可见性)
……【总结:因此,
volatile
关键字解决的是变量在多线程之间的可见性;而synchronized
关键字解决的是多线程之间访问公共资源的同步性】……
🎈 问题引申2:synchronized锁的到底是什么?(八锁问题及扩展)
- 当 synchronized 修饰普通方法时,锁的是方法的调用者,也就是对象;
- 当 synchronized 修饰静态方法时,锁的是当前类的 class 对象;
- 当 synchronized 修饰代码块时,可以设置为锁对象,或者锁引用类型的变量或常量.
……
补充资料:
- 所谓的JUC的“八锁”问题(synchronized的灵活使用) - 知乎
- 经典8锁问题–助你彻底搞懂锁的概念 - 叁有三分之一 - 博客园【八锁代码示例】
……
🎈 问题引申3:并发编程三大特性(原子性、可见性、有序性)
- 原子性:一个或多个操作,要么全部执行,要么全部不执行(执行的过程中是不会被任何因素打断的);
- 可见性:只要有一个线程对共享变量的值进行了修改,其他线程都将马上收到通知,并立即获得最新值。
- 有序性:程序执行代码指令的顺序应当保证按照程序指定的顺序执行,即便是编译优化(指令重排),也应当保证程序源语一致。
……
补充资料:
- 浅谈JMM和并发三大特性(volatile、MESI、内存屏障) - 掘金【三大特性解释】
- 并发编程深入理解JMM&并发三大特性 - 余明星 - 博客园【JMM 内存交互的八大操作】
PS : 相似题目
① LeetCode 260. 只出现一次的数字 III 【位运算】
只有两个数字出现过一次,其他数字都出现了两次
该题解法和 只出现一次的数字Ⅱ 基本一致,都是 哈希表 和 位运算 的方法.
Ⅰ. 哈希表:找value为1的数
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)
class Solution {// 哈希表:同只出现一次数字Ⅱpublic int[] singleNumber(int[] nums) {Map<Integer, Integer> map = new HashMap<>();for (int num : nums) {map.put(num, map.getOrDefault(num,0)+1);}List<Integer> list = new ArrayList<>();for (Map.Entry<Integer,Integer> entry : map.entrySet()) {int key = entry.getKey();int value = entry.getValue();if (value == 1) list.add(key);}// Java中没有直接将ArrayList转成int数组的库函数,但是我们可以通过Stream流来简化操作// 要不然就是手写循环return list.stream().mapToInt(Integer::intValue).toArray(); }
}
Ⅱ. 位运算:分治异或 + lowbit (⭐)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
class Solution {// 位运算:分治异或 + lowbitpublic int[] singleNumber(int[] nums) {int x = 0;for (int num : nums) {x ^= num;}// 使用lowbit,找出二进制位最右边1的数作为标记值int mark = x & -x;// 根据 mark 将数组分成两组,然后分别再进行异或即可int[] res = new int[2];for (int num : nums) {if ((num & mark) == 0) {res[0] ^= num;} else res[1] ^= num;}return res;}
}
② LeetCode 540. 排序数组中只出现一次的数字【2022.05.18 字节一面】
Ⅰ. 二分:全数组二分 (⭐)
时间复杂度 O ( l o g n ) O(logn) O(logn),空间复杂度 O ( 1 ) O(1) O(1)
因为是排序数组,所以如上图所示,数组中相同的元素一定相邻(如果排序数组中所有的元素都是两两对应的,那么奇数索引的元素一定与索引减1的元素相等;同理,偶数索引的元素也一定与其索引加1的元素相等).
……
因此,我们就可以通过二分查找来根据mid
的奇偶性决定和左边或右边的相邻元素比较:
- 如果 mid 是奇数,比较 mid 和 mid - 1 是否相同;
- 如果 mid 是偶数,比较 mid 和 mid + 1 是否相同;
……
值得一提的是:利用按位异或的性质,可得
- 当 m i d 是奇数时, m i d − 1 = m i d ⊕ 1 当 \ mid \ 是奇数时, mid -1 = mid ⊕ 1 当 mid 是奇数时,mid−1=mid⊕1
- 当 m i d 是偶数时, m i d + 1 = m i d ⊕ 1 当 \ mid \ 是偶数时, mid + 1 = mid ⊕ 1 当 mid 是偶数时,mid+1=mid⊕1
因此,对于 mid 的奇偶索引比较,就通过比较 nums[mid] 和 nums[mid^1] 来简化代码.
class Solution {// 1. 全数组的二分查找:排序后的奇偶规律public int singleNonDuplicate(int[] nums) {int l = 0, r = nums.length-1;while (l < r) {int mid = l + r >> 1;// 奇偶处理System.out.println(mid);if (nums[mid] != nums[mid ^ 1]) r = mid;else l = mid + 1;}return nums[l];}
}// 2. 偶数下标的二分查找
// 优化方式:由于只出现一次的元素所在下标 x 的左边有偶数个元素,
// 因此下标 x 一定是偶数,所以我们可以把二分查找范围缩小到偶数下标范围内.
class Solution {// 偶数下标的二分查找public int singleNonDuplicate(int[] nums) {int l = 0, r = nums.length-1;while (l < r) {int mid = l + r >> 1;mid -= mid & 1; // 判断 mid 的奇偶,如果是奇数,则减1,变成偶数if (nums[mid] != nums[mid+1]) r = mid;else l = mid + 2;}return nums[l];}
}
③ 自拟 只出现一次的数字 Ⅳ【位运算】
已知 array 中只有 1 个数出现一次,其他的数都出现 k 次,请返回只出现了 1 次的数。
……
同时,题目还可以这样出:已知数组中只有一个数字出现了 m 次,其他数字都出现 n 次,请找出那个唯一出现 m 次的数字,假设 m 不能被 n 整除。
- m 不能被 n 整除:意为 m % n 依旧等于 m;基于此,不管这个唯一的数字是出现了 1 次,还是 m 次,都没有关系,只要 m ≠ n 即可,解题方法都是一样的(按位取模后相加)
Ⅰ. 位运算:不开数组版按位取模再相加(⭐)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
class Solution {public int singleNumber(int[] nums) {int k = 3, res = 0; // k 代表其余元素出现了 k 次for (int i = 0; i < 32; i++) {int bit = 0;for (int num : nums) {bit += num >> i & 1;}res += (bit % k) << i;}return res;}
}
④ 剑指 Offer 03. 数组中重复的数字【离散化】
Ⅰ. 快排:顺序查找(也可以用哈希表来判重)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
class Solution {public int findRepeatNumber(int[] nums) {Arrays.sort(nums); // 库函数排序 O(nlogn)for (int i = 0; i < nums.length-1; i++) { // O(n)if (nums[i] == nums[i+1]) return nums[i];}return -1;}
}
Ⅱ. 离散化:原地交换 (⭐)
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
class Solution {public int findRepeatNumber(int[] nums) {int i = 0;while(i < nums.length) {if(nums[i] == i) { // 值与下标索引对应后,移动到下一位置i++;continue;}if(nums[nums[i]] == nums[i]) return nums[i]; // 发现重复数字// swap 交换过程:交换nums[i] 与 nums[num[i]],直到与下标索引一致int tmp = nums[i]; nums[i] = nums[tmp];nums[tmp] = tmp;}return -1;}
}
5. 剑指 Offer II 005. 单词长度的最大乘积 – P10
5.1 哈希表:数组模拟
时间复杂度 O ( n k + 26 n 2 ) O(nk + 26n^2) O(nk+26n2),空间复杂度 O ( 26 n ) O(26n) O(26n)
class Solution {// 找出数组中的两个不同的字符串// 1. 哈希表:二维数组public int maxProduct(String[] words) {int n = words.length, res = 0;boolean[][] flags = new boolean[n][26];// 初始化二维数组 O(nk)for (int i = 0; i < n; i++) {for (char c : words[i].toCharArray()) {flags[i][c-'a'] = true;}} // 开始比较数组中各字符串是否含有相同字符 O(26n^2)for (int i = 0; i < n; i++) {for (int j = i + 1; j < n; j++) {int k = 0;while (k < 26) {if (flags[i][k] && flags[j][k]) break;k++;}// 循环结束,没有相同,说明找到了两个完全不同的字符串if (k == 26) {int prod = words[i].length() * words[j].length();res = Math.max(res, prod); // 特别注意:返回乘积的最大值}}}return res;}
}
5.2 位运算:一维数组(⭐)
时间复杂度 O ( n k + 26 n 2 ) O(nk + 26n^2) O(nk+26n2),空间复杂度 O ( n ) O(n) O(n)
该题解相当于是对 5.1 题解的进一步升级,将 boolean 类型的二维数组 改成了 int 类型的 一维数组(这是因为boolean类型其实也是只有两个状态,true 和 false,这就很像二进制的形式,恰好英文小写字母的数量是26个,而一个 int 型的数字是 32 位,所以说我们用一个 int 类型的数字就可以表示一个字符串了),具体形式可见题目开头的展示图片.
class Solution {// 找出数组中的两个不同的字符串// 2. 位运算:一维数组public int maxProduct(String[] words) {int n = words.length, res = 0;int[] flags = new int[n];// 初始化一维数组,数组中每个元素存储的是对应下标的字符串按序转成的2进制形式(去重)for (int i = 0; i < n; i++) {for (char c : words[i].toCharArray()) {flags[i] |= 1 << (c - 'a'); // 我们只需要确定某个字符是否存在即可,不需要考虑重复的问题,因此用的是 |}}// 找出完全不同的两个字符串:(flags[i] & flags[j]) == 0for (int i = 0; i < n; i++) {for (int j = i+1; j < n; j++) {if ((flags[i] & flags[j]) == 0) res = Math.max(res, words[i].length() * words[j].length());}}return res;}
} // 使用哈希表进行代码优化:用「哈希表」代替 flags 数组
// 但是实际上的执行速度没有纯位运算的要快
class Solution {public int maxProduct(String[] words) {Map<Integer, Integer> map = new HashMap<>();for (String w : words) {int t = 0, m = w.length();for (int i = 0; i < m; i++) {int u = w.charAt(i) - 'a';t |= (1 << u);}if (!map.containsKey(t) || map.get(t) < m) map.put(t, m);}// 最后哈希表中剩下的就全都是不同的字符串int ans = 0;for (int a : map.keySet()) { for (int b : map.keySet()) {if ((a & b) == 0) ans = Math.max(ans, map.get(a) * map.get(b));}}return ans;}
}
6. 继续提升:加练题目
可参考:
[1] 【宫水三叶】简单位运算模拟题
[2] 模拟 · SharingSource/LogicStack-LeetCode Wiki
[3] 位运算 · SharingSource/LogicStack-LeetCode Wiki
相关文章:

【LeetCode】剑指 Offer Ⅱ 第1章:整数(5道题) -- Java Version
题库链接:https://leetcode.cn/problem-list/e8X3pBZi/ 题目解决方案剑指 Offer II 001. 整数除法快速除 ⭐剑指 Offer II 002. 二进制加法模拟:StringBuilder ⭐剑指 Offer II 003. 前 n 个数字二进制中 1 的个数动规:res[i] res[i & (…...
解析数据可视化工具:如何选择最合适的软件
在当今信息爆炸的时代,数据已成为各行各业的重要资源。为了更好地理解和分析数据,数据可视化成为一种必不可少的工具。市面上数据可视化工具不说上千也有上百,什么帆软、powerbi、把阿里datav,腾讯云图、山海鲸可视化等等等等&…...

大数据面试题之Elasticsearch:每日三题(七)
大数据面试题之Elasticsearch:每日三题 1.Elasticsearch索引文档的流程?2.Elasticsearch更新和删除文档的流程?3.Elasticsearch搜索的流程? 1.Elasticsearch索引文档的流程? 协调节点默认使用文档ID参与计算(也支持通过routing)&a…...

ubuntu20.04 安装 Qt5.15
目录 安装前工作 选择安装QT的哪个版本 安装时候选择哪些组件 安装Qt5.15 在线安装 我选择的组件 源码包安装 测试 安装前工作 ubuntu20.04.3安装Qt6.22操作步骤_ubuntu安装qt6_sonicss的博客-CSDN博客 # 安装g、gcc编译器 sudo apt-get install build-essential 安装l…...

web之标签元素转换成图片、a标签元素下载图片、获取浏览器窗口名称、重命名、元素定位、旋转、拉伸文字、文字向心对齐
文章目录 准备版本二的效果图版本一htmlJavaScript 版本二htmlJavaScript 准备 NPM下载指令 npm install dom-to-image框架加载 in ES6 import domtoimage from dom-to-image;in ES5 var domtoimage require(dom-to-image);CDN(标签)加载 案例 <script src"dist/dom…...

你应该知道的关于PCB布线的31条建议
1、走线长度应包含过孔和封装焊盘的长度。 2、布线角度优选135角出线方式,任意角度出线会导致制版出现工艺问题。 图1 PCB布线的角度 3、布线避免直角或者锐角布线,导致转角位置线宽变化,阻抗变化,造成信号反射,如图2…...
matlab中dir的各种使用方法(包括递归遍历子文件夹)
遍历文件夹1下的所有文件夹和文件(不会递归遍历): listdir(’ D:\文件夹1’);遍历文件夹1及其所有子文件夹下的所有文件夹和文件(会递归遍历): listdir(’ D:\文件夹1\** );遍历文件夹1下的所有csv文件&…...

软件测试/测试开发丨Selenium环境安装与使用
Selenium 官方网站: www.selenium.dev/ 简介: 用于web浏览器测试的工具;支持的浏览器包括IE,Firefox,Safari,Chrome,Edge等;使用简单,可使用Java,Python等…...
WPF实战学习笔记15-使用Memo类的GetAll接口
使用Memo类的GetAll接口 总体参照上节即可 创建MemoService接口 新建文件Mytodo/Service/IMemoService.cs using MyToDo.Share.Models; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace M…...

算法与数据结构-二分查找
文章目录 什么是二分查找二分查找的时间复杂度二分查找的代码实现简单实现:不重复有序数组查找目标值变体实现:查找第一个值等于给定值的元素变体实现:查找最后一个值等于给定值的元素变体实现:查找最后一个小于给定值的元素变体实…...

【软件测试】什么是selenium
1.seleniumJava环境搭建 前置条件: Java最低版本要求为8,浏览器使用chrome浏览器 1.1下载chrome浏览器 https://www.google.cn/chrome/ 1.2查看浏览器版本 点击关于Google chrome. 记住版本的前三个数. 1.3下载浏览器驱动 http://chromedriver.chromium.org/downloads 下载…...

redis线程模型
文章目录 一、redis单线程模型1.1 为什么redis命令处理是单线程,而不采用多线程1.2 单线程的局限及redis的优化方式 二、redis单线程为什么这么快2.1 采用的机制2.2 优化的措施 三、redis的IO多线程模型3.1 redis 为什么引入IO多线程模型3.2 配置io-threads-do-read…...

【idea工具】idea工具,build的时候提示:程序包 com.xxx.xx不存在的错误
idea工具,build的时候提示:程序包 com.xxx.xx不存在的错误,如下图,折腾了好一会, 做了如下操作还是不行,idea工具编译的时候,还是提示 程序包不存在。 a. idea中,重新导入项目,也还…...
线性代数——特征值和特征向量
系列文章目录 线性代数——行列式线性代数——矩阵线性代数——向量线性代数——线性方程组线性代数——特征值和特征向量线性代数——二次型 文章目录 系列文章目录版权声明补充知识求和公式的性质常用希腊字符读音 特征值和特征向量相似矩阵相似对角化实对称矩阵 版权声明 …...
运筹系列83:使用分枝定界求解tsp问题
1. 辅助函数 Node算子用来存储搜索树的状态。其中level等于path的长度,path是当前节点已经访问过的vertex清单,bound则是当前的lb。 这里的bound函数是一种启发式方法,等于当前路径的总长度,再加上往后走两步的最小值。 struct …...

linux 指令 第3期
cat cat 指令: 首先我们知道一个文件内容属性 我们对文件操作就有两个方面:对文件内容和属性的操作 扩展:echo 指令 直接打印echo后面跟的字符串 看: 这其实是把它打印到了显示器上,我们也可以改变一下它的打印位置…...

测试用例实战
测试用例实战 三角形判断 三角形测试用例设计 测试用例编写 先做正向数据,再做反向数据。 只要有一条边长为0,那就是不符合要求,不需要再进行判断,重复。 四边形 四边形测试用例...

Unity XML1——XML基本语法
一、XML 概述 全称:可拓展标记语言(EXtensible Markup Language) XML 是国际通用的,它是被设计来用于传输和存储数据的一种文本特殊格式,文件后缀一般为 .xml 我们在游戏中可以把游戏数据按照 XML 的格式标…...

了解Unity编辑器之组件篇Playables和Rendering(十)
Playables 一、Playable Director:是一种用于控制和管理剧情、动画和音频的工具。它作为一个中央控制器,可以管理播放动画剧情、视频剧情和音频剧情,以及它们之间的时间、顺序和交互。 Playable Director组件具有以下作用: 剧情控…...

python的包管理器pip安装经常失败的解决办法:修改pip镜像源
pip 常用的国内镜像源: https://pypi.tuna.tsinghua.edu.cn/simple/ // 清华 http://mirrors.aliyun.com/pypi/simple/ // 阿里云 https://pypi.mirrors.ustc.edu.cn/simple/ // 中国科技大学 http://pypi.hustunique.com/ // 华中理…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...