哈希
HashMap
遍历:
// 遍历键值对
for (Map.Entry<Integer, String> entry : map.entrySet()) {
int key = entry.getKey();
String value = entry.getValue();
System.out.println("Key: " + key + ", Value: " + value);
}
// 遍历键
for (int key : map.keySet()) {
System.out.println("Key: " + key);
}
// 遍历值
for (String value : map.values()) {
System.out.println("Value: " + value);
}
常用方法
containsKey(Object key)
方法用于检查 HashMap 中是否包含指定的键。getOrDefault(Object key, V defaultValue)
方法用于获取 HashMap 中指定键的值,如果键不存在,则返回默认值。values()
方法返回一个 Collection 视图,其中包含 HashMap 中所有的值。size()
putIfAbsent()
用于在仅当指定键尚不存在时才将给定键值对插入映射中
key
● 排序后的字符串唯一,可以作为键
● 返回不同连续子数组时,可以用数组下标
HashSet
● remove()
● contains()
双指针
- 同一个起点
- 左右两端
- 慢指针一次走一步,快指针一次走两步,快慢指针同时出发。
- 当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。
- 具体地,我们定义两个指针,一快一慢。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。
链表
滑动窗口
动态规划
字符串
toCharArray()
方法,可以将字符串转换为字符数组substring(int beginIndex)
:从指定的索引beginIndex处开始截取到字符串末尾,返回一个新的字符串。substring(int beginIndex, int endIndex)
:从指定的起始索引beginIndex开始,到结束索引endIndex(但不包括该位置)截取字符串,返回一个新的字符串。- 将一个字符转换为字符串,可以使用
String.valueOf()
方法- 将字符与空字符串相加
- Character
Character.isDigit()
判断是否是数字Character.isLetter()
判断是否是字符
StringBuilder
StringBuilder 是 Java 中的一个类,用于创建和操作可变字符串。与 String 不同,String 是不可变的,每次修改都会生成新的字符串,而 StringBuilder 允许你在原有内容上进行修改而不创建新的对象。
常用方法
StringBuilder sb = new StringBuilder();
// 1. append - 添加字符串
sb.append("Hello");
sb.append(" ");
sb.append("World");
System.out.println(sb.toString()); // 输出: Hello World
// 2. insert - 在指定位置插入字符串
sb.insert(5, ",");
System.out.println(sb.toString()); // 输出: Hello, World
// 3. delete - 删除指定范围的字符
sb.delete(5, 6);
System.out.println(sb.toString()); // 输出: Hello World
sb.deleteCharAt(sb.length() - 1);
// 4. reverse - 反转字符串
sb.reverse();
System.out.println(sb.toString()); // 输出: dlroW olleH
// 5. capacity - 获取当前容量
System.out.println("Capacity: " + sb.capacity());
// 6. length - 获取当前长度
System.out.println("Length: " + sb.length());
数组
Arrays.sort()
:对于基本数据类型的数组(如 int[]、double[]、char[] 等),可以直接使用 Arrays 类的 sort 方法来排序。在原数组上排序- 实现自定义排序:
Comparator如何判断升降序:用来比较的元素类型为int[ ],compare函数的含义为是否交换两个元素的值,如果interval1[0]>interval2[0],返回true,即交换两个元素的值。所以,经过排序后的数组为按元素索引0处的值升序排列。
- 实现自定义排序:
Arrays.sort(intervals, new Comparator<int[]>() {
public int compare(int[] interval1, int[] interval2) {
return interval1[0] - interval2[0];
}
});
● char[ ]数组转字符串:new String()
● Arrays.asList()
● Arrays.copyOfRange()
List
- 将List<int []>转二维数组返回:
return result.toArray(new int[result.size()][]);
addAll()
:将另一个集合中的所有元素添加到当前List中的方法List<List<Integer>> list = new ArrayList<>();
ArrayList<String> list = new ArrayList<>(10);
初始化容量为10list.add(Arrays.asList(1, 2));
get()
:获取List中元素add(int index, E element)
方法在指定位置插入元素,第一个参数是一个整数,表示要插入元素的位置,不可以传变量。
栈
- 初始化栈:
Stack<Integer> stack = newStack<>();
2.push(E item)
: 将元素压入栈顶。 pop()
: 弹出栈顶元素,并返回该元素。peek()
: 返回栈顶元素,但不移除它。empty()
: 检查栈是否为空。
6.search(Object o)
: 查找元素在栈中的位置,返回距离栈顶最近的位置(1为栈顶)。isEmpty()
: 判断栈是否为空,等同于empty()方法。
队列
双端队列Deque
Deque<String> deque =new ArrayDeque<>();
- 在头部和尾部添加元素
deque.addFirst("first");
deque.addLast("last");
- 获取并移除头部和尾部元素
- String first = deque.removeFirst();
- String last = deque.removeLast();
- 获取但不移除头部和尾部元素
- String peekFirst = deque.peekFirst();
- String peekLast = deque.peekLast();
排序
回溯
回溯法,一般可以解决如下几种问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
组合是不强调元素顺序的,排列是强调元素顺序
{1, 2} 和 {2, 1} 在组合上,就是一个集合,因为不强调顺序,而要是排列的话,{1, 2} 和 {2, 1} 就是两个集合了。
集合的大小就构成了树的宽度,递归的深度就构成了树的深度
算法模板
for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,一般来说,搜索叶子节点就是找的其中一个结果
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
以上,处理节点不一定是在for()
循环中,如括号生成
就是用的两个if()
排列
组合
- 如果是一个集合来求组合的话,就需要startIndex
- 如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex
startIndex
:防止出现重复的组合,根据条件传参数时要不要i + 1
- 传参
i
:在每次遍历完后不能取上次的值,即list中元素可以重复,但是list要去重 - 传参
i + 1
:取完上个值后就不能取这个值了,即list中元素不可以重复
二叉树
中序遍历
先序遍历
后序遍历
- 适合自底向上遍历,天然回溯
二叉搜索树
二叉搜索树的中序遍历是递增序列,可以用于;
- 寻找树中第几大的元素
- 判断是否是二叉搜索树
递归函数什么时候要有返回值,什么时候没有返回值
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。
图
BFS
可以用来求最短路径问题,需要使用队列
有向无环图
把一个 有向无环图 转成 线性的排序 就叫 拓扑排序。
- 入度为 0 的节点入队列
- 然后逐个出列,减小相关节点的入度。
- 如果相关节点的入度新变为 0,安排它入列、再出列……直到没有入度为 0 的课可入列。
总结:拓扑排序问题
- 根据依赖关系,构建Map、入度数组。
- 选取入度为 0 的数据,根据邻接表,减小依赖它的数据的入度。
- 找出入度变为 0 的数据,重复第 2 步。
- 直至所有数据的入度为 0,得到排序,如果还有数据的入度不为 0,说明图中存在环。
DFS
动态规划
动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。
很多动规的题目其实都是当前状态依赖前两个,或者前三个状态,都可以做空间上的优化。
解题步骤
- 定义子问题
- 子问题是和原问题相似,但规模较小的问题。例如这道小偷问题,原问题是 “从全部房子中能偷到的最大金额”,将问题的规模缩小,子问题就是 “从 k 个房子中能偷到的最大金额 ”,用 f(k) 表示。
- 可以看到,子问题是参数化的,我们定义的子问题中有参数 k。假设一共有 n 个房子的话,就一共有 n 个子问题。动态规划实际上就是通过求这一堆子问题的解,来求出原问题的解。这要求子问题需要具备两个性质:
- 原问题要能由子问题表示。例如这道小偷问题中,k=n 时实际上就是原问题。
- 一个子问题的解要能通过其他子问题的解求出。例如这道小偷问题中,f(k) 可以由 f(k−1) 和 f(k−2) 求出
- 写出子问题的递推关系
- 确定 DP 数组的计算顺序
- 空间优化(可选)
背包问题
遍历顺序就记住遍历物品在外层循环,遍历背包容量在内层循环
01背包
每个物品只有选与不选两个状态,最多选一个
二维数组[i][j]
遍历顺序无所谓,因为dp递归公式有i-1,所以i为0的时候就一定要初始化。递归公式中是由上一行的上方和左上角得到的,遍历顺序不会影响
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
滚动数组
从大到小遍历背包容量,保证物品只加一次。
一维数组初始化为0
先遍历物品嵌套遍历背包容量,不可以先遍历背包容量嵌套遍历物品。因为一维dp的写法,背包容量一定是要倒序遍历,如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。就比如说:
weight = [1, 3, 4], value = [15, 20, 30]
j=4:
- i = 0, dp[4] = Math.max(dp[4], dp[3] + 15) = 15,加了一个物品1
- i = 1, dp[4] = Math.max(dp[4], dp[1] + 20) = 20,加了一个物品2,但此时的容量为4,可以放下一个物品1和一个物品2,最大价值为35
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
完全背包
不选/选几个
从小到大遍历背包容量,因为完全背包物品可以重复加
在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的!但还要再加个判断,因为先遍历j,在遍历i,要判断j >= weight[i],再做大小比较
因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了
// 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
// 先遍历背包,再遍历物品
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
for(int i = 0; i < weight.size(); i++) { // 遍历物品
if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
cout << endl;
}
求排列
先遍历背包再遍历物品,排列数,先把物品排列好再放进背包
求组合
先遍历物品再遍历背包,组合数就是把物品放进背包,不考虑放的顺序
贪心
common
Math.max()超时
第二种写法使用Math.max()会超出时间限制,猜测是因为对于下面测试用例来说第一种只比较没有赋值,所以速度较快
第一种写法为:
for (int j = i - 1; j >= 0; j--) {
if (height[j] > max_left) {
max_left = height[j];
}
}
第二种写法为:
for (int left = i - 1; left >= 0; left--){
leftMax = Math.max(leftMax, height[left]);
}
测试用例为:
数据转换
- char -> String :
String.valueOf()
- String -> int :
Integer.parseInt()
- Character -> int :
//第一种方法:如果字符是数字字符('0' - '9'),你可以直接使用 Character.digit(char, int radix) 方法
char ch = '5';
int intValue = Character.digit(ch, 10); // intValue = 5
//你可以将字符减去字符 '0',因为 '0' 的 ASCII 码值为 48。
char ch = '5';
int intValue = ch - '0'; // intValue = 5
//使用 (int) ch 强制类型转换。
char ch = 'A';
int unicodeValue = (int) ch; // unicodeValue = 65
char cur = s.charAt(ptr);
if (Character.isDigit(cur)) {
// 获取一个数字并进栈
String digits = getDigits(s);
stk.addLast(digits);
}
- List -> int
- int -> Integer :
Integer.valueOf()
- Integer -> int :
Integer numObj = new Integer(10);
int num = numObj; // 自动拆箱
int num2 = numObj.intValue();