算法
美团技术岗笔试
https://codefun2000.com/
python
输入输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import sys
# 多组测试用例
for line in sys.stdin:
print(line)# 键盘输入,包含回车
a = line.split()# str类型列表
print(a,type(a),type(a[0]))
print(int(a[0]) + int(a[1]))
"""
['1', '5'] <class 'list'> <class 'str'>
6
['10', '20'] <class 'list'> <class 'str'>
30
"""
while True:# 多行测试用例
try:
a, b = map(int, input().split(' '))
print("{}".format(a+b))
except EOFError:
break
|
https://blog.csdn.net/m0_72538340/article/details/131012294
map:在Python中,map()是一个内置函数,它可以将一个函数应用于一个序列(例如列表或元组)的每个元素,并返回一个新的序列,其中包含每个元素应用函数后的结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
a,b=input().split(' ')
print(int(a)+int(b))
a, b = map(int, input().split(' '))
print("{}".format(a+b))
n = int(input())
a = list(map(int, input().split()))
x, y = map(int, input().split())
print(n,a,x,y)
"""
4
1 2 3 4
1 2
4 [1, 2, 3, 4] 1 2
"""
|
https://blog.csdn.net/guan022/article/details/115293946
输入一行:input(),str类型
输入的一行有多个信息:input().split(),一个接收是list元素是str,多个接收是每个str
有多组测试用例(多组同时输入):
while true:
try:
xxx
except:
break
for line in sys.stdin: #多行用例
print(line)# 键盘输入,包含回车
a = line.split()# 列表,str
list a[0]第一位,a[-1]最后一个
lower():str中大写变小写、upper()变大写
https://www.bilibili.com/video/BV1144y1c7ef?p=2&vd_source=ad42090d7d6fcdfc144126ae0e2884ac
前缀和的思想?
**strip()**是Python中字符串对象的一个内置函数。它用于去除字符串开头和结尾的指定字符(默认情况下是空格字符)。
Python的内置函数**abs()**。该函数返回一个数的绝对值。
二维前缀和,二维前缀和的作用就是快速计算区块和,行列的下标都从1开始
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
def Abs(a, b):
return a - b if a > b else b - a
# 直接用 abs(1-3)
n, m = map(int, input().split())
a = [[0] * (m + 1) for _ in range(n + 1)]
# 这个列表将用于存储矩阵的数据。(n+1) × (m+1)
# 原矩阵相当于从1,1开始,00,01,10位置补0,便于11应用公式
pre = [[0] * (m + 1) for _ in range(n + 1)]
# 前缀,这个列表将用于存储矩阵的前缀和。
for i in range(1, n + 1):
row = list(map(int, input().split()))
for j in range(1, m + 1):
a[i][j] = row[j - 1]
pre[i][j] = a[i][j] + pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1]
# 二维前缀和公式
ans = 1e9
for i in range(1, n):
ans = min(ans, Abs(pre[i][m], pre[n][m] - pre[i][m]))
for j in range(1, m):
ans = min(ans, Abs(pre[n][j], pre[n][m] - pre[n][j]))
print(int(ans))
"""
2 2
2 2
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
1 2
3 4
[[0, 0, 0], [0, 1, 2], [0, 3, 4]] [[0, 0, 0], [0, 1, 3], [0, 4, 10]]
2
"""
|
矩阵联通块:上下左右四个方向相邻阳的相同字符是连通的。
请在所有可能的矩阵种找到一个权值最小的矩阵,并输出权值。
枚举+bfs广度优先搜索
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
import pdb
from collections import deque
len = int(input())
s = input() # 表示字符矩阵。
dx = [-1, 0, 1, 0]# 分别表示在四个方向上移动的水平和垂直距离。
dy = [0, 1, 0, -1]
def get(n, m):
# 计算给定二维字符矩阵中的连通区域数量
# `n` 和 `m`,分别表示字符矩阵的行数和列数。
q = deque()# 双端队列
# pdb.set_trace()
res = 0 #记录连通块的和
vis = [0] * len # 用于记录每个位置是否被访问过。初始时,所有位置的访问状态都设置为 0。
for i in range(n):# 1hang 4lie
for j in range(m):
if vis[i * m + j]:# =1 被访问过,跳过
continue
ch = s[i * m + j] # 将该位置的字符存储在变量 `ch` 中,并将该位置的坐标 `(i, j)` 添加到队列 `q` 中。ch='a'
q.append((i, j))#入队列 deque([(0, 0)])
while q: #循环用于进行广度优先搜索。
top = q.popleft() #从队列的左侧移出一个位置 `(top[0], top[1])`#(0,0)出队列
for k in range(4):
nx = top[0] + dx[k]
ny = top[1] + dy[k]# 计算相邻位置 `(nx, ny)` 的坐标 左,上,右,下
if nx >= 0 and nx < n and ny >= 0 and ny < m and not vis[nx * m + ny] and s[nx * m + ny] == ch:
# 如果相邻位置在合法的范围内、未被访问过且字符与当前位置相同
vis[nx * m + ny] = 1 #将相邻位置标记为已访问
q.append((nx, ny)) # 并将其坐标添加到队列 `q` 中。
res += 1
return res
mid = len // 2
ans = float('inf')
for i in range(1, mid + 1):#枚举可能的矩阵
if len % i == 0:
ans = min(ans, get(i, len // i))
print(ans)
print(ans)
"""
4
abaa
3
2
2
"""
|
deque 是一个双端队列(double-ended queue)的实现,具有在两端高效地进行插入和删除操作的特性。通过使用 deque,可以方便地实现需要在两端频繁插入和删除元素的场景,例如队列、栈等数据结构的实现。
append(x):将元素 x 添加到双端队列的右侧。
appendleft(x):将元素 x 添加到双端队列的左侧。
pop():从双端队列的右侧移除并返回一个元素。
popleft():从双端队列的左侧移除并返回一个元素。
extend(iterable):在双端队列的右侧追加可迭代对象 iterable 中的所有元素。
extendleft(iterable):在双端队列的左侧追加可迭代对象 iterable 中的所有元素。
clear():清空双端队列中的所有元素。
len(d):返回双端队列 d 的长度。
树上染色 树形DP动态规划
dp*[*i][0] 表示以 i 为子树,不选择 i 这个节点进行染色,i 这棵子树可以染色的结点最大数量
dp*[*i][1] 表示以 i 为子树,对 i 这个节点进行染色,i 这棵子树可以染色的结点最大数量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
import pdb
import math
def dfs(u, fa):# 深度优先搜索,`u` 和 `fa`,分别表示当前节点和其父节点。
global dp, g, a # 全局变量 `dp`、`g` 和 `a`
# `dp` 是一个二维列表,用于存储节点的最大路径和
# `g` 是一个邻接表,用于存储树的连接关系
# `a` 是一个列表,用于存储每个节点的权值
pdb.set_trace()
for v in g[u]: # 遍历节点 `u` 的邻接节点 `v`
if v == fa: # 如果 `v` 是 `u` 的父节点,则跳过
continue
dfs(v, u) # 递归调用 `dfs(v, u)` 进行深度优先搜索
dp[u][0] += max(dp[v][0], dp[v][1]) # 当前节点 `u` 的路径和 `dp[u][0]` 进行累加
for v in g[u]:
if v == fa:
continue
val = a[v] * a[u]
sq = int(math.sqrt(val + 0.5))
if sq * sq != val:
continue
dp[u][1] = max(dp[u][1], (dp[u][0] - max(dp[v][0], dp[v][1])) + dp[v][0] + 2)# 更新节点 `u` 的路径和 `dp[u][1]`
n = int(input())# 表示树的节点数
a = list(map(int, input().split()))# 每个节点的权值
g = [[] for _ in range(n)]
for _ in range(n - 1):# 每行输入两个整数 `u` 和 `v`,表示节点 `u` 和节点 `v` 之间有一条边。
u, v = map(int, input().split())
u -= 1
v -= 1
g[u].append(v)
g[v].append(u)
dp = [[0, 0] for _ in range(n)]# 二维列表 `dp`,用于存储每个节点的最大路径和
dfs(0, -1)# 从根节点开始进行深度优先搜索
print(max(dp[0][0], dp[0][1]))
|
美团笔试
笔试:题型和上述题型类似,但只做出来了1和26.6-2 和16.6-3
不知道后续会不会有面试,也不清楚我通过笔试是因为内推还是真的就是都有
如果没面试机会,希望春招的时候可以再次参与笔试。
关于笔试,有朋友帮助,但是我还是自己做,按自己的思路进行,再辅以答案
一向如此,可能有答案在我面前,我也会按自己的思路取走,然后再去修正或者是执拗的去继续按照自己的思路,我觉得这是 我的妥协中的小倔强吧,我认为是。
接下来就是同步进行考公和刷题和面经
加油
在人才市场成为人才。
中国铁塔笔试
\1. 写一个程序,输入一个数字n,然后输出1到n的所有整数。 2. 写一个程序,输入一个数字n,然后计算并输出1到n的所有整数的和。 3. 编写一个函数,接受一个整数列表作为参数,并返回其中最大的数。 4. 写一个程序,输入一个英文字符串,然后将该字符串中的每个单词反转并输出。 5. 编写一个函数,接受一个长度为n的整数数组,然后返回该数组中的所有元素之和。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import java.util.Scanner;
public class PrintNumbers {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Enter a number: ");
//int n = input.nextInt();
double n = input.nextDouble();
System.out.println("Numbers from 1 to " + n + ":");
for (int i = 1; i <= n; i++) {
System.out.print(i + " ");
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import java.util.Scanner;
public class SumOfNumbers {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Enter a number: ");
double n = input.nextDouble();
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
System.out.println("Sum of numbers from 1 to " + n + ": " + sum);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
//编写一个函数,接受一个整数列表作为参数,并返回其中最大的数
import java.util.Scanner;
public class MaxNumberFinder {
public static int findMax(int[] numbers) {
//定义了一个名为 `findMax` 的公共静态方法,该方法接受一个整数数组 `numbers` 作为参数,并返回一个整数类型的值。
if (numbers.length == 0) {
throw new IllegalArgumentException("The array cannot be empty");
}
int max = numbers[0];
for (int i = 1; i < numbers.length; i++) {
if (numbers[i] > max) {
max = numbers[i];
}
}
return max;
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Enter the number of elements: ");
int n = input.nextInt();
int[] numbers = new int[n]; //定义长度为n的数组
System.out.println("Enter the elements:");
for (int i = 0; i < n; i++) {
numbers[i] = input.nextInt(); //为数组赋值
}
int maxNumber = findMax(numbers);
System.out.println("The maximum number is: " + maxNumber);
}
}
//打印数组 Arrays.toString(arr)
import java.util.Arrays;
public class ArrayPrinter {
public static void printArray(int[] arr) {
System.out.println(Arrays.toString(arr));
}
public static void main(String[] args) {
int[] array = {1, 2, 3, 4, 5};
printArray(array); // Output: [1, 2, 3, 4, 5]
}
}
//如果不输入列表的长度,你可以要求用户输入列表的元素,直到用户输入一个特定的标记来表示输入的结束。例如,用户可以输入一个负数或者输入 “done” 来表示输入的结束。以下是一个简单的示例程序:
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
ArrayList<Integer> numbers = new ArrayList<>();
System.out.println("Enter the elements (enter a non-numeric value to finish):");
while (input.hasNextInt()) {
numbers.add(input.nextInt());
}
int maxNumber = findMaxInList(numbers);
System.out.println("The maximum number is: " + maxNumber);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
//输入一个英文字符串,然后将该字符串中的每个单词反转并输出。
import java.util.Scanner;
public class WordReversal {
public static String reverseWords(String sentence) {
String[] words = sentence.split("\\s+");
StringBuilder reversedSentence = new StringBuilder();
for (String word : words) {
StringBuilder reversedWord = new StringBuilder(word);
reversedWord.reverse();
reversedSentence.append(reversedWord).append(" ");
}
return reversedSentence.toString().trim();
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Enter a sentence: ");
String sentence = input.nextLine();
String reversedSentence = reverseWords(sentence);
System.out.println("Reversed sentence: " + reversedSentence);
}
}
//Enter a sentence: hello world
//Reversed sentence: olleh dlrow
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
//接受一个长度为n的整数数组,然后返回该数组中的所有元素之和
import java.util.Arrays;
public class ArraySumCalculator {
public static int calculateArraySum(int[] arr) {
int sum = 0;
for (int num : arr) {
sum += num;
}
return sum;
}
public static void main(String[] args) {
int[] array = {1, 2, 3, 4, 5};
int sum = calculateArraySum(array);
System.out.println("Sum of array elements: " + sum);
}
}
import java.util.Scanner;
public class ArrayProcessor {
public static int calculateArraySum(int[] arr) {
int sum = 0;
for (int num : arr) {
sum += num;
}
return sum;
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Enter the length of the array: ");
int length = input.nextInt();
int[] array = new int[length];
System.out.println("Enter the elements of the array:");
for (int i = 0; i < length; i++) {
array[i] = input.nextInt();
}
int sum = calculateArraySum(array);
System.out.println("Sum of array elements: " + sum);
}
}
|
for (int num : arr) 是 Java 中的增强型 for 循环,也被称为 for-each 循环。它用于遍历数组或集合中的元素,可以在不知道数组或集合长度的情况下方便地对其中的元素进行访问。
具体来说,for (int num : arr) 中的 int num 表示对数组 arr 中的每个元素进行迭代访问,并将当前迭代的元素赋值给 num。这样,在循环体内就可以直接使用 num 来访问数组中的元素,而不需要使用索引来访问。
"\\s+" 表示一个或多个空白字符(空格、制表符、换行符等)。因此,split("\\s+") 的作用是将句子分割成多个单词,并将这些单词存储在 words 数组中。
1.在一个1到100之间的整数序列中,除了一个数字出现了两次外,其他数字都只出现了一次。请写一个算法,找出那个重复出现的数字。 2. 给定一个数组arr,其中包含1到100之间的无重复整数,现在将其中一个数字删除,然后将剩下的数字重新排序,得到新的数组arr'。请找出被删除的数字。 3. 给定一个有序数组arr,其中包含1到100之间的无重复整数,现在要求将其中其中一数字替换成另一个数字,并且保持数组有序。请设计一个算法来实现这个要求。 4. 给定一个整数数组arr,其中包含重复数字,要求使用最少的额外空间来找出这些重复数字。 5.在一个1到100之间的整数序列中,有两个数字出现了两次,其他数字都只出现了一次。请写一个算法,找出这两个重复出现的数字。 6. 给定一个整数数组arr,其中包含重复数字,要求找出所有重复数字并且统计它们的出现次数。 7.给定一个字符串s,其中包含大小写字母、数字和特殊字符。请编写一个函数,对s进行加密,将其转换成一个新的字符串。加密规则为将每个字符的ASCII码加上一个固定值,然后再将结果转换成对应的ASCII字符。 8.给定一个字符串s,其中包含大小写字母、数字和特殊字符,请编写一个函数,将s中的英文单词逆序排列。 9. 给定一个整数数组arr和一个目标值target,要求从arr中找到两个数字,使得它们的和等于target。请设计一个算法来实现这个要求,并返回这两个数字的下标。 10. 给定一个整数数组arr,其中包含若干个数字,要求找出其中出现次数最多的数字。如果有多个数字出现次数相同,则返回其中任意一个数字。
\1. 什么是栈?栈的特点是什么? 2. 什么是队列?队列的特点是什么? 3. 什么是链表?链表和数组有什么不同? 4. 什么是树?树的几个重要概念是什么? 5. 什么是哈希表?哈希表的特点是什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
1、栈(Stack)是一种常见的数据结构,它遵循后进先出(Last-In-First-Out,LIFO)的原则。栈可以被看作是一种容器,其中的元素按照一定的顺序进行存储和访问。
栈具有以下几个主要特点:
后进先出(LIFO):栈的最后一个添加的元素将成为第一个被移除的元素。也就是说,最后压入(添加)到栈中的元素最先弹出(移除)。
只能在栈顶进行插入和删除操作:栈只允许在栈顶进行元素的插入(称为"压栈"或"入栈")和删除(称为"弹栈"或"出栈")操作。插入和删除元素都是在栈顶进行的。
限制性:栈具有一种受限制的访问方式,只能在栈顶进行插入和删除操作,而不能在中间或底部插入或删除元素。这种限制使得栈具有高效的插入和删除操作。
容量有限:栈的容量是有限的,即栈中可以存储的元素数量有限。当栈的容量已满时,继续进行插入操作将导致栈上溢(Stack Overflow)。
栈在计算机科学中有着广泛的应用,例如函数调用栈、表达式求值、括号匹配、逆波兰表达式等。栈的特点使其非常适合解决需要遵循后进先出原则的问题。
2、队列(Queue)是一种常见的数据结构,它遵循先进先出(First-In-First-Out,FIFO)的原则。队列可以被看作是一种容器,其中的元素按照一定的顺序进行存储和访问。
队列具有以下几个主要特点:
先进先出(FIFO):队列中最早添加的元素将首先被移除。也就是说,最先进入队列的元素最先被取出。
只能在队尾插入,在队头删除:队列只允许在队尾进行元素的插入(称为"入队")和在队头进行元素的删除(称为"出队")操作。新元素只能添加到队列的末尾,而只能从队列的头部移除元素。
限制性:队列具有一种受限制的访问方式,只能在队头删除元素和在队尾插入元素,不能在中间或其他位置进行操作。这种限制使得队列具有有序的数据访问方式。
容量有限:队列的容量是有限的,即队列中可以存储的元素数量有限。当队列已满时,继续进行插入操作将导致队列上溢(Queue Overflow);当队列为空时,继续进行删除操作将导致队列下溢(Queue Underflow)。
队列在计算机科学中有着广泛的应用,例如任务调度、缓冲区管理、消息传递等。队列的特点使其非常适合解决需要按照先进先出原则进行操作的问题。
3、链表(Linked List)是一种常见的数据结构,它由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的引用(指针)。
链表和数组是两种不同的数据结构,它们在以下几个方面有所不同:
存储方式:数组使用连续的内存空间来存储元素,而链表使用离散的内存块来存储元素,每个节点可以在内存的任意位置。
大小固定性:数组的大小是固定的,一旦创建后,大小就不能改变。而链表的大小可以根据需要动态地进行扩展或缩小。
插入和删除操作的效率:对于数组,插入和删除操作需要移动元素,因为数组中的元素是连续存储的。而对于链表,插入和删除操作只需要修改节点之间的指针,效率较高。
随机访问的效率:数组可以通过索引直接访问元素,时间复杂度为O(1)。而链表需要从头节点开始逐个遍历,时间复杂度为O(n)。因此,数组在随机访问元素时效率更高。
空间占用:由于链表每个节点需要存储指针信息,相对于数组来说,链表在存储同样数量的元素时可能会占用更多的内存空间。
总的来说,数组适合在需要随机访问元素和已知大小的情况下使用,而链表适合在需要频繁进行插入和删除操作、大小不确定或动态变化的情况下使用。选择使用哪种数据结构取决于具体的应用需求和操作的特点。
4、树(Tree)是一种抽象数据类型,由n(n≥1)个节点的有限集合构成。树具有以下几个重要概念:
节点(Node):树中的每个元素称为节点。节点可以包含一个数据元素,也可以不包含。节点之间通过边相连,用于表示节点之间的关系。
根节点(Root):树中有且仅有一个根节点,它位于树的顶部,没有父节点。根节点是树中唯一的起始节点。
父节点和子节点:除了根节点外,每个节点都有且仅有一个父节点,它们之间通过一条边相连。节点的子节点是该节点的直接后继节点,而该节点是子节点的直接前驱节点。
叶节点(Leaf):叶节点是没有子节点的节点,它们位于树的末端。
路径(Path):从树的一个节点到另一个节点的序列被称为路径。路径的长度是路径上的边的数量。
层次(Level):树的根节点位于第一层,它的子节点位于第二层,以此类推。树中每个节点的层次是从根节点到该节点的路径的长度。
深度(Depth):树中节点的最大层次称为树的深度。根节点的深度为0。
子树(Subtree):树中的任意节点及其子孙节点组成的集合称为子树。
森林(Forest):由m(m≥0)棵互不相交的树组成的集合称为森林。
树是一种重要的数据结构,在计算机科学和信息技术领域有着广泛的应用,例如在数据库系统、操作系统、图形学、人工智能等领域都有树的身影。
树的节点可以不包含元素。
在某些应用场景中,树的节点可能只用于表示节点之间的关系,而不存储实际的数据元素。这种情况下,节点的作用主要是连接不同的部分或提供结构上的组织。
举例来说,考虑一个文件系统的目录结构,每个目录可以看作是树中的一个节点。目录节点可以有子目录和文件作为子节点,而目录本身不需要存储实际的数据元素,只需要存储子节点的引用和一些属性(例如目录名称、权限等)。在这种情况下,节点不包含元素是合理的,因为节点本身并不代表具体的数据。
另外,有时候在算法设计中,我们可能需要使用一种特殊的树结构,例如二叉搜索树(Binary Search Tree),其中节点的关键字(Key)用于比较和排序,而实际的数据元素则存储在节点之外。这种设计可以使得树的结构更加紧凑,减少存储的冗余。
5、哈希表(Hash Table),也被称为散列表,是一种常见的数据结构,用于存储键-值对(Key-Value)的集合。
哈希表的特点如下:
1. 快速的插入和查找:哈希表通过将键映射到一个固定的位置来实现快速的插入和查找操作。具体来说,通过一个哈希函数将键映射为哈希值,然后将该哈希值转换为数组索引,从而可以在常数时间内访问对应位置的元素。
2. 高效的查找操作:在哈希表中,通过哈希函数的映射,键被分散到不同的桶(bucket)中,每个桶存储了一组具有相同哈希值的键-值对。因此,当进行查找操作时,只需计算哈希值并在对应的桶中查找,而不需要逐个比较每个元素,使得查找操作具有常数时间复杂度。
3. 灵活的存储空间:哈希表的大小可以根据需要进行动态调整。通过合理设置负载因子(load factor),可以在时间和空间的平衡之间进行权衡。当负载因子过高时,哈希冲突的可能性增加,可能会导致性能下降;而当负载因子过低时,可能会浪费大量的内存空间。
4. 解决哈希冲突:由于不同的键可能映射到相同的哈希值,称为哈希冲突。哈希表使用冲突解决方法来处理这种情况。常见的冲突解决方法包括链地址法(Chaining),即在同一个桶中使用链表或其他数据结构来存储冲突的键-值对;和开放地址法(Open Addressing),即在发生冲突时,尝试寻找下一个可用的空桶。
总的来说,哈希表通过利用哈希函数和数组的结构,实现了高效的插入、查找和删除操作。它在各种应用场景中广泛使用,例如数据库系统、缓存实现、编译器和哈希算法等。
|
\1. 什么是虚拟内存?它的作用是什么? 2. 什么是死锁?死锁的原因是什么? 3. 什么是进程?进程和线程有什么区别? 4. 什么是文件系统?文件系统有哪些重要概念? 5. 什么是虚拟机?虚拟机的作用是什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
1、虚拟内存(Virtual Memory)是一种计算机操作系统提供的抽象概念,它使得应用程序能够访问超出物理内存(RAM)容量的存储空间。
虚拟内存的主要作用包括:
扩大可用的内存空间:虚拟内存允许应用程序使用比实际物理内存更大的地址空间。它通过将不常用的数据从内存中换出到硬盘的存储空间中,释放出物理内存空间供其他程序使用。这样,即使系统的物理内存有限,应用程序也可以运行更大规模的任务。
提供内存的保护和隔离:虚拟内存为每个应用程序提供了独立的地址空间,使得每个应用程序彼此隔离,互不干扰。这样可以提高系统的安全性和稳定性,防止一个应用程序对其他应用程序或操作系统造成损害。
简化内存管理:虚拟内存通过将物理内存和磁盘上的存储空间统一管理,为程序员和操作系统提供了一致的内存模型。程序员不需要关注实际的物理内存布局和使用情况,而是将其视为连续的虚拟地址空间。操作系统负责将虚拟地址映射到物理地址,并负责内存的分配和回收等管理操作。
实现了分页机制:虚拟内存将物理内存和磁盘空间划分为固定大小的页面(page)。每个应用程序的虚拟地址空间也被划分为相同大小的页面。当应用程序访问某个虚拟地址时,操作系统根据页面表(page table)将虚拟地址映射到物理地址。这种分页机制可以有效地管理内存,并提供了更好的内存利用率。
总的来说,虚拟内存是操作系统提供的一种机制,它允许应用程序使用超出物理内存容量的存储空间。通过虚拟内存,系统可以扩大可用的内存空间、提供内存的保护和隔离、简化内存管理,并实现了分页机制,从而提高了系统的性能和稳定性。
2、死锁(Deadlock)是指在多个进程或线程之间发生的一种特殊情况,其中每个进程或线程都在等待一个被其他进程或线程持有的资源,而同时又持有其他进程或线程需要的资源,导致它们都无法继续执行,陷入无限等待的状态。
死锁通常发生在多个进程或线程同时满足以下四个条件时:
互斥条件(Mutual Exclusion):至少有一个资源只能被一个进程或线程占用,即在一段时间内只允许一个进程或线程访问。
请求与保持条件(Hold and Wait):一个进程或线程可以在持有某个资源的同时请求其他资源,而不释放已经占有的资源。
不可剥夺条件(No Preemption):已经被分配给一个进程或线程的资源不能被强制性地剥夺,只能由占有它的进程或线程显式地释放
循环等待条件(Circular Wait):存在一个进程或线程的资源请求序列,使得每个进程或线程都在等待下一个进程或线程所占有的资源。
当这四个条件同时满足时,就可能发生死锁。死锁的发生是一个潜在的问题,可能导致系统无法继续执行,需要通过适当的死锁检测和解决算法来解决。
要解决死锁问题,可以采取以下几种策略:
预防死锁:通过破坏四个死锁条件中的一个或多个条件来预防死锁的发生,如避免循环等待、强制资源分配顺序等。
避免死锁:在资源分配时,通过安全序列算法等方法来判断是否会发生死锁,只有当系统处于安全状态时才进行资源分配。
检测和恢复死锁:周期性地检测系统中是否存在死锁,并采取相应的措施来解除死锁,如终止某个进程或线程、回滚操作等。
忽略死锁:某些情况下,由于死锁发生的概率极低,可以选择忽略死锁,而不采取主动的死锁处理策略。
总之,死锁是指多个进程或线程因相互等待对方持有的资源而无法继续执行的状态。死锁的发生原因是四个条件同时满足,包括互斥条件、请求与保持条件、不可剥夺条件和循环等待条件。为了解决死锁问题,可以采取预防、避免、检测和恢复等策略。
3、在计算机科学中,进程(Process)是指正在执行的一个程序实例。它是操作系统中进行资源分配和调度的基本单位,可以看作是一个独立的执行环境,包含了程序代码、数据和执行状态等信息。
每个进程都拥有自己的内存空间、文件描述符、打开的文件等资源。进程之间是相互独立的,它们通过进程间通信(Inter-Process Communication,IPC)来实现数据共享和通信。
进程的特点包括:
独立性:每个进程都是相互独立的执行实体,它们有自己的内存空间和资源。
并发性:操作系统可以同时执行多个进程,通过时间片轮转等调度算法来实现进程之间的并发执行。
隔离性:进程之间相互隔离,一个进程的错误或异常不会直接影响其他进程的执行。
资源管理:操作系统负责为进程分配和管理资源,如内存、文件和设备等。
而线程(Thread)是进程中的执行单元,一个进程可以包含多个线程。线程共享同一个进程的资源,包括内存空间、文件和设备等,因此多个线程之间可以直接访问和共享数据。
线程的特点包括:
轻量性:相比进程,线程的创建和切换开销较小,可以更高效地实现并发执行。
共享性:线程可以共享进程的资源,包括内存、文件和设备等。
通信性:线程之间可以通过共享内存和同步机制进行通信和同步。
并发性:多个线程可以同时执行,从而提高程序的执行效率。
总结起来,进程是操作系统中执行的一个程序实例,拥有独立的执行环境和资源,进程之间相互独立;而线程是进程中的执行单元,共享进程的资源,多个线程之间可以并发执行,通过共享内存和同步机制进行通信和同步。进程是资源分配的基本单位,而线程是操作系统调度的基本单位。
4、文件系统(File System)是操作系统用于组织和管理计算机存储设备上文件和目录的一种机制。它提供了一种逻辑结构,使用户和应用程序可以方便地访问和管理文件。
文件系统包含以下重要概念:
文件(File):文件是存储在存储设备上的命名数据单元,可以是文本文件、图像文件、程序文件等。文件通常具有名称、大小、创建日期和权限等属性。
目录(Directory):目录是一种特殊的文件,用于组织和管理其他文件和子目录。目录可以形成层次结构,允许用户以逻辑方式组织文件。
路径(Path):路径是用于定位文件或目录在文件系统中位置的字符串。路径可以是绝对路径(从根目录开始的完整路径)或相对路径(相对于当前工作目录的路径)。
文件描述符(File Descriptor):文件描述符是操作系统用于标识和访问打开的文件的抽象概念。它是一个整数值,用于在文件操作中唯一标识打开的文件。
文件权限(File Permissions):文件系统通过权限机制控制对文件的访问权限。权限包括读取(Read)、写入(Write)和执行(Execute)等。
文件系统层次结构(File System Hierarchy):文件系统通常采用层次结构组织文件和目录。常见的层次结构包括树状结构,其中根目录是顶层目录,下面可以有多个子目录。
文件系统缓存(File System Cache):为了提高文件访问效率,操作系统通常使用文件系统缓存来缓存最近访问的文件数据。这样可以减少对物理存储设备的访问次数,提高系统性能。
这些概念共同构成了文件系统的基本组成部分,使得用户和应用程序可以方便地进行文件的读取、写入、创建、删除和管理等操作。不同的操作系统和文件系统可能有不同的实现和特性,但上述概念是文件系统中的核心要素。
5、虚拟机(Virtual Machine)是一种软件实体,它在物理计算机上创建了一个独立的、隔离的虚拟环境,可以在这个虚拟环境中运行操作系统和应用程序。虚拟机技术通过在物理硬件上模拟出多个逻辑计算环境,从而使得一台物理计算机可以同时运行多个独立的操作系统和应用程序。
虚拟机的作用包括:
资源隔离:虚拟机可以将物理计算机的资源(如 CPU、内存、存储等)划分为多个独立的虚拟环境,从而实现不同虚拟机之间的资源隔离和保护。
多环境支持:虚拟机允许在同一台物理计算机上运行不同操作系统的多个实例,这样可以在一台机器上同时运行多个不同类型的应用程序。
硬件共享:多个虚拟机可以共享物理计算机的硬件资源,如网络接口、磁盘存储等,从而更有效地利用物理计算机的资源。
开发和测试:虚拟机可以用于应用程序的开发和测试,开发人员可以在虚拟机中模拟不同的操作系统和环境,进行软件开发和测试。
容错和恢复:虚拟机的快照和复制功能使得在虚拟化环境中实现系统的快速备份、恢复和迁移变得更加容易。
资源优化:通过虚拟机管理软件(如VMware、VirtualBox等),可以对虚拟机的资源分配和管理进行优化,以满足不同应用场景下的需求。
总之,虚拟机技术通过软件层面的虚拟化,实现了对物理计算机资源的高效利用和多环境支持,从而提高了计算资源的利用率、灵活性和安全性。
|
1
2
3
4
5
6
7
8
9
10
11
|
class test {
public static void main(String[] args) {
int a =10;
Integer a1 =10;
int b =10;
Integer b1= 10;
System.out.println(""+(a==b)+(a==a1)+(a1==b1));
}
}
//truetruetrue
|
笔试真题
美团
1、小美拿到了一个大小为n的数组,她希望删除一个元素,使得剩余元素之和不小于x。
小美想知道,有多少种不同的删除方案?
输入描述
第一行输入两个正整数n和x。第二行输入n个正整数ai,代表数组的元素。
1≤n≤105
1≤a≤104
1≤x≤10
输出描述
一个整数,代表删除的方案数。
3 7
3 4 3
2
说明
删除第一个或者第三个元素都可以。
1
2
3
4
5
6
7
8
9
10
|
n,x = map(int,input().split())
a = list(map(int,input().split()))
flag = 0
for i in range(len(a)):
if sum(a)-a[i]>=x:
flag=flag+1
print(flag)
|
–
2、小美拿到了一个数组,她可以进行最多一次操作:选择两个元素 ai和aj,使得ai加1,aj减1
小美希望最终数组所有元素的乘积尽可能大。你能帮帮她吗?
输入描述
第一行输入一个正整数n,代表数组的大小。
第二行输入n个正整数ai,代表数组的元素。
输出描述
输出描述
最终的最大乘积 由于答案过大,请对10^9+7取模
3
3 1 6
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
n = int(input())
a = map(int,input().split())
a = sorted(a)
mod = int(1e9)+7
def chengji(list):
product =1
for num in list:
product*=num
return product%mod
product =chengji(a)
a[0] = a[0]+1
a[n-1] = a[n-1]-1
product=max(product,chengji(a))
print(product)
|
–
3、小美拿到了一个字符串s,她准备按以下的规则生成一个字符串t
1.首先令t等于s
2.若s不为空,删除s最后一个字符,然后将剩下的字符加入t的后缀。
3.若s不为空,删除s第一个字符,然后将剩下的字符加入t的后缀。
4.若s不为空,回到第二步,否则结束。
操作结束后,小美有若干次询问,每次查询t的第x个字符是多少。
输入描述
第一行输入两个正整数n,q,代表字符串s的长度,以及询问次数。
第二行输入一个长度为n的、仅由小写字母组成的字符串,代表字符 s
接下来的q行,每行输入一个正整数x,代表一次查询。
1≤n,q≤105
输出描述
输出q行,每行输出一个小写字母代表查询的答案。
输入
32
abc
2
5
输出
b
b
说明:输出的字符串为abcabb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
from collections import deque
n,q = map(int,input().split())
s = input()
t =s
s = deque(s)
#t =deque(t)
print(t)
while len(s)>0:
if len(s)>0:
s.pop()
t =t +list(s)
if len(s)>0:
s.popleft()
t =t +list(s)
for i in range(q):
x = int(input())
print(t[x-1])
|
中华财险技术笔试
java
1、java中哪些类型的变量不能被赋值为null
基本数据类型不能赋值为null;只有引用类型,如对象数组,可以被赋值为null
静态变量(类变量)和成员变量(实例变量)都是引用类型(Reference types),因此它们可以被赋值为null。
2、super,this关键字
用于引用对象的特定类型
super:引用父类的成员使用 super 关键字可以在子类中引用父类的成员(方法、属性),包括在子类中被隐藏的同名属性或方法。
调用父类构造函数在子类构造函数中,可以使用 super() 来调用父类的构造函数。这通常用于子类需要在构造过程中执行父类特定的初始化操作。
this:引用当前对象使用 this 关键字可以在类的成员方法中引用当前对象。
解决命名冲突,this 可以用来区分成员变量和局部变量,当它们同名时,使用 this 可以指代当前对象的成员变量。
总的来说,super 用于与父类有关的操作,包括引用父类的成员和调用父类的构造函数,而 this 则用于当前对象的操作,包括引用当前对象的成员和解决成员变量与局部变量之间的命名冲突。
最佳实践是将 super() 或 this() 调用放在构造函数的第一行,以确保正确的对象初始化。
super() 和 this() 可以同时出现在一个构造函数中。这种情况通常发生在需要调用另一个构造函数或者需要引用当前对象的情况下。
当两者同时出现时,需要注意以下几点:
- 构造函数中只能出现一个显式的调用:在一个构造函数中,只能显式调用
super() 或 this() 中的一个,因为它们都需要作为构造函数的第一条语句。
- 隐式调用:如果在构造函数中没有显式调用
super() 或 this(),Java会在构造函数的第一行自动插入一个无参的 super() 调用。
- 先调用super(),再调用this():如果在一个构造函数中显式调用了
this(),那么它必须是构造函数的第一条语句,而在**this()**调用的构造函数中也可能含有**super()**调用。
1
2
3
4
5
6
7
8
9
10
11
12
|
class Example {
int value;
Example() {
this(0); // 调用另一个构造函数
}
Example(int value) {
super(); // 可以在this()后调用super()
this.value = value;
}
}
|
在上述示例中,无参的构造函数中显式调用了有参的构造函数 this(0),而有参的构造函数中又显式调用了 super(),这种情况是允许的。
在Java中,super 和 this 关键字在静态环境中(例如静态方法和静态语句块)是不允许使用的。这是因为这两个关键字是用来引用对象实例的,而在静态环境中,并不存在具体的对象实例,因此无法使用 this 和 super。
3、java classloader
Java中的ClassLoader是Java虚拟机(JVM)的一个子系统,它的主要作用是动态加载Java类到JVM中。
作用:
- 加载类文件:ClassLoader负责将类文件加载到内存中,使得Java程序能够使用这些类。
- 类隔离:不同的ClassLoader加载的类相互隔离,每个ClassLoader都有自己的命名空间,从而避免类名冲突。
- 动态更新:允许在运行时动态地加载新的类,这在一些框架和应用程序中具有重要作用,比如插件系统。
特点:
- 层级结构:ClassLoader以层级结构组织,形成父子关系,当子ClassLoader需要加载类时,会先委托给父ClassLoader来尝试加载,只有在父ClassLoader无法加载时,子ClassLoader才会尝试加载类。
- 双亲委派模型:基于双亲委派模型,每个类加载请求都会被委托给父ClassLoader,只有在父ClassLoader无法完成加载时,子ClassLoader才会尝试加载。这样做可以保证类加载的一致性和避免重复加载。
- 自定义ClassLoader:允许开发人员自定义ClassLoader,以满足一些特殊的加载需求,比如从网络中加载类,或者对类进行加密保护等。
ClassLoader在Java中起着至关重要的作用,它不仅实现了Java程序的动态性,同时也保证了类的安全性和隔离性。
在Java中,默认提供了三个主要的ClassLoader,Bootstrap ClassLoader,Extension ClassLoader,System ClassLoader。
当JVM判定两个class是否相同时,不仅要判断类名是否相同,还要考虑类加载器。在Java中,类的唯一性是由类加载器和类的全限定名(包括类的包名)共同决定的。
4、sleep()方法和wait()方法
- 所属类:
- **
sleep()方法属于Thread类,而wait()方法属于Object**类。
- 调用方式:
- **
sleep()方法是静态方法,可以直接通过Thread.sleep()**调用。
- **
wait()方法则需要在同步上下文(即synchronized**块或方法)中调用。
- 释放锁:
- 当线程调用**
sleep()**方法时,它不会释放持有的任何锁。
- 当线程调用**
wait()**方法时,它会释放对象上的锁,并进入等待状态。
- 唤醒条件:
- 线程调用**
sleep()**方法后会在指定时间后自动唤醒,或者可以通过**interrupt()**方法提前唤醒。
- 线程调用**
wait()**方法后,需要通过其他线程调用对象上的**notify()**或**notifyAll()**方法来唤醒它。
- 它们都涉及线程的暂停和恢复操作,用于线程之间的协调和同步。
5、抽象类
抽象类是用于包含抽象方法的类,无法被实例化。抽象类使用**abstract**关键字来定义,它可以包含抽象方法和具体方法。子类必须实现抽象类中的所有抽象方法才能实例化。
抽象类的主要作用是作为其他类的父类,提供一组通用的方法和属性,并定义一些抽象方法,让子类去实现这些抽象方法。
抽象类可以有构造方法,但是它们不能被用来实例化抽象类本身。构造方法在抽象类中的作用是初始化实例变量或执行其他必要的操作。子类在实例化时会调用父类的构造方法来初始化父类的实例变量。
抽象类必须提供至少一个抽象方法。抽象方法是在抽象类中声明但没有实现的方法,它不包含方法体。抽象方法的存在使得抽象类本身无法被实例化,因为抽象类中的抽象方法必须由子类实现后才能使用。
有抽象方法的类不一定是抽象类(抽象类的子类),但是只有抽象类才能包含抽象方法。一般类可以包含抽象方法,但它必须标记为抽象类。抽象类可以包含抽象方法,也可以包含具体方法。另一方面,普通类不能包含抽象方法,因为抽象方法必须在抽象类中。
6、ThreadLocal
ThreadLocal 是 Java 中的一个类,它提供了线程局部变量的支持。使用 ThreadLocal 可以让每个线程都拥有自己独立的变量副本,互不影响。这在多线程环境下非常有用,可以避免线程间的数据共享问题。
通过 ThreadLocal,每个线程可以像使用普通变量一样操作自己的局部变量,而不必担心线程安全性。
ThreadLocal 采用线程隔离的方式存放数据。每个 ThreadLocal 实例都为每个线程提供了一个独立的变量副本,这意味着每个线程都可以独立地操作自己的局部变量,而不会受到其他线程的影响。当一个线程访问 ThreadLocal 的 get() 方法或set()方法时,实际上是操作了当前线程的局部变量,这种方式实现了线程之间数据的隔离,从而避免了线程安全性问题。
在Java 9中,引入了 ThreadLocal 的 remove 方法,它可以用来删除当前线程的局部变量值。调用 remove() 方法后,当前线程对应的局部变量会被清除,这样可以帮助减少内存泄漏的风险。
7、权限修饰符
| 操作修饰符 |
本类内部 |
本包内 |
其他包的子类 |
其他包非子类 |
| private |
√ |
|
|
|
| 缺省(default) |
√ |
√ |
|
|
| protected |
√ |
√ |
√ |
|
| public |
√ |
√ |
√ |
√ |
8、== 和 equals
在Java中,== 和 equals 是用于比较对象的两种不同方式。
== 比较的是对象的引用,即判断两个对象是否指向同一个内存地址。如果比较的是基本数据类型,则比较它们的值。
equals 是用于比较对象的内容是否相等,它是一个方法,可以被重写。在**Object**类中,**equals**方法默认是使用**==**比较对象的引用
9、多线程
多线程是指在同一个进程内同时执行多个线程的能力。每个线程都可以独立执行不同的任务,但它们共享同一个进程的内存空间和资源。多线程可以提高程序的并发性能和响应速度,常用于并发处理、异步编程和提升系统性能等方面。
10、str.substring()的取值范围
str.substring() 方法用于从指定的开始索引到结束索引截取字符串。其取值范围是从开始索引处的字符(包括)到结束索引前的字符(不包括)
11、java object默认的基本方法
Java中,**Object**类默认包含以下基本方法:
equals(Object obj): 用于判断对象是否相等。
hashCode(): 返回对象的哈希码值。
toString(): 返回对象的字符串表示。
getClass(): 返回对象的运行时类。
clone(): 用于创建并返回对象的副本。
finalize(): 当对象被垃圾回收器回收时调用。
notify(), notifyAll(), wait(): 用于实现线程间的通信。wait()导致当前线程等待,直到其他线程调用此对象的notify(),notifyall().
- 不包含copy()方法
12、nginx.log
nginx.log通常是Nginx Web服务器生成的日志文件,记录了服务器的访问日志、错误日志等信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
tail -f nginx.log //实时监视`nginx.log`文件的最新内容,并将其输出到屏幕上。
`cat nginx.log`: 显示nginx.log文件的全部内容。
`less nginx.log`: 逐页显示nginx.log文件的内容,可向上/向下滚动查看。
`grep "keyword" nginx.log`: 在nginx.log文件中搜索包含指定关键字的内容。
`wc -l nginx.log`: 统计nginx.log文件中的行数。
`head -n 10 nginx.log`: 显示nginx.log文件的前10行内容。
`tail -n 10 nginx.log`: 显示nginx.log文件的最后10行内容。
|
13、String框架的持久化支持方法
Java中的String类是不可变的,因此并不需要专门的持久化支持方法。如果您需要将String对象持久化到文件或数据库中,您可以使用常规的持久化技术,如文件I/O或数据库操作。
在JDBC中,通常会使用String来表示和处理数据库中的文本数据,但String并不是JDBC的一部分,也没有直接封装JDBC的数据操作
Spring对各种持久化技术提供了统一的编程方式
14、CMS垃圾回收器
CMS垃圾回收器通常指的是内容管理系统的垃圾回收机制,用于清理不再需要的或过时的内容和资源,以节省存储空间和优化系统性能。不过,这与编程语言中的垃圾回收器不同,后者用于自动回收不再使用的内存。
15、JVM堆内存
JVM(Java Virtual Machine)中的堆内存被分为两个主要区域:新生代(Young Generation)和老年代(Old Generation)。这种分区的目的是为了更有效地管理内存和进行垃圾回收。
新生代主要用来存放新创建的对象。由于大多数对象在创建后不久就会变为不可达(即成为垃圾),因此新生代是垃圾收集发生最频繁的区域。新生代又进一步细分为三个区:Eden区、SurvivorFrom区和SurvivorTo区。
老年代主要存放那些经历了多次GC仍然存活的对象,或者是在新生代中分配空间不足而直接分配到老年代的大对象。由于老年代中的对象生命周期较长,因此Major GC(也称作Full GC)在老年代中发生的频率相对较低。但每次Full GC都会暂停所有的应用线程,因此其执行时间相对较长,对系统性能的影响也较大。
JVM(Java Virtual Machine)中的HotSpot是Oracle JDK和OpenJDK的默认虚拟机,也是目前使用最广泛的JVM实现之一。HotSpot虚拟机通过即时编译(JIT,Just-In-Time)技术,将热点代码(即频繁执行的代码)编译成机器码,从而提高了Java程序的执行效率。
新生代与老年代的比例是1:2。新生代内部又被细分为Eden区和两个Survivor区(通常命名为S0和S1)。在HotSpot虚拟机中,Eden区与Survivor区的默认比例是8:1:1。
16、?三元运算符
条件 ? 表达式1 : 表达式2
int max = (a > b) ? a : b;
17、ArrayList
在Java中,当你使用ArrayList并初始化其指定容量为10时,这并不意味着ArrayList只能存储10个元素。实际上,这个容量只是ArrayList的内部数组的初始大小。当你试图添加第11个元素时,ArrayList会自动进行扩容。
添加元素导致当前容量不足时,ArrayList通常会将其内部数组的大小增加大约50%。它会创建一个新的更大的数组,将原数组的内容复制到新数组中,然后丢弃原数组,以便为新的元素腾出空间。这个过程被称为数组的扩容或再分配。
在创建 ArrayList 时,虽然不强制要求指定初始化容量,但根据实际应用场景,有时指定一个合适的初始化容量可以提高性能。当你预计 ArrayList 将存储大量元素,并且你知道大致的数量时,指定初始化容量可以避免在添加元素过程中频繁的扩容操作,这可以减少内存分配和数组复制的开销。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
// 1. 创建 ArrayList 实例
ArrayList<String> names = new ArrayList<>();
// 指定初始化容量为 10
// ArrayList<String> names = new ArrayList<>(10);
// 2. 向 ArrayList 中添加元素
names.add("Alice");
names.add("Bob");
names.add("Charlie");
// 3. 访问和修改 ArrayList 中的元素
String firstName = names.get(0); // 获取第一个元素
System.out.println("第一个名字是: " + firstName); // 输出: 第一个名字是: Alice
names.set(0, "Amy"); // 修改第一个元素为 Amy
firstName = names.get(0);
System.out.println("修改后的第一个名字是: " + firstName); // 输出: 修改后的第一个名字是: Amy
// 4. 遍历 ArrayList 中的元素
for (String name : names) {
System.out.println(name);
}
// 输出:
// Amy
// Bob
// Charlie
// 5. 执行其他操作
names.remove(1); // 删除索引为 1 的元素 (Bob)
System.out.println("删除元素后的 ArrayList:");
for (String name : names) {
System.out.println(name);
}
// 输出:
// Amy
// Charlie
System.out.println("ArrayList 的大小是: " + names.size()); // 输出 ArrayList 的大小
}
}
|
18、元注解:
Java元注解(Meta-Annotation)是用于修饰其他注解的注解。它们提供了定义注解时的一些通用属性和行为。
@Target用于指定注解的使用范围,即该注解可以被用于哪些Java元素上,例如类、方法、字段等
@Retention用于指定注解的生命周期,即该注解会被保留到哪个阶段。
RetentionPolicy.SOURCE:注解只在源码阶段存在,编译时会被丢弃。
RetentionPolicy.CLASS:注解在类文件中可用,但会被JVM丢弃。
RetentionPolicy.RUNTIME:注解在运行时也保留,因此可以通过反射读取。
@Documented表示使用该注解的元素应该被javadoc或类似工具文档化。简单来说,如果一个注解类型被声明为@Documented,那么使用了此注解的Java元素在使用javadoc生成API文档时,注解将被包含在生成的文档中。
@Inherited元注解是一个标记注解,如果它存在于一个被用于类的注解类型上,那么这个注解将被自动继承给子类的成员。但是要注意,继承性并不具备传递性,如果一个类使用了一个有@Inherited注解的注解类型,这个类的子类将继承这个注解,但是这个类的孙子类不会继承这个注解。
注解:
Java的注解(Annotation)是Java 5开始引入的一种用于为代码提供元数据的机制。注解本质上是一种接口,它提供了一种为程序元素(类、方法、变量、参数和包等)附加信息的方式,而这些信息并不会直接影响代码的执行,但可以被编译器用来生成代码、进行编译检查,或者在运行时由JVM或其他工具读取并进行相应的处理。
- 元数据:注解为代码提供了额外的信息,这些信息并不直接参与程序的执行逻辑,但可以被其他工具或框架用来生成代码、检查代码或执行其他任务。
- 与注释的区别:注解与Java的注释不同,注释是给程序员看的,而注解是给编译器或其他工具看的。编译器会处理注解,而忽略注释。
- 定义:注解使用
@interface关键字定义
19、锁升级:
在Java中,锁升级通常与synchronized关键字以及Java HotSpot JVM的实现相关。synchronized关键字在Java中用于提供线程同步,它背后的机制依赖于JVM的实现。在某些JVM实现(如Java HotSpot VM)中,为了优化性能,当synchronized块或方法被争用时,锁的状态可能会经历一个“升级”过程。
Java HotSpot VM的锁升级通常涉及以下状态:
- 无锁状态:这是锁的初始状态,当没有线程持有锁时,锁处于无锁状态。
- 偏向锁:这是锁的第一次优化状态。偏向锁的目标是减少无竞争情况下的同步开销。当一个线程首次访问同步块并获取锁时,JVM会记录锁的持有者,并在后续访问中偏向这个线程,从而避免不必要的锁获取和释放操作。如果其他线程尝试获取偏向锁,偏向锁就会被撤销并升级到轻量级锁。
- 轻量级锁:当多个线程试图同时访问同一个同步块时,锁可能会升级到轻量级锁状态。轻量级锁通过自旋等待(busy-waiting)的方式尝试获取锁,而不是立即阻塞线程。如果自旋等待一段时间后仍然无法获取锁,轻量级锁会升级为重量级锁。
- 重量级锁:当轻量级锁的自旋等待无法解决问题时,锁会最终升级为重量级锁。重量级锁通常涉及操作系统层面的同步机制(如互斥锁),并且当线程无法获取锁时会阻塞。重量级锁的开销相对较大,因为它涉及到线程的挂起和恢复。
互斥锁(Mutex,全称Mutual Exclusion Lock)是一种基本的同步原语,用于在并发编程中保护共享资源,防止多个线程同时访问同一资源,从而导致数据不一致或其他并发问题。当一个线程获得互斥锁时,其他尝试获取该锁的线程将会被阻塞,直到持有锁的线程释放该锁。
互斥锁通常具有以下特性:
- 互斥性:在任何时刻,只有一个线程可以持有互斥锁。这确保了同一时间只有一个线程能够访问被保护的共享资源。
- 原子性:锁的获取和释放操作是原子的,即这些操作不会被其他线程中断。这保证了线程安全地获取和释放锁。
- 非忙等待:当一个线程试图获取一个已被其他线程持有的互斥锁时,该线程通常会被阻塞(即进入等待状态),而不是忙等待(即持续检查锁是否可用)。这允许线程在等待期间释放CPU资源,从而提高了系统的整体效率。
- 可重入性:某些互斥锁实现允许同一个线程多次获取同一把锁而不会引起死锁,这种锁被称为可重入锁或递归锁。
- 公平性:某些互斥锁实现会确保等待时间最长的线程优先获得锁,这被称为公平锁。然而,并非所有的互斥锁实现都是公平的,有些实现可能采用非确定性策略来分配锁。
20、支持反射:
Java是一种支持反射(Reflection)的语言。反射是Java语言的一个强大特性,它允许程序在运行时检查类、接口、字段和方法的信息,并且允许程序动态地创建和操作对象。这种动态性为Java提供了极大的灵活性和扩展性。
- 获取类的信息:通过反射,可以获取一个类的Class对象,进而获取该类的名称、父类、实现的接口、声明的字段和方法等信息。
- 动态创建对象:使用反射,可以在运行时动态地创建类的实例,而无需提前编写具体的类名。
- 调用方法:通过反射,可以获取一个类的方法信息,并动态地调用该方法,传递参数并获取返回值。
- 操作字段:反射允许程序获取类的字段信息,并可以动态地读取和修改字段的值。
反射虽然强大,但也有一些缺点。比如性能开销较大,因为反射涉及到动态类型解析和内存分配等操作;另外,反射破坏了封装性,因为它允许程序直接访问类的内部结构和成员。
JAVA反射主要涉及的类包括Class、Field、Method和Constructor。这些类都位于特定的包下,以便有效地组织和使用。通过这些类,Java的反射机制能够在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够动态调用它的任意方法和属性。这为Java提供了丰富的动态性支持,使得程序能够在运行时根据需要进行灵活的调整。虽然反射机制提供了很大的灵活性,但它也有一些缺点,比如性能开销较大,破坏了封装性等。
- Class类:这是反射的核心类,它位于
java.lang包下。通过Class类,我们可以获取类的属性、方法等信息,这是实现反射机制的关键。
- Field类:这个类位于
java.lang.reflect包下。它表示类的成员变量,通过Field类,我们可以获取和设置类中的属性值。
- Method类:同样,
Method类也位于java.lang.reflect包下。它表示类的方法,通过Method类,我们可以获取类中的方法信息或者执行方法。
- Constructor类:这个类也位于
java.lang.reflect包下。它表示类的构造方法,通过Constructor类,我们可以获取类的构造方法信息,并在运行时动态地创建对象。
Java反射机制本身并不提供字节码修改技术。Java反射机制主要允许程序在运行时检查类、接口、字段和方法的信息,并且可以动态地创建和操作对象。它提供了对类和对象的内部结构的访问能力,但并不涉及对字节码的修改。Java反射机制本身不能直接动态地实现一个接口并形成一个新的类。反射主要用于在运行时检查和操作已经存在的类、接口、字段和方法。虽然反射能够获取接口的信息,但它不能用来动态地创建实现该接口的新类。
设计模式
Java设计模式是一套被广泛接受和应用于Java编程的解决方案。这些设计模式是针对常见问题的通用解决方案,通过它们可以更容易地编写出易于理解、可维护和可扩展的代码。Java设计模式通常可以分为以下几种类型: 1. 创建型模式:这些模式关注对象的创建机制,包括如何实例化对象或者将对象组合成更复杂的结构。典型的创建型模式包括工厂模式、抽象工厂模式、建造者模式、原型模式和单例模式。 2. 结构型模式:这些模式处理类和对象的组合,从而形成更大的结构。它们帮助确保在系统中更容易地识别对象之间的关系。结构型模式包括适配器模式、桥接模式、组合模式、装饰器模式、外观模式、享元模式和代理模式。 3. 行为型模式:这些模式涉及对象之间的交互,包括如何分配职责以及如何实现通信。行为型模式包括模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式和访问者模式。 这些设计模式为解决特定类型的问题提供了指导,使得开发人员可以更容易地编写出高质量、易于理解和可维护的代码。在实际的软件开发中,设计模式通常被视为一种最佳实践,有助于提高代码的灵活性、可重用性和可扩展性。
1、模版模式和策略模式
模板模式和策略模式都属于行为型设计模式,它们都关注对象之间的交互,但它们的应用场景和解决问题的方式略有不同。
模板模式
- 特点:
- 模板模式定义了一个算法的骨架,将某些步骤的具体实现延迟到子类中。它允许子类为一个或多个步骤提供其自己的实现,同时保持算法的结构不变。
- 模板模式通常由一个抽象类和一组实现类组成,抽象类中定义了算法的框架,而具体实现类提供了算法中具体步骤的实现。
- 在模板模式中,算法的结构由父类决定,具体步骤的实现则由子类决定。
- 应用场景:
- 当有一个算法的整体结构是固定的,但是其中某些步骤的具体实现可能不同的时候,可以考虑使用模板模式。
策略模式
- 特点:
- 策略模式定义了一系列的算法,并将每一个算法封装起来,使它们可以相互替换。这使得算法可以独立于使用它们的客户而变化。
- 在策略模式中,算法被视为一个可替换的行为,并且客户可以在运行时动态地选择所需的算法。
- 应用场景:
- 当一个问题有多种解决方案,或者一个系统需要动态地在几种算法中选择一种的时候,可以考虑使用策略模式。
联系
-
联系:
- 模板模式和策略模式都涉及到算法的封装和替换,但它们的关注点不同。
- 模板模式关注的是算法的整体结构,提供一个固定的算法框架,允许子类提供特定步骤的实现。
- 策略模式关注的是多个算法的替换和动态选择,使得客户可以在运行时选择合适的算法。
虽然它们的关注点不同,但是在实际应用中,这两种模式可能会相互结合,比如模板模式可以使用策略模式来实现某些具体步骤的替换。
在某些情况下,策略模式和模板模式确实可以在一定程度上互换使用,特别是在涉及算法的封装和替换方面。虽然它们的主要关注点不同,但在一些情况下可以进行相互转换或结合使用。
模板模式使用的是集成关系实现,策略模式使用的是组合关系实现
模板模式倾向于将解决问题的过程定义为一个完整的框架,其中具体的步骤由子类来实现。策略模式中,通常会定义一个策略接口或抽象类,具体的算法实现则通过这个接口或抽象类来实现。
2、单例模式:
Java中的单例模式是一种创建对象的设计模式,它保证在一个应用程序中,某个类只有一个实例存在。单例模式通常用于那些只需要一个对象的场景,比如配置文件的读取、线程池、缓存等。
单例模式并不是Java特有的。单例模式是一种设计模式,它的主要目的是确保一个类仅有一个实例,并提供一个全局访问点来访问这个唯一实例。这种设计模式可以应用于任何面向对象的编程语言,包括Java、C++、C#、Python等。
3、观察者模型
对象间存在一对多关系,当一个对象被修改时,自动通知其依赖对象,这种设计模式通常采用的是观察者模式(Observer Pattern)。
观察者模式是一种行为设计模式,它允许对象之间定义一对多的依赖关系,当一个对象状态发生改变时,它的所有依赖者都会收到通知并自动更新。
这种设计模式有助于实现模块化、可复用和可扩展的代码,因为它降低了对象之间的耦合度,使得各个对象可以独立地改变和演进。虽然观察者模式在处理一对多关系时非常有效,但在某些情况下,如果依赖关系过于复杂,可能会导致性能问题或难以维护
数据结构
1、二叉树遍历 前序,中序,后序。
后序遍历从后开始看,最后一个是根节点,然后根据中序遍历区分左右子树。
2、循环队列
循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首以形成一个循环。它也被称为“环形”队列。循环队列在内存中是一个环形空间,当存储空间被填满后,就从第一个元素开始覆盖。
循环队列的主要优势在于,它可以更有效地利用存储空间,因为当一个元素被移除后,其位置可以被新的元素立即占用,而不需要进行任何额外的数据移动。此外,循环队列的入队和出队操作都是 O(1) 复杂度,即常数时间复杂度,这使得循环队列在处理大量数据时非常高效。
循环队列的实现通常需要两个指针,一个指向队头(front),另一个指向队尾(rear)。当元素入队时,rear 指针向前移动;当元素出队时,front 指针向前移动。当 front 和 rear 相遇时,队列为空;当 rear 到达队列的末尾并准备再次回到队首时,队列为满。
在实现中,我们预留了一个数组位置来区分队列满和队列空的情况。当rear指针追上front指针时,队列是满的;当front和rear都指向同一个位置且队列大小为0时,队列是空的。
数据库
1、MySQL数据库四种隔离级别
MySQL 数据库提供了四种标准的事务隔离级别,分别是:
- READ UNCOMMITTED(读取未提交的数据):最低的隔离级别,事务可以读取未提交的数据,可能会导致脏读、不可重复读和幻读问题。
- READ COMMITTED(读取提交的数据):事务只能读取已提交的数据,可以避免脏读问题,但仍可能出现不可重复读和幻读问题。
- REPEATABLE READ(可重复读):确保在事务执行期间多次读取的数据保持一致,可以避免脏读和不可重复读问题,但仍可能出现幻读问题。
- SERIALIZABLE(可串行化):最高的隔离级别,确保事务之间的数据读写操作彼此之间完全隔离,可以避免脏读、不可重复读和幻读问题,但会降低并发性能。
2、数据库设计范式:
数据库设计范式是数据库设计中所需要满足的规范,这些规范确保数据库结构清晰、简洁,并避免数据冗余和异常操作。范式是一系列逐级递增的规范,越高的范式数据库冗余越小。关系数据库中有六种范式,分别是第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。
第一范式(1NF)要求数据库表中的每个字段都是不可分割的原子数据项,即每个字段都是单一属性的,不可再分。这意味着每个字段只能包含一个值,而不能包含多个值或者数组。第一范式(1NF)是所有关系型数据库的最基本要求
第二范式(2NF)是在第一范式的基础上建立的,它要求数据库表中的每个非主键字段必须完全依赖于整个主键。换句话说,如果表中有复合主键,那么任何非主键字段不能仅依赖于主键的一部分。
第三范式(3NF)则进一步要求数据库表中的每个非主键字段不能依赖于其他非主键字段。这确保了数据表中的字段之间不存在传递依赖关系。
遵循数据库设计范式可以确保数据库结构的合理性,减少数据冗余,提高数据的一致性和可维护性。同时,它也有助于避免在数据库操作(如插入、删除和更新)时出现异常。
数据库设计并不必须完全遵守所有的范式。虽然范式理论为数据库设计提供了有价值的指导原则,但在实际应用中,完全遵守所有范式可能会导致数据库设计过于复杂,影响性能,甚至与业务需求相悖。
- 业务需求:首先,数据库设计的最终目标是满足业务需求。在某些情况下,为了满足特定的业务需求,可能需要适当地牺牲某些范式规则。例如,在某些数据仓库或数据分析场景中,为了提高查询性能,可能会选择保留一些冗余数据。
- 性能考虑:完全遵守高级范式(如4NF和5NF)可能导致数据库结构过于复杂,增加查询的复杂性,从而影响性能。在某些情况下,为了提高性能,可能会选择保留一些低级别的冗余数据。
- 维护成本:过于严格的范式遵守可能导致数据库结构过于复杂,增加维护成本。在某些情况下,简化数据库结构以降低维护成本可能是一个合理的选择。
- 数据一致性:虽然范式理论有助于确保数据一致性,但在实际应用中,还需要通过其他手段(如触发器、存储过程等)来维护数据的一致性。
设计范式之间确实有一定的独立性,因为每个范式都是针对特定问题或场景提出的解决方案。然而,这些范式之间也存在一定的关联和交互。
计网
1、tcp/ip网络中,为各种公共服务保留的端口范围是 0-1023
大于1023的端口则可供用户级应用程序使用
2、tcp/ip协议
TCP/IP协议集分为四层,分别是:
-
应用层:包括诸如HTTP、FTP、SMTP等协议,用于应用程序之间的通信和数据交换。
HTTP(超文本传输协议),SMTP(简单邮件传输协议),FTP(文件传输协议),DNS(域名系统)
-
传输层:主要有TCP(传输控制协议)和UDP(用户数据报协议),负责在网络中传输数据并提供端到端的通信服务。
-
网络层:主要有IP(网际协议),负责数据包的路由和转发,实现不同网络之间的通信。
-
数据链路层:包括以太网、WiFi等,负责在物理网络中传输数据帧,并提供了物理寻址、错误检测和纠正等功能。
HTTP(Hypertext Transfer Protocol)位于TCP/IP协议集中的应用层。 HTTP协议用于在网络中传输超文本文档,是Web应用程序的基础,通过HTTP,客户端(如浏览器)可以向服务器请求资源,并接收服务器返回的数据。
TFTP(Trivial File Transfer Protocol)位于TCP/IP协议集中的传输层。 TFTP是一个简单的文件传输协议,用于在计算机网络中进行文件传输,它基于UDP协议进行数据传输,通常用于在局域网内进行简单的文件传输操作。
TCP在建立连接时使用三次握手,断开连接时使用四次挥手。
- 三次握手:
- 第一次握手:客户端发送连接请求报文段,服务端收到请求后,进入到 LISTEN(监听)状态。
- 第二次握手:服务端接收到连接请求后,发送确认报文段,并进入到 SYN-RECEIVED 状态。
- 第三次握手:客户端收到服务端的确认后,发送一个确认报文段,双方进入 ESTABLISHED(已建立连接)状态。
- 四次挥手:
- 第一次挥手:客户端发送连接释放报文段,并进入 FIN-WAIT-1 状态。
- 第二次挥手:服务端接收到释放报文段后,发送确认报文段,自己进入 CLOSE-WAIT 状态,等待数据传输完成。
- 第三次挥手:服务端完成数据传输后,向客户端发送连接释放报文段,并进入 LAST-ACK 状态。
- 第四次挥手:客户端接收到释放报文段后,发送确认报文段,双方进入 CLOSED(连接关闭)状态。
3、TCP超时重传:
在TCP中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。对于在传输过程中丢失的数据包,TCP使用重传机制来解决。这种机制包括设定一个定时器,在超过指定的时间后没有收到对方的ACK确认应答报文时,就会重发该数据,即超时重传。
git
1、哪个命令可以查看git所有分支git branch
如果想要查看远程仓库的分支,可以加上 -r 选项,或者使用 -a 选项来查看所有本地和远程跟踪的分支。-d 是删除本地分支的命令