一、油车最远距离
每辆车每跑 1 公里消耗 1 升油,油箱容量 12 升;车与车之间可随时无损互相加油,被"弃置"的车不必返回。问最远能让一辆车开多远。
思路
让多辆车一起走,在合适的位置把其中一辆的油"喂"给其他车,使剩下的车都满箱,然后把这辆空车留在原地不再前进。
当有 m 辆车一起走时,总耗油是 m 升/公里。要凑到正好"淘汰"1 辆(即净消耗一个满箱 C升),应走公里。依次减少车数,直到只剩 1 辆独自前进。
当我们这C=12升、一共四辆车
-
4 车同行 12/4=3 公里,在 3 公里处把油分给其余三辆使其满箱,留下一辆空车停下。
-
3 车再走 12/3=41 公里,到 7 公里处再淘汰一辆。
-
2 车再走 12/2=6 公里,到 13 公里处再淘汰一辆。
-
剩下 1 车最后在13公里处再独走 12/1=12公里。
所以最后的公里数是25公里
这里有一个一般化结论

这种题应该不好用程序穷尽,让这些车能返回补油就是一个小学奥数题,没想到小学奥数题都是这种程度了吗?

二、基于父节点关联的扁平节点列表,构建树形结构并实现可视化打印
一个ArrayList<TreeNode>
,每个节点仅存储「自身 ID」和「父节点 ID」(无直接子节点列表),节点结构如下:
java
// 核心节点结构(仅含父子关联必要字段)
class TreeNode {
private String id; // 节点唯一标识
private String parentId; // 父节点ID(根节点为null)
private String name; // 节点名称(用于打印展示)
// 构造器、getter、toString略
}
列表长这样
java
List<TreeNode> nodes = new ArrayList<>();
nodes.add(new TreeNode("1", null, "技术部")); // 根节点:技术部(无父节点)
nodes.add(new TreeNode("2", "1", "前端组")); // 技术部的子节点:前端组
nodes.add(new TreeNode("3", "1", "后端组")); // 技术部的子节点:后端组
nodes.add(new TreeNode("4", "2", "PC前端组")); // 前端组的子节点:PC前端组
nodes.add(new TreeNode("5", "2", "移动端组")); // 前端组的子节点:移动端组
nodes.add(new TreeNode("6", "3", "Java组")); // 后端组的子节点:Java组
nodes.add(new TreeNode("7", "3", "大数据组")); // 后端组的子节点:大数据组
nodes.add(new TreeNode("8", null, "产品部")); // 根节点:产品部(无父节点)
nodes.add(new TreeNode("9", "8", "产品设计组")); // 产品部的子节点:产品设计组
要求输出入下
java
=== 部门层级(空格缩进) ===
技术部
前端组
PC前端组
移动端组
后端组
Java组
大数据组
产品部
产品设计组
思路
我最初的思路是 用一个map 把 每个节点的子节点存储起来,
- 键(Key):父节点 ID
- 值(Value):该父节点对应的所有子节点列表(
List<TreeNode>
)
然后用递归的方式也就是树的深度有限遍历打印
但是在做这个算法的时候时间有限,没手搓出来,感觉快了,但是看了AI给的方法,感觉用代码优雅的写出来还是很有难度的。
完整代码加注释
java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 树状结构打印工具类(空格缩进版)
* 功能:将扁平存储的节点列表(通过parentId关联父子关系)转换为树状结构,
* 并仅通过空格缩进的方式在控制台打印层级关系,不使用特殊符号
*/
public class TreeSpacePrinter {
/**
* 树节点实体类
* 存储节点的唯一标识、父节点标识及节点名称,用于构建树状结构
*/
static class TreeNode {
private String id; // 节点唯一ID(用于标识当前节点)
private String parentId; // 父节点ID(用于关联父节点,根节点为null)
private String name; // 节点名称(打印时展示的内容)
/**
* 构造方法:初始化节点的核心属性
* @param id 节点唯一标识
* @param parentId 父节点标识(根节点传null)
* @param name 节点展示名称
*/
public TreeNode(String id, String parentId, String name) {
this.id = id;
this.parentId = parentId;
this.name = name;
}
// Getter方法:供工具类获取节点属性
public String getId() { return id; }
public String getParentId() { return parentId; }
public String getName() { return name; }
/**
* 重写toString方法:打印节点时直接展示名称
* 避免默认打印对象地址,简化输出内容
*/
@Override
public String toString() { return name; }
}
/**
* 构建父节点到子节点的映射关系
* 作用:将扁平的节点列表转换为"父节点ID→子节点列表"的映射,
* 便于快速查找某个节点的所有子节点,避免多次遍历列表
* @param nodeList 原始扁平节点列表
* @return 父节点ID为键,对应子节点列表为值的Map
*/
private static Map<String, List<TreeNode>> buildTreeMap(List<TreeNode> nodeList) {
// 初始化Map:键为父节点ID,值为该父节点下的所有子节点
Map<String, List<TreeNode>> treeMap = new HashMap<>();
// 遍历所有节点,将节点添加到其父节点对应的子列表中
for (TreeNode node : nodeList) {
// computeIfAbsent:若父节点ID对应的列表不存在,则创建空列表;存在则直接获取
// 然后将当前节点添加到该列表中,完成父子关联
treeMap.computeIfAbsent(node.getParentId(), k -> new ArrayList<>()).add(node);
}
return treeMap;
}
/**
* 查找所有根节点
* 根节点定义:父节点ID为null,或在节点列表中不存在对应父节点的节点
* @param nodeList 原始扁平节点列表
* @return 所有根节点组成的列表
*/
private static List<TreeNode> findRootNodes(List<TreeNode> nodeList) {
List<TreeNode> rootNodes = new ArrayList<>();
// 提取所有节点的ID到列表,用于快速判断父节点是否存在
List<String> allNodeIds = nodeList.stream().map(TreeNode::getId).toList();
// 遍历节点,筛选出根节点
for (TreeNode node : nodeList) {
String parentId = node.getParentId();
// 根节点条件:父节点ID为null,或父节点ID不在所有节点ID中(父节点不存在)
if (parentId == null || !allNodeIds.contains(parentId)) {
rootNodes.add(node);
}
}
return rootNodes;
}
/**
* 递归打印节点及其子节点(核心打印方法)
* 通过控制空格缩进量体现节点层级,每层缩进固定数量的空格
* @param node 当前需要打印的节点
* @param treeMap 父节点到子节点的映射(用于获取当前节点的子节点)
* @param depth 当前节点的层级深度(根节点为0,子节点深度+1)
*/
private static void printNode(TreeNode node, Map<String, List<TreeNode>> treeMap, int depth) {
// 计算缩进字符串:每层缩进4个空格(可根据需要修改空格数量调整缩进幅度)
String indent = " ".repeat(depth);
// 打印当前节点:缩进+节点名称(缩进量由深度决定,体现层级)
System.out.println(indent + node);
// 获取当前节点的所有子节点(从映射中根据节点ID查询)
List<TreeNode> children = treeMap.get(node.getId());
// 若没有子节点,直接返回(递归终止条件)
if (children == null || children.isEmpty()) {
return;
}
// 遍历所有子节点,递归打印(子节点深度=当前节点深度+1)
for (TreeNode child : children) {
printNode(child, treeMap, depth + 1);
}
}
/**
* 对外暴露的树状打印入口方法
* 封装了树构建、根节点查找和打印的完整流程
* @param nodeList 待打印的扁平节点列表
*/
public static void printTree(List<TreeNode> nodeList) {
// 边界处理:若节点列表为空,直接提示
if (nodeList == null || nodeList.isEmpty()) {
System.out.println("节点列表为空,无需打印");
return;
}
// 1. 构建父节点到子节点的映射
Map<String, List<TreeNode>> treeMap = buildTreeMap(nodeList);
// 2. 查找所有根节点
List<TreeNode> rootNodes = findRootNodes(nodeList);
// 3. 从根节点开始打印(根节点深度为0)
for (TreeNode root : rootNodes) {
printNode(root, treeMap, 0);
}
}
/**
* 测试方法:创建示例节点列表并验证打印效果
* 模拟实际业务中的层级数据(如部门结构、菜单列表等)
*/
public static void main(String[] args) {
// 1. 构建测试用的扁平节点列表(模拟从数据库查询的结果)
List<TreeNode> nodes = new ArrayList<>();
nodes.add(new TreeNode("1", null, "技术部")); // 根节点:技术部(无父节点)
nodes.add(new TreeNode("2", "1", "前端组")); // 技术部的子节点:前端组
nodes.add(new TreeNode("3", "1", "后端组")); // 技术部的子节点:后端组
nodes.add(new TreeNode("4", "2", "PC前端组")); // 前端组的子节点:PC前端组
nodes.add(new TreeNode("5", "2", "移动端组")); // 前端组的子节点:移动端组
nodes.add(new TreeNode("6", "3", "Java组")); // 后端组的子节点:Java组
nodes.add(new TreeNode("7", "3", "大数据组")); // 后端组的子节点:大数据组
nodes.add(new TreeNode("8", null, "产品部")); // 根节点:产品部(无父节点)
nodes.add(new TreeNode("9", "8", "产品设计组")); // 产品部的子节点:产品设计组
// 2. 打印提示信息并执行树状打印
System.out.println("=== 部门层级结构(空格缩进展示) ===");
printTree(nodes);
}
}