332.重新安排行程
技巧:
递归函数返回值设置boolean阻断回溯
对递归的理解又深入了:
不管是二叉树的左右节点遍历还是回溯算法槽位选择数据池,都可以看成是线性的,线上的每一个槽位可以更换数据,回溯算法槽位数据的更换是由for循环控制的,二叉树槽位数据的更换是手动写两个递归函数使它们参数列表不同来控制的。
缺点:
- 对ArrayList,LinkedList,增删查改调用不熟,分别擅长什么操作也不知道
- map的映射的高级应用比如映射对象是个列表或是个map,对与List的复合搞不熟练
- 排序不会,compareTo不懂
ArrayList和LinkedList:
删除
ArrayList 和 LinkedList 在删除元素的方式上有些区别:
ArrayList:
- 你可以使用
remove(int index)
方法,根据索引来删除元素。例如:list.remove(2)
将删除列表中索引位置为 2 的元素。- 你也可以使用
remove(Object obj)
方法,根据元素的值来删除元素。例如:list.remove("value")
将删除列表中第一个匹配到的值为 "value" 的元素。LinkedList:
- 与 ArrayList 不同,LinkedList 提供了
removeFirst()
和removeLast()
方法,分别用于删除链表的第一个和最后一个元素。- 你也可以使用
remove(Object obj)
方法,根据元素的值来删除元素,与 ArrayList 类似。- LinkedList 还提供了
removeFirstOccurrence(Object obj)
方法,它可以删除链表中第一次出现的指定值的元素。在大多数情况下,你可以根据需要选择合适的方法来删除元素。但需要注意的是,如果你需要频繁在列表中间插入或删除元素,LinkedList 通常更适合,因为它的插入和删除操作比 ArrayList 更高效。如果只需要按索引访问元素,ArrayList 可能更合适。
插入
对于插入操作,也有一些不同的方法:
ArrayList:
- 你可以使用
add(int index, E element)
方法,根据索引将元素插入到指定位置。例如:list.add(2, "value")
将在索引位置 2 处插入元素 "value"。- 你还可以使用
addAll(int index, Collection<? extends E> c)
方法,将一个集合的元素插入到指定位置。LinkedList:
- LinkedList 在插入方面非常高效。你可以使用
addFirst(E e)
和addLast(E e)
方法,在链表的开头和末尾插入元素。- 你还可以使用
add(int index, E element)
方法,根据索引将元素插入到指定位置。总的来说,ArrayList 和 LinkedList 都提供了在指定位置插入元素的方法,你可以根据需要选择合适的方法来执行插入操作。LinkedList 在插入和删除操作上通常更高效,但在按索引访问元素时性能略低于 ArrayList。
访问
访问元素通常是指获取列表中特定位置的元素。在这方面,ArrayList 和 LinkedList 也有一些不同之处:
ArrayList:
- 你可以使用
get(int index)
方法,根据索引来访问特定位置的元素。例如:String element = list.get(2)
将返回索引位置 2 处的元素。- 由于 ArrayList 内部使用数组来存储元素,通过索引直接访问元素是非常高效的操作。
LinkedList:
- 你可以使用
get(int index)
方法,根据索引来访问特定位置的元素,与 ArrayList 类似。- 此外,LinkedList 还支持从头部或尾部访问元素:
getFirst()
方法用于获取第一个元素。getLast()
方法用于获取最后一个元素。需要注意的是,虽然 LinkedList 支持从头部和尾部访问元素,但在按索引访问元素时性能较差,因为它需要从头部或尾部开始遍历链表,直到达到目标位置。如果需要频繁按索引访问元素,ArrayList 通常更高效。如果只需在开头或末尾执行访问操作,LinkedList 可能更合适。
Map:
HashMap
是 Java 中的一种键值对映射容器,提供了访问、删除和插入操作。以下是关于HashMap
的常见操作方法:
插入元素:
你可以使用
put(K key, V value)
方法将键值对插入到HashMap
中,其中key
是键,value
是值。如果key
已经存在,它将替换旧值。HashMap<String, Integer> map = new HashMap<>(); map.put("apple", 10); map.put("banana", 5);
访问元素:
你可以使用
get(Object key)
方法根据键来获取值。Integer count = map.get("apple"); // 获取键 "apple" 对应的值
删除元素:
你可以使用
remove(Object key)
方法根据键来删除键值对。map.remove("banana"); // 删除键 "banana" 对应的键值对
检查是否包含键或值:
你可以使用
containsKey(Object key)
和containsValue(Object value)
方法来检查是否包含特定的键或值。boolean hasKey = map.containsKey("apple"); // 检查是否包含键 "apple" boolean hasValue = map.containsValue(10); // 检查是否包含值 10
获取所有键或值:
你可以使用
keySet()
方法来获取所有键的集合,或者使用values()
方法来获取所有值的集合。Set<String> keys = map.keySet(); // 获取所有键的集合 Collection<Integer> values = map.values(); // 获取所有值的集合
遍历键值对:
你可以使用
entrySet()
方法获取包含键值对的集合,然后遍历这个集合。for (Map.Entry<String, Integer> entry : map.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); // 迭代键值对 }
获取大小:
你可以使用
size()
方法来获取HashMap
中键值对的数量。int size = map.size(); // 获取键值对数量
清空
HashMap
:你可以使用
clear()
方法来清空HashMap
中的所有键值对。map.clear(); // 清空所有键值对
HashMap
是一个常用的数据结构,可以用于快速查找、插入和删除键值对。它提供了高效的操作方法,但需要注意键的唯一性,因为相同的键只能在HashMap
中出现一次。
getOrDefault(K key, V defaultValue)
和computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
是 JavaHashMap
提供的两个有用的方法。
初始化V,getOrDefault()
初始化V getOrDefault(K key, V defaultValue)
:这个方法用于获取指定键的值,如果键存在,则返回对应的值;如果键不存在,则返回默认值。这对于避免空指针异常很有用。方法签名如下:
V getOrDefault(Object key, V defaultValue)
例子:
HashMap<String, Integer> map = new HashMap<>(); map.put("apple", 10); // 获取键 "apple" 的值,如果不存在返回默认值 0 int count = map.getOrDefault("apple", 0); // 返回 10 // 获取键 "banana" 的值,如果不存在返回默认值 0 int count2 = map.getOrDefault("banana", 0); // 返回 0
初始化K-V对,computeIfAbsent()
初始化K computeIfAbsent(K key, Function<? super K, ? extends V>mappingFunction)
:这个方法用于在键不存在时计算一个值并将其放入
HashMap
。如果指定键已经存在,则不会执行计算操作。方法签名如下:V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
例子:
HashMap<String, Integer> map = new HashMap<>(); // 如果键 "apple" 不存在,计算默认值为 0 并放入 map int count = map.computeIfAbsent("apple", key -> 0); // 返回 0 // 如果键 "apple" 存在,不执行计算操作 int count2 = map.computeIfAbsent("apple", key -> 5); // 返回 0,因为键已存在
这两个方法是
HashMap
中的便捷方法,可以帮助你更轻松地处理键值对的操作,避免空指针异常,并按需进行计算。
合并,merge(),可用于初始化V
merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)
是 JavaHashMap
提供的方法,用于合并(或替代)特定键对应的值。如果键不存在,它会将指定值放入HashMap
;如果键已存在,则可以通过指定的函数来合并现有值和新值。方法签名如下:
V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)
key
:要合并的键。value
:要与现有值合并的新值。remappingFunction
:一个函数,接受两个参数:现有值和新值,并返回一个新值,用于合并这两个值。以下是一个示例:
HashMap<String, Integer> map = new HashMap<>(); map.put("apple", 10); // 合并键 "apple" 的值,将现有值 10 和新值 5 合并为 15 map.merge("apple", 5, (oldValue, newValue) -> oldValue + newValue); // 合并键 "banana" 的值,因为键不存在,所以直接将新值 3 放入 map.merge("banana", 3, (oldValue, newValue) -> oldValue + newValue); System.out.println(map.get("apple")); // 输出 15 System.out.println(map.get("banana")); // 输出 3
在示例中,我们使用
merge
方法合并了键 "apple" 的值,将现有值 10 和新值 5 合并为 15。然后,我们使用merge
方法将新键 "banana" 的值设置为 3,因为该键之前不存在。这个方法使得处理合并键值对变得更加方便,你可以根据需要定义如何合并现有值和新值。示例:
targets.computeIfAbsent(departureAirport, k -> new HashMap<>()) .merge(arrivalAirport, 1, Integer::sum)
对于整数类型的值
V
,如果在HashMap
中查找一个不存在的键,其默认值为0
。这是因为HashMap
的get
方法返回默认值,如果键不存在,而对于整数类型,其默认值就是0
。所以,如果你使用get
方法来获取不存在的键,将返回0
,而不会抛出空指针异常。这是 Java 中的约定。如果你需要不同的默认值,你可以使用getOrDefault
方法来明确指定默认值。这段代码是使用 `computeIfAbsent` 方法来初始化 `targets` 中的键值对,并使用 `merge` 方法来增加或更新到达机场的航班次数。具体解释如下:
1. `computeIfAbsent(departureAirport, k -> new HashMap<>())`:
- `computeIfAbsent` 方法用于在 `targets` 中获取或创建以 `departureAirport` 为键的 `Map` 对象。如果 `departureAirport` 不存在,它会使用 lambda 表达式 `k -> new HashMap<>()` 来创建一个新的空 `HashMap` 并将其放入 `targets` 中。如果已经存在,则直接获取该 `Map`。
- 这是为了确保每个出发机场都有一个对应的目标机场的映射,以存储航班次数信息。2. `.merge(arrivalAirport, 1, Integer::sum)`:
- 一旦获得或创建了 `departureAirport` 对应的 `Map`,就使用 `merge` 方法来增加到达机场 `arrivalAirport` 的航班次数。
- `arrivalAirport` 是要合并的键,`1` 是要合并的值(表示增加一次航班次数),`Integer::sum` 表示合并操作。它告诉 `HashMap` 如何合并现有值和新值,这里使用的是整数相加。所以,这两个方法的连续调用确保了在 `targets` 中为每个出发机场和到达机场创建了正确的映射关系,并增加了航班次数信息。这是在构建机票信息的过程中初始化和更新映射的关键步骤。
3.`Integer::sum` 是一个方法引用,它表示对 `Integer` 类中的静态方法 `sum` 的引用。在这种情况下,`sum` 方法是接受两个整数参数并返回它们的和。方法引用的语法通常不需要参数列表和表达式,因为它们从目标方法的签名中推断出来。
在 Java 中,方法引用可以用于替代 Lambda 表达式,用更简洁的方式来表示某个函数式接口的方法。`Integer::sum` 表示一个函数,它将两个整数作为参数并返回它们的和,这正好符合 `BiFunction<Integer, Integer, Integer>` 接口的方法签名,这是 `HashMap` 中 `merge` 方法所期望的方法签名。所以,`Integer::sum` 在这个上下文中非常适合作为合并操作。
51. N皇后
给原来的一维组合槽位拓展成了二维,排列组合的深度是一维槽位,n皇后深度是二维槽位
- 排列组合的深度是看组合的长度,宽度是看每个槽位上可选择的数据量
- n皇后的深度看棋盘的上下长度,宽度看棋盘的左右宽度
class Solution {
List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
char[][] chessBoard = new char[n][n];
for(char[] c : chessBoard){
Arrays.fill(c, '.');
}
backTracking(chessBoard, n, 0);
return res;
}
void backTracking(char[][] chessBoard, int n, int row){
if(row == n){
res.add(rowString(chessBoard));
return;
}
for(int col = 0; col < n; col++){
if(isValid(chessBoard, row, col, n)){
chessBoard[row][col] = 'Q';
backTracking(chessBoard, n, row + 1);
chessBoard[row][col] = '.';
}
}
return;
}
boolean isValid(char[][] chessBoard, int row, int col, int n){
for(int i = 0; i < row; i++){
if(chessBoard[i][col] == 'Q'){
return false;
}
}
for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++){
if(chessBoard[i][j] == 'Q'){
return false;
}
}
for(int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--){
if(chessBoard[i][j] == 'Q'){
return false;
}
}
return true;
}
List<String> rowString(char[][] chessBoard){
List<String> list = new ArrayList<>();
for(char[] c : chessBoard){
list.add(String.copyValueOf(c));
}
return list;
}
}
37. 解数独
- 在槽位的递归上是一维线性的,不过槽位的内容可换(用回溯换),槽位其实是深度;
- N皇后的槽位是按行递归下去,槽位的内容是选取该行的哪一个格子,深度在参数列表用row显示了出来
- 解数独的槽位是每一个格子,槽位的内容是9的数字,深度用双for和continue在函数内部顺序递归
递归结束的边界判断,当9行9列都填满后,就表示找到了,而不用给槽位里的内容做替换
class Solution {
public void solveSudoku(char[][] board) {
backTracking(board);
}
boolean backTracking(char[][] board){
for(int i = 0; i < board.length; i++){
for(int j = 0; j < board[0].length; j++){
if(board[i][j] == '.'){
for(char k = '1'; k <= '9'; k++){
if(isValid(board, i, j, k)){
board[i][j] = k;
if(backTracking(board)){
return true;
}
board[i][j] = '.';
}
}
return false;
}
}
}
return true;
}
boolean isValid(char[][] board, int row, int col, char k){
for(int j = 0; j < 9; j++){
if(board[row][j] == k){
return false;
}
}
for(int i = 0; i < 9; i++){
if(board[i][col] == k){
return false;
}
}
int startRow = (row / 3) * 3;
int startCol = (col / 3) * 3;
for(int i = startRow; i < startRow + 3; i++){
for(int j = startCol; j < startCol + 3; j++){
if(board[i][j] == k){
return false;
}
}
}
return true;
}
}