递归:
递归指函数调用自身。常用的递归算法有dfs(深度优先搜索)、记忆化搜索和分治。
一、dfs(深度优先搜索)
1.图的dfs
/**
* 深度优先搜索
*
* @param node
* @param set
*/
public void DFS(Node node, Set<Node> set) {
if (node == null) {
//当没有节点时,退出此次方法
return;
}
if (!set.contains(node)) {
//没有搜到过就保存,并且继续向下推进
set.add(node);
System.out.print(node.value + " ");
for (Node node1 : node.nexts) {
DFS(node1, set);
}
}
}
2.树的dfs
树(边数=顶点数-1的图)的dfs
public static void dfs(Node treeNode) {
if (treeNode == null) {
return;
}
System.out.print(treeNode.value + " ");
// 遍历左节点
dfs(treeNode.left);
// 遍历右节点
dfs(treeNode.right);
}
二、记忆化搜索
记忆化搜索,指通过记录已经搜索到的值来降低重复操作次数,从而加快运行速度。
三、分治
将问题拆分成子问题进行求解。
四、算法题
1.dia和威严 难度⭐⭐
输入描述:
第一行一个正整数n,代表学生会的人数(dia是1号,其他人标记为2号到n号)。 (1≤n≤200000) 第二行有n-1个正整数ai,代表从2号到n号,每个人需要理解信息的时间。 (1≤a1≤100) 接下来的n-1行,每行有3个正整数x,y,k,代表x是y的上级,x向y传播信息需要消耗k的时间。(1≤x,y≤n,1≤k≤100)
输出描述:
一个正整数,代表dia选定某人作为信息接收指定者后,花费总时间的最大值。
示例1:
import java.util.*;
import java.io.*;
public class Main{
static class Edge{
int to;
int weight;
//保存子节点,与线权(传播时间)。
Edge(int to,int weight){
this.to = to;
this.weight = weight;
}
}
static ArrayList<Edge>[] g;
static int maxtime;
static int weight[];
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine()); //n为总人数
weight = new int[n+1];
g = new ArrayList[n+1];
// 每个桶中保存一个ArrayList(可达)
//这里的i代表了第几个数(从1开始)
for(int i = 1;i<=n;i++){
g[i] = new ArrayList();
}
//保存所有点权(理解时间)纪录了从2到n的点权,与数字的位子相对应。
String[] s = br.readLine().split(" ");
for(int i = 0;i<n-1;i++){
weight[i+2] = Integer.parseInt(s[i]);
}
for(int i = 0;i<n-1;i++){
String[] s1 = br.readLine().split(" ");
int x = Integer.parseInt(s1[0]);
int y = Integer.parseInt(s1[1]);
int z = Integer.parseInt(s1[2]);
g[x].add(new Edge(y,z));
}
dfs(1,0);
System.out.println(maxtime);
}
static void dfs(int i ,int time){
if(maxtime<time+weight[i])maxtime = time+weight[i];
for(int k = 0;k<g[i].size();k++){
dfs(g[i].get(k).to,time+g[i].get(k).weight);
}
}
}
思考:???
2.小红点点点 难度⭐⭐
输入描述:
第一行输出两个正整数 n 和 m ,用空格隔开。分别代表顶点数和边数。 第二行输入一个长度为 n 的字符串,代表每个顶点的染色情况。第 i 个字符为'R'代表第 i 个点被染成红色,为'W'代表未被染色。 接下来的 m 行,每行两个正整数 x 和 y ,代表 x 和 y 有一条无向边相连。 不保证图是整体连通的。不保证没有重边和自环。 数据范围:
输出描述:
第一行输出一个正整数 cnt ,代表小红的点击次数。 第二行输出 cnt 个正整数,用空格隔开,代表小红点击的顺序。
示例1
import java.util.*;
import java.io.*;
public class Main {
static ArrayList<Integer>[] g;
static String[] strings;
static int[] visited;
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] firstLine = br.readLine().split(" ");
int n = Integer.parseInt(firstLine[0]);
int m = Integer.parseInt(firstLine[1]);
g = new ArrayList[n+1];
visited = new int[n+1];
for (int i=1;i<n+1;i++) {
g[i] = new ArrayList<Integer>();
}
//一个字符一个字符的读取
strings = br.readLine().split("");
for (int i=0;i<m;i++) {
//描绘双向图
String[] temp = br.readLine().split(" ");
int x = Integer.parseInt(temp[0]);
int y = Integer.parseInt(temp[1]);
g[x].add(y);
g[y].add(x);
}
int cnt = 0;
StringBuilder sb = new StringBuilder();
for (int i=1;i<n+1;i++) {
if (visited[i] ==0 && strings[i-1].equals("R")) {
cnt++;
sb.append(i + " ");
//从糖葫芦小的开始纪录,然后延深。
dfs(i);
}
}
System.out.println(cnt);
System.out.println(sb);
}
static void dfs(int x) {
if (visited[x] ==0 && strings[x-1].equals("R")) {
//点亮它
visited[x] = 1;
for (int i=0;i<g[x].size();i++) {
dfs(g[x].get(i));
}
}
}
}
3.kotori和素因子 难度⭐⭐⭐
输入描述:
第一行一个正整数n,代表kotori拿到正整数的个数。 第二行共有n个数ai,表示每个正整数的值。 保证不存在两个相等的正整数。 1<=n<=10 2<=ai<=1000
输出描述:
一个正整数,代表取出的素因子之和的最小值。若不存在合法的取法,则输出-1。
示例1
示例2
import java.util.*;
import java.io.*;
public class Main{
static boolean[] check = new boolean[2000];
static int[] ai;
static int n;
static int min = Integer.MAX_VALUE;
//判断是否为素数
static boolean isPrime(int x){
if(x<2) return false;
//这是根据开根号的演化版本,提高了效率。
for(int i=2;i*i<=x;i++){
if(x%i==0)return false;
}
return true;
}
static void dfs(int index,int sum){
if(index==n){
min=Math.min(min,sum);
return;
}
//查找这个数没有被占用的素因子。
for(int i=2;i<=ai[index];i++){
if(ai[index]%i==0&&check[i]==false&&isPrime(i)){
check[i]=true;
dfs(index+1,sum+i);
//回溯的方法。
check[i]=false;
}
}
}
public static void main(String[] args)throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
n = Integer.parseInt(br.readLine());
String[] a = br.readLine().split(" ");
//负责保存所有输入的数
ai = new int[n];
for(int i=0;i<n;i++){
ai[i]=Integer.parseInt(a[i]);
}
dfs(0,0);
System.out.println(min==Integer.MAX_VALUE ? -1:min);
br.close();
}
}
4.kotori和糖果 难度⭐⭐⭐⭐
输入描述:
第一行是一个正整数T,代表数据组数。 第二行到第T+1行,每行一个非负整数n,代表kotori的糖果总数量。
输出描述:
每行一个整数,代表kotori需要花费的最小代价。
示例1
备注:
对于50%的数据,0<T≤100000, 0≤n≤100000 对于另外50%的数据,T=1 , 0≤n≤1e18(这个数据范围是为了为了让你动态规划做不了,上面的范围可以做)
import java.util.*;
import java.io.*;
public class Main{
static HashMap<Long,Long> map = new HashMap<>();
public static void main (String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int num = Integer.parseInt(br.readLine());
for(int i = 0; i<num; i++){
System.out.println(recurse(Long.parseLong(br.readLine())));
}
}
static long recurse(long num){
if(num <= 1) return 0;
//用于保存
if(map.containsKey(num)) return map.get(num);
long pay = recurse(num/2) + recurse(num-num/2) +num%2;
map.put(num,pay);
return pay;
}
}