【蓝桥杯】学习算法


1 Java 集合框架

1.1 链表

LinkedList<Integer> linkedList = new LinkedList<>();
        linkedList.add(234); // 链表末尾添加元素,返回是否成功,成功为 true,失败为 false
        linkedList.add(1,456); // 向指定位置插入元素
        linkedList.addFirst(1); // 元素添加到头部
        linkedList.remove(1); // 删除指定位置的元素
        linkedList.remove(); // 删除链表首元素
        linkedList.addLast(2); // 元素添加到尾部
        if (linkedList.contains(234)) {
            // 判断是否含有某一元素
        }
        linkedList.get(1); // 返回指定位置的元素,还有 getFirst getLast
        linkedList.indexOf(234); // 查找指定元素从前往后第一次出现的索引
        linkedList.lastIndexOf(234); // 查找指定元素最后一次出现的索引

        linkedList.set(1,234); // 设置指定位置的元素

        Object[] a = linkedList.toArray(); // 返回一个由链表元素组成的数组
        linkedList.clear(); // 清空链表

1.2 队列

1.3 栈

1.4 HashMap

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射

HashMap<Integer,Character> hashMap = new HashMap<>();
        hashMap.put(1,'A'); // 将键/值对添加到 hashMap 中
        hashMap.putIfAbsent(1,'B'); // 如果 hashMap 中不存在指定的键,则将指定的键/值对插入到 hashMap 中
        hashMap.remove(1); // 删除 hashMap 中指定键 key 的映射关系

        if (hashMap.containsKey(1)) {
            // 检查 hashMap 中是否存在指定的 key 对应的映射关系
            if (hashMap.containsValue('B')) {
                // 检查 hashMap 中是否存在指定的 value 对应的映射关系
            }
        }

        hashMap.replace(1,'C'); // 替换 hashMap 中是指定的 key 对应的 value
        hashMap.get(1); // 获取指定 key 对应对 value
        hashMap.clear(); // 删除 hashMap 中的所有键/值对

1.5 HashSet

基于 HashMap 实现,是一个不允许有重复元素的集合

HashSet<Integer> hashSet = new HashSet<>();
        hashSet.add(234); // 如果指定的元素尚不存在,则将其添加到此集合中
        if (hashSet.contains(234)) {
            // 如果包含指定的元素,则返回 true
        }
        hashSet.remove(234); // 如果存在,则从该集合中移除指定的元素

        hashSet.clear(); // 从该集中删除所有元素

2 字符串处理

2.1 String类

String s = new String("hello");
        // 注意 String 不可修改,所以以下方法都是有【返回】值
        s.charAt(0); // 返回指定索引处的 char 值
        s.compareTo("Hello"); // 按字典顺序比较,大于字符串参数,则返回正数
        s.compareToIgnoreCase("Hello"); // (忽略大小写)按字典顺序比较,大于字符串参数,则返回正数

        s.concat("world"); // 将指定字符串连接到此字符串的结尾

        if (s.equals("hello")) {
            // 如果给定对象与字符串相等,则返回 true;否则返回 false
        }
        s.indexOf("h"); // 返回指定子字符串在此字符串中第一次出现处的索引
        s.lastIndexOf("h"); // 返回指定字符在此字符串中最后一次出现处的索引

        s.split("regex"); // 根据给定正则表达式的匹配拆分此字符串

        s.substring(1); // 返回一个新的字符串,它是此字符串的一个子字符串
        s.substring(1,2); // 返回一个子字符串,包含起始索引,不包含末尾索引

        s.toCharArray(); // 将此字符串转换为一个新的字符数组

        s.toLowerCase(); // 转小写

        if (s.contains("llo")) {
            // 判断是否包含指定的字符系列
        }

2.2 StringBuilder类

// 注意要初始化
        StringBuilder stringBuilder = new StringBuilder("hello");
        // String 的方法 StringBuilder 都可以用
        stringBuilder.append("world"); // 将指定的字符串追加到此字符序列
        stringBuilder.reverse(); // 字符串反转
        stringBuilder.delete(1,2); // 移除此序列的子字符串中的字符
        stringBuilder.insert(1,"abc"); // 插入
        stringBuilder.replace(1,3,"zz"); // 使用给定 String 中的字符替换此序列的子字符串中的字符

大数

import java.math.BigInteger;
import java.util.Scanner; 
public class Main {     
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        BigInteger a,b;
        a = sc.nextBigInteger();   
        b = sc.nextBigInteger(); 
        System.out.println(a.add(b));       // 加
        System.out.println(a.subtract(b));  // 减
        System.out.println(a.multiply(b));  // 乘
        System.out.println(a.divide(b));    // 除
        System.out.println(a.mod(b));       // 取余
    }                     
}

4 算法

4.1 打表

打表是一种典型的用空间换时间的技巧,一般指将所有可能需要用到的结果事先计算出来,这样后面需要用到时就可以直接查表获得。打表常见的用法有如下几种:

1、在程序中一次性计算出所有需要用到的结果,之后的查询直接取这些结果

  这个是最常用到的用法,例如在一个需要查询大量Fibonacci数F(n)的问题中,显然每次从头开始计算是非常耗时的,对Q次查询会产生O(nQ)的时间复杂度;而如果进行预处理,即把所有Fibonacci数预先计算并存在数组中,那么每次查询就只需O(1)的时间复杂度,对Q次查询就值需要O(n+Q)的时间复杂度(其中O(n)是预处理的时间)。

2、在程序B中分一次或多次计算出所有需要用到的结果,手工把结果写在程序A的数组中,然后在程序A中就可以直接使用这些结果

  这种用法一般是当程序的一部分过程小号的时间过多,或是没有想到好的算法,因此在另一个程序中使用暴力算法算出结果,这样就能直接在源程序中使用这些结果。例如对n皇后问题来说,如果使用的算法不够好,就容易超时,而可以在本地用程序计算付出对所有n来说n皇后问题的方案数,然后把算出的结果直接卸载数组中,就可以根据题目输入的n来直接输出结果。

3、对一些感觉不会做的题目,先用暴力程序计算小范围数据的结果,然后找规律,或许就能发现一些“蛛丝马迹”

  这种用法在数据范围非常大时候容易用到,因为这样的题目可能不是用直接能想到的算法来解决的,而需要寻找一些规律才能得到结果。

4.2 枚举

1,枚举算法的定义:
在进行归纳推理时,如果逐个考察了某类事件的所有可能情况,因而得出一般结论,那么该结论是可靠 的,这种归纳方法叫做枚举法。
2,枚举算法的思想是:
将问题的所有可能的答案一一列举,然后根据条件判断此答案是否合适,保留合适的,舍弃不合适的。
3,使用枚举算法解题的基本思路如下:
(1)确定枚举对象、范围和判定条件。
(2)逐一枚举可能的解并验证每个解是否是问题的解。
4,枚举算法步骤:
(1)确定解题的可能范围,不能遗漏任何一个真正解,同时避免重复。
(2)判定是否是真正解的方法。
(3)为了提高解决问题的效率,使可能解的范围将至最小,
5,枚举算法的流程图如下所示:
       

4.3 倍增

4.4 离散化

用 HashMap 

4.5 前缀和

首先了解“前缀和”的概念。一个长度为n的数组a[1] ~ a[n],前缀和sum[i]等于a[1] ~ a[i]的和:
    sum[i] = a[1] + a[2] + … + a[i]
  利用递推,可以在O(n)时间内求得所有前缀和:
    sum[i] = sum[i-1] + a[i]
  如果预计算出前缀和,就能利用它快速计算出数组中任意一个区间a[i] ~ a[j]的和。即:
    a[i] + a[i+1] + … + a[j-1] + a[j] = sum[j] - sum[i-1]
  上式说明,复杂度为O(n)的区间求和计算,优化到了O(1)的前缀和计算。

(LeetCode)一维数组的动态和

class Solution {
    public int[] runningSum(int[] nums) {
        int[] a = new int[nums.length];
        for (int i = 1; i <= nums.length; i++) {
            a[i - 1] = recursionSum(nums, i);
        }
        return a;
    }

    public static int recursionSum(int[] nums, int k) {
        if (k == 1) {
            return nums[0];
        }
        return recursionSum(nums, k - 1) + nums[k - 1];
    }
}

时间复杂度极高

(蓝桥杯)求和

import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        //在此输入您的代码...
        int n = sc.nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = sc.nextInt();
        }


        // 暴力 -> 30%用例
//        int sum = 0;
//        for (int i = 0; i < n; i++) {
//            for (int j = i + 1; j < n; j++) {
//                sum += a[i] * a[j];
//            }
//        }

        // 优化 -> 30%用例(实际上没有优化,sum要计算要遍历一遍数组,还是O(n^2))
//        int aSum = 0;
//        for (int i = 0; i < n; i++) {
//            aSum += a[i];
//        }
//        int sum = 0;
//        for (int i = 0; i < n; i++) {
//            sum += (aSum-a[i])*a[i];
//            aSum-=a[i];
//        }

        // 前缀和优化 -> 100%AC
        long[] prefixSum = new long[n];
        long sum = 0; //非常要注意【范围】!!!!!!!!!!!!!!!!!!!
        prefixSum[0] = a[0];
        for (int i = 1; i < n; i++) {
            prefixSum[i] = prefixSum[i - 1] + a[i];

        }
        for (int i = 1; i < n; i++) {
            sum +=  a[i] * prefixSum[i - 1];
        }
        System.out.println(sum);
        sc.close();
    }

}

4.6 差分

前缀和的主要应用是差分:差分是前缀和的逆运算

与一维数组a[]对应的差分数组d[]的定义:
    d[k]=a[k]-a[k-1]
即原数组a[]的相邻元素的差。根据d[]的定义,可以推出:
    a[k]=d[1]+d[2]+…+d[k]

把区间[L, R]内每个元素a[]加上v,只需要把对应的d[]做以下操作:
  (1)把d[L]加上v: d[L] += v
  (2)把d[R+1]减去v:d[R+1] -= v

还原的时候这样还原:

cnt[i] = cnt[i-1]+d[i];

【例题】:重新排序

import java.util.Arrays;
import java.util.Scanner;

public class 重新排序 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        // 索引从1到n
        int[] cnt = new int[n + 1];
        int[] a = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            a[i] = sc.nextInt();
        }
        int m = sc.nextInt();
        int beforeSum = 0;
        for (int i = 0; i < m; i++) {
            int p = sc.nextInt();
            int q = sc.nextInt();
            for (int j = p; j <= q; j++) {
                cnt[j]++;
                beforeSum += a[j];
            }
        }

        Arrays.sort(a);
        Arrays.sort(cnt);
        int afterSum = 0;
        for (int i = n; i >= 1; i--) {
            afterSum += a[i] * cnt[i];
        }

        System.out.println(afterSum - beforeSum);
    }
}

(暴力法)引入一个cnt数组,存放每个数字的被访问次数,然后排序cnt和a,从后面做乘法

通过40%用例

【优化】:

使用差分数组

import java.util.Arrays;
import java.util.Scanner;

public class 重新排序 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        // 索引从1到n
        int[] cnt = new int[n + 2];
        int[] a = new int[n + 2];
        // 引入差分数组d[]
        int[] d = new int[n + 2];
        for (int i = 1; i <= n; i++) {
            a[i] = sc.nextInt();
        }
        int m = sc.nextInt();
        long beforeSum = 0;
        for (int i = 0; i < m; i++) {
            int p = sc.nextInt();
            int q = sc.nextInt();

            d[p]++;
            d[q+1]--;
        }
        cnt[0] = d[0];
        for (int i = 1; i <=n ; i++) {
            cnt[i] = cnt[i-1]+d[i]; // 先拿到cnt
        }
        for (int i = 1; i <=n ; i++) {
            beforeSum += (long)a[i]*cnt[i];
        }
        Arrays.sort(a,1,n+1); // 关键在于排序
        Arrays.sort(cnt,1,n+1);
        long afterSum = 0;
        for (int i = 1; i<=n; i++) {
            afterSum += (long)a[i] * cnt[i];
        }

        System.out.println(afterSum-beforeSum);
    }
}

4.7 尺取

尺取法:顾名思义,像尺子一样取一段

尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。

尺取法比直接暴力枚举区间效率高很多,尤其是数据量大的时候,所以说尺取法是一种高效的枚举区间的方法,是一种技巧,一般用于求取有一定限制的区间个数或最短的区间等等。当然任何技巧都存在其不足的地方,有些情况下尺取法不可行,无法得出正确答案,所以要先判断是否可以使用尺取法再进行计算。

4.8 分治

【分而治之】把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并

分治法在每一层递归上都有三个步骤:

    step1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;

    step2 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题

    step3 合并:将各个子问题的解合并为原问题的解。

它的一般的算法设计模式如下:

    Divide-and-Conquer(P)

    1. if |P|≤n0

    2. then return(ADHOC(P))

    3. 将P分解为较小的子问题 P1 ,P2 ,...,Pk

    4. for i←1 to k

    5. do yi ← Divide-and-Conquer(Pi) △ 递归解决Pi

    6. T ← MERGE(y1,y2,...,yk) △ 合并子问题

    7. return(T)

4.9 贪心

【贪心】(Greedy):把整个问题分解成多个步骤,在每个步骤,都选取当前步骤的最优方案,直到所有步骤结束;在每一步,都不考虑对后续步骤的影响,在后续步骤中也不能回头改变前面的选择

总结活动安排问题的贪心策略:先按活动的结束时间(区间右端点)排序,然后每次选结束最早的活动,并保证选择的活动不重叠

P1803 凌乱的yyy / 线段覆盖

import java.util.Scanner;

public class P1803凌乱的yyy_线段覆盖 {
    static Scanner sc = new Scanner(System.in);
    static int n = sc.nextInt();
    static int[][] a = new int[n][2];
    static int res = 0;

    public static void DFS(int time, int cnt) {
        for (int i = 0; i < n; i++) {
            if (a[i][0] >= time) {
                DFS(a[i][1], cnt + 1);
            }
        }
        res = Math.max(res, cnt);
    }

    public static void main(String[] args) {
        for (int i = 0; i < n; i++) {
            a[i][0] = sc.nextInt();
            a[i][1] = sc.nextInt();
        }
        int time = 0;
        DFS(0, 0);
        System.out.println(res);
    }
}

先尝试用dfs写,只能通过两个用例,其他全超时

【优化】注意这段代码的书写!!!!!

import java.util.*;
class Main {
    static class Data { int L, R; }        // 开始时间、结束时间
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        Data[] a = new Data[n];
        for (int i = 0; i < n; i++) {
            a[i] = new Data();
            a[i].L = scanner.nextInt();
            a[i].R = scanner.nextInt();
        }
        Arrays.sort(a, new Comparator<Data>() {
            public int compare(Data x, Data y) {
                return x.R - y.R;
            }
        });
        int ans = 0;
        int lastend = -1;
        for (int i = 0; i < n; i++) 
            if (a[i].L >= lastend) {
                ans++;
                lastend = a[i].R;
            }
        
        System.out.println(ans);
    }
}

4.10 二分

二分法把长度为n的有序序列上O(n)的查找时间,优化到了O(logn)。

  注意二分法的应用条件是:序列是单调有序的,从小到大,或从大到小。在无序的序列上无法二分,如果是乱序的,应该先排序再二分。
  如果在乱序序列上只搜一次,不需要用二分法。如果用二分,需要先排序,排序复杂度O(nlogn),再二分是O(logn),排序加二分的总复杂度O(nlogn)。如果用暴力法直接在乱序的n个数里面找,复杂度是O(n)的,比排序加二分快。
  但是如果不是搜一个数,而是搜m个数。那么先排序再做m次二分的计算复杂度是O(nlogn+mlogn),而暴力法是O(mn)的,当m很大时,二分法远好于暴力法。
  做二分法题目时,需要建模出一个有序的序列,并且答案在这个序列中。编程时,根据题目要求确定区间[L, R]范围,并写一个check()函数来更新L和R

模板

while (L < R){                      //一直二分,直到区间[L,R]缩小到L=R
    int mid = (L + R) / 2;          //mid是L、R的中间值
    if (check(mid))  R = mid;       //答案在左半部分[L,mid],更新R=mid
    else             L = mid + 1;   //答案在右半部分[mid+1, R],更新L=mid+1
}

public static int binarySearch(int[] a, int n) {
        int left = 0;
        int right = a.length;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (a[mid] == n) {
                return mid;
            } else if (a[mid] > n) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return -1;
    }

 

import java.util.Arrays;
import java.util.Scanner;

public class P1873_EKO砍树 {
    static int[] a;
    static int m;

    public static boolean check(int high) {
        int sum = 0;
        for (int i = 0; i < a.length; i++) {
            if (high < a[i]) {
                sum += a[i] - high;
            }
        }
        return sum >= m;
    }

    public static int bs(int l, int r) {
        int res = -1;
        while (l <= r) {
            int mid = l + (r - l) / 2;
            if (check(mid)) {
                res = mid;
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        return res;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        m = sc.nextInt();
        a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = sc.nextInt();
        }
        Arrays.sort(a);
        System.out.println(bs(a[0], a[a.length - 1]));
    }
}

4.11 快速幂

import java.util.Scanner;

public class P1226_模板快速幂_Long {
    static long p;

    public static long fastPow(long a, long n) { // a^n
        // 注意【步步取模】!!!!
        if (n == 0) {
            return 1;
        } else if (n % 2 == 1) { // 奇数,降一次幂
            return fastPow(a, n - 1) * a % p;
        } else { // 偶数,55分治
            long t = fastPow(a, n / 2) % p;
            return t * t % p;
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        long a = sc.nextLong();
        long b = sc.nextLong();
        p = sc.nextLong();
        long res = fastPow(a, b);
        res %= p;
        System.out.println(a + "^" + b + " mod " + p + "=" + res);
    }
}

6 DFS与DP

模板

ans;                              //答案,常常用全局变量表示
void dfs(层数,其他参数){
    if (到达目的地、或者出局){    //到达最底层,或者满足条件退出 
        更新答案ans;              //答案一般用全局变量表示,ans是最优解
        return;                   //递归返回,即返回到上一层
    }
    (剪枝)                        //在进一步DFS之前剪枝
    for (用i遍历下一层所有可能的情况)    //对每一个情况继续DFS 
        if (used[i] == 0) {        //如果状态i没有处理过,就可以进入下一层dfs
            used[i] = 1;           //标记状态i为已经使用,在后续dfs时不能再使用
            dfs(层数+1,其他参数);      //下一层,即搜小规模后继续dfs
            used[i] = 0;           //恢复状态i,回溯时,不影响上一层对这个状态的使用
        }
    return;                        //返回到上一层
}

dfs例题

P1048 [NOIP2005 普及组] 采药:

import java.util.*;

public class 采藥 {
	static int[][] a = new int[10005][2];
	static boolean[] vis = new boolean[10005];
	static int t;
	static int m;
	static int res=0;
	public static void dfs(int cnt,int time,int value) {
		
		if (time>t) {
			return;
		}
		if (cnt == m) {
			res = Math.max(res, value);
			return;
		}
		
		for (int i = 0; i < m; i++) {
			if (a[i][0]>t) {
				continue;
			}
			if (!vis[i]) {
				vis[i] = true;
				dfs(cnt+1, time, value); // 不采
				dfs(cnt+1, time+a[i][0], value+a[i][1]); // 采
				vis[i] = false;
			}
		}
	}
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		t = sc.nextInt();
		m = sc.nextInt();
		for (int i = 0; i < m; i++) {
			a[i][0] = sc.nextInt();
			a[i][1] = sc.nextInt();
		}
		dfs(0, 0, 0);
		System.out.print(res);
	}
}

先用dfs,只能通过两个用例

考虑用dp:

import java.util.*;

public class 采藥 {
	static int[][] a = new int[1050][2];
	static int[][] dp = new int[1050][1050];
	// 注意dp[i][j]的含義:前i個物品,容量限制為j,所取得的最大價值
	static int t;
	static int m;
	static int res=0;
	
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		t = sc.nextInt();
		m = sc.nextInt();
		for (int i = 1; i <= m; i++) {
			a[i][0] = sc.nextInt();
			a[i][1] = sc.nextInt();
		}
		for (int i = 1; i <= m; i++) {
			for (int j = 1; j <=t; j++) {
				if (j>=a[i][0]) { // 放得下
					dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-a[i][0]] + a[i][1]);
				}else {
					dp[i][j] = dp[i-1][j];
				}
			}
		}
		
		System.out.print(dp[m][t]);
	}
}

另:完全背包的改动:

01背包:

dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-a[i][0]] + a[i][1]);

完全背包:

dp[i][j] = Math.max(dp[i-1][j], dp[i][j-a[i][0]] + a[i][1]);

7 数论

素数

static boolean isPrime(int s) {
        if (s <= 1)       return false;
        for (int i = 2; i <= Math.sqrt(s); i++) 
            if (s % i == 0)   return false;        
        return true;
    }

最大公约数GCD

public static long gcd(long a, long b) {
        if (b == 0)   return a;        
        return gcd(b, a % b);
    }

最小公倍数

public static long lcm(int a, int b) {
        return (long) a / gcd(a, b) * b;
    }

素数

public static boolean isPrime(int n) {
        for (int i = 2; i <= Math.sqrt(n); i++) {
            if (n % i == 0)
                return false;
        }
        return true;
    }

线性筛

public static List<Integer> generatePrimes(int n) {
        boolean[] isPrime = new boolean[n + 1];
        List<Integer> primes = new ArrayList<>();
        Arrays.fill(isPrime, true); // 一开始认为所有的数都是质数
        for (int i = 2; i <= n; i++) {
            if (isPrime[i]) { // 从2到n,如果i是质数,就加入到质数表
                primes.add(i);
            }
            for (int j = 0; j < primes.size() && i * primes.get(j) <= n; j++) {
                isPrime[i * primes.get(j)] = false; // 标记非质数
                if (i % primes.get(j) == 0) {
                    break;
                }
            }
        }
        return primes;
    }

8 数据结构

import java.util.*;

public class P1125_NOIP2008提高组_笨小猴 {
    public static boolean isPrime(int n) {
        if (n == 1) {
            return false;
        }
        for (int i = 2; i <= Math.sqrt(n); i++) {
            if (n % i == 0)
                return false;
        }
        return true;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        HashMap<Character, Integer> hm = new HashMap<>(); // 哈希表的查找是 O(1),字母本身是不重复,设置为key
                                                          // 字母出现的次数不停加1,设为value
        String s = sc.nextLine();
        class WordList { // 不能在哈希表中排序,自定义单词表类,方便之后对 count 的排序
            char word;
            int count;
        }
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (hm.containsKey(c)) {
                hm.put(c, hm.get(c) + 1); // 哈希表重新设置value
            } else {
                hm.put(c, 1);
            }
        }
        WordList[] w = new WordList[hm.size()];
        Iterator<Map.Entry<Character, Integer>> entries = hm.entrySet().iterator(); // 迭代器放在循环外面
        for (int i = 0; i < hm.size(); i++) {
            w[i] = new WordList(); // 自定义类的每次使用都要初始化
            if (entries.hasNext()) {
                Map.Entry<Character, Integer> entry = entries.next(); // Map.Entry是一个键值对,entries是键值对的集合
                w[i].word = entry.getKey();
                w[i].count = entry.getValue();
            }
        }

        Arrays.sort(w, new Comparator<WordList>() {
            @Override
            public int compare(WordList o1, WordList o2) {
                return o2.count - o1.count; // 比较器 对 count 字段按降序排序
            }
        });

        int div = w[0].count - w[w.length - 1].count;
        if (div == 0) {
            System.out.println("No Answer");
            System.out.println(div);
        } else {
            if (isPrime(div)) {
                System.out.println("Lucky Word");
                System.out.println(div);
            } else {
                System.out.println("No Answer");
                System.out.println(0);
            }
        }
    }
}

9 日期

DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
LocalDate start = LocalDate.parse(sStart,dateTimeFormatter);

2 记忆化搜索(DFS+DP)

2.1 滑行

import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改

public class Main {
    static Scanner scan = new Scanner(System.in);
    static int n = scan.nextInt();
    static int m = scan.nextInt();
    static int[][] map = new int[n][m];
    static int[][] dp = new int[n][m];
    static int[][] dir = {
            {1, 0},
            {-1, 0},
            {0, 1},
            {0, -1}
    };
    public static void main(String[] args) {

        int max = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                map[i][j] = scan.nextInt();
            }
        }
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                int temp = DFS(i, j);
                max = Math.max(max, temp);
            }
        }
        System.out.println(max);
        scan.close();
    }

    public static int DFS(int x, int y) {
        if (dp[x][y] > 0) {
            return dp[x][y];
        }
        dp[x][y] = 1;

        for (int i = 0; i < dir.length; i++) {
            int dx = x + dir[i][0];
            int dy = y + dir[i][1];

            if (dx >= 0 && dx < n && dy >= 0 && dy < m) {
                if (map[x][y] < map[dx][dy]) {
                    int temp = DFS(dx, dy);
                    dp[x][y] = Math.max(dp[x][y], temp + 1);
                }
            }
        }
        return dp[x][y];
    }
}

DFS只能通过一些用例,需要进行剪枝,就使用dp来存储已经遍历过的节点的滑行距离

3 子矩阵遍历

4 DFS

4.1 yes or no

迷宫

迷宫dfs(T是终点)

public class Maze_DFS {
    static String[] map = {
            "S....",
            ".XXX.",
            ".X...",
            "..XXX",
            "....T",
    };
    static boolean[][] visited = new boolean[10][10];
    static int[][] dir = {
            {1, 0},
            {-1, 0},
            {0, 1},
            {0, -1}
    };

    public static boolean DFS(int x, int y) {
        visited[x][y] = true;
        if (map[x].charAt(y) == 'T') {
            System.out.println("successful");
            return true;
        }
        for (int i = 0; i < dir.length; i++) {
            int dx = x + dir[i][0];
            int dy = y + dir[i][1];
            if (dx >= 0 && dx < 5 && dy >= 0 && dy < 5 && map[dx].charAt(dy) != 'X' && !visited[dx][dy]) {
                if (DFS(dx, dy)) {
                    System.out.println("( " + dx +", "+dy+")");
                    return true;
                }
            }
        }
        // 回溯
        visited[x][y] = false;
        return false;
    }

    public static void main(String[] args) {
        DFS(0, 0);
    }
}

【回溯】:把当前失败的节点标记为 false ,同时返回false,这样这个节点以后不会再去重复遍历,一步一步退回去

象棋

注意马的走法(八向)

import java.util.Scanner;

public class xiangqi_DFS {
    static String[] map = new String[15];
    static boolean[][] vis = new boolean[15][15];
    static int[][] dir = {
            {-2, -1},
            {-2, 1},
            {-1, -2},
            {-1, 2},
            {1, 2},
            {2, 1},
            {2, -1},
            {1, -2},
    };

    public static boolean DFS(int x, int y) {
        if (map[x].charAt(y) == 'T') {
            return true;
        }
        vis[x][y] = true;
        for (int i = 0; i < 8; i++) {
            int dx = x + dir[i][0];
            int dy = y + dir[i][1];
            if (dx >= 0 && dx < 10 && dy >= 0 && dy < 10 && map[dx].charAt(dy) != '#' && !vis[dx][dy]) {
                if (DFS(dx, dy)) {
                    return true;
                }
            }
        }
        vis[x][y] = false;
        return false;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        for (int i = 0; i < 10; i++) {
            map[i] = sc.nextLine();
        }

        int x = 0, y = 0;
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (map[i].charAt(y) == 'S') {
                    x = i;
                    y = j;
                    break;
                }
            }
        }
        if (DFS(x, y)) {
            System.out.println("Yes");
        } else {
            System.out.println("No");
        }
    }
}

和上一个问题【迷宫】很相似

4.2 how many steps

现在计算【象棋】共走了多少步

部分代码:

static int ans = 10000;
public static boolean DFS(int x, int y, int step) {
        if (map[x].charAt(y) == 'T') {
            if (step<ans){
                ans = step;
            }
            return true;
        }
        vis[x][y] = true;
        for (int i = 0; i < 8; i++) {
            int dx = x + dir[i][0];
            int dy = y + dir[i][1];
            if (dx >= 0 && dx < 10 && dy >= 0 && dy < 10 && map[dx].charAt(dy) != '#' && !vis[dx][dy]) {
                if (DFS(dx, dy,step+1)) {
                    //step++  不要这么写!!!错误写法
                    return true;
                }
            }
        }
        vis[x][y] = false;
        return false;
    }

public static void main(String[] args) {
        DFS(x,y,0);
        System.out.println(ans + "步");
    }

注意的是,不要在DFS递归中对step进行加减,要在递归调用DFS的时候,带上参数step

还有声明一个全局变量 ans,初始化一个大数,这样取全局最小步数,最后在主函数输出 ans

4.3 抽象DFS

4.3.1 选数

问题:给定n个数,要从中选择k个数,使得K个数的总和为sum

建立搜索树:S为当前选择数的总和,k为选择的数的个数

时间复杂度:O(2^k)

import java.util.Scanner;

public class ChooseNumsToSum_DFS {
    /*
    n个数,选k个,凑成sum
    用例:

    输入  5 3 9
         1 2 3 4 5
    输出  2
     */
    static int n;
    static int sum;
    static int k;
    static int ans = 0;
    static int[] a = new int[100];

    public static void DFS(int i, int cnt, int s) {
        // i是当前的数 cnt是已经选择的数的个数 s是当前数的总和
        if (i == n) {
            if (cnt == k && s == sum) {
                ans++;
            }
            return;
        }
        DFS(i + 1, cnt, s); // 一种是不选择这个数,i+1之间调用
        DFS(i + 1, cnt + 1, s + a[i]); // 另一种是选择这个数,cnt+1,s加上这个数
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        k = sc.nextInt();
        sum = sc.nextInt();
        for (int i = 0; i < n; i++) {
            a[i] = sc.nextInt();
        }
        DFS(0, 0, 0);
        System.out.println(ans);
    }

}

4.4 剪枝

可行性剪枝:

当前数的和s大于目标sum,或者当前选的数个数大于目标k,都直接return而不接着往下

public static void DFS(int i, int cnt, int s) {
        if (s > sum || cnt > k) {
            return;
        }
        // i是当前的数 cnt是已经选择的数的个数 s是当前数的总和
        if (i == n) {
            if (cnt == k && s == sum) {
                ans++;
            }
            return;
        }
        DFS(i + 1, cnt, s);
        DFS(i + 1, cnt + 1, s + a[i]);
    }

举个例子--->去重剪枝

需要去重,注意是 i+1

public static void chooseThreeNumsPermutationDFS(int cnt,int pos){
        if (cnt == k) {
            for (int i = 0; i < k; i++) {
                System.out.print(path[i] + " ");
            }
            count++;
            System.out.println("共计: " + count + " 次");
            return;
        }

        for (int i = pos; i < n; i++) {
            if (!vis[a[i]]) {
//                if (a[i]<path[cnt]){
//                    continue;
//                }
                path[cnt] = a[i];
                vis[a[i]] = true;
                chooseThreeNumsPermutationDFS(cnt + 1,i+1);
                vis[a[i]] = false;
            }
        }
    }

【例】30个数,取8个凑sum,有多少方案?(答案70种)

加上剪枝明显减少运行时间

import java.util.Scanner;

public class ChooseNumsToSum_DFS_CutBranch {
    static int n;
    static int k;
    static int sum;
    static int[] a = new int[100];
    static boolean[] vis = new boolean[100];
    static int ans = 0;

    public static void DFS(int s, int cnt, int pos) {
        // 可行性剪枝
        if (s > sum || cnt > k) {
            return;
        }
        if (s == sum && cnt == k) {
            ans++;
        }
        // 重复性剪枝 i从pos位置开始
        for (int i = pos; i < n; i++) {
            if (!vis[i]) {
                vis[i] = true;
                DFS(s + a[i], cnt + 1, i + 1);
                vis[i] = false;// 回溯
            }
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = 30;
        k = 8;
        sum = 200;
        for (int i = 0; i < n; i++) {
            a[i] = i + 1;
        }
        DFS(0, 0, 0);
        System.out.println(ans);
    }
}

4.5 全排列

import java.util.Scanner;

public class FullPermutation {
    static int n;

    static boolean[] vis = new boolean[100];
    static int[] path = new int[100];

    public static void DFS(int i) {
        if (i == n) {
            for (int j = 0; j < n; j++) {
                System.out.print(path[j] + " ");
            }
            System.out.println();
            return;
        }
        for (int j = 1; j <= n; j++) {
            if (!vis[j]) {
                path[i] = j;
                vis[j] = true;
                DFS(i + 1);
                vis[j] = false;
            }
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        DFS(0);
    }
}

与或异或

public class LanQiao与或异或 {
    // time is  15:48
    static int[][] a = new int[5][5];
    /*
        1 0 1 0 1
        1 1 1 0
        1 1 1
        1 0
        1
     */
    static int res = 0;
    static int[] path = new int[10]; // 1&  2|  3^
    static int pathX =0;
    static int pathY =0;
    static int pIndex =0;
    public static boolean check(){
        pIndex=0;
        a[0][0] = 1;
        a[0][1] = 0;
        a[0][2] = 1;
        a[0][3] = 0;
        a[0][4] = 1;
        for (int i = 1; i <=4 ; i++) {
            for (int j = 0; j <= 4-i; j++) {
                if (path[pIndex]==1)
                    a[i][j] = a[i-1][j] & a[i-1][j+1];
                if (path[pIndex]==2)
                    a[i][j] = a[i-1][j] | a[i-1][j+1];
                if (path[pIndex]==3)
                    a[i][j] = a[i-1][j] ^ a[i-1][j+1];
                pIndex++;
            }
        }
        return a[4][0]==1;
    }

    public static void dfs(int cnt){
        if (cnt==10){
            if (check()){
                res++;
            }
            return;
        }

        for(int i = 1; i <= 3; i++) {    //第k个逻辑门有三种选择:与、或、异或
            path[cnt] = i;        //记录第k个逻辑门:与、或、异或
            dfs(cnt + 1);         //继续深搜第k+1个逻辑门
        }
    }

    public static void main(String[] args) {
        dfs(0);
        System.out.println(res);
    }
}

5 DP

5.1 路径

import java.util.Scanner;

public class CrossRiverSoldier_DP {
    /*
    用例
    输入 5 5 2 4
        n m hx hy
        棋盘大小 马的位置
    输出 14
     */
    static int n, m;
    static int[][] dir = {
            {-2, -1},
            {-2, 1},
            {-1, -2},
            {-1, 2},
            {1, 2},
            {2, 1},
            {2, -1},
            {1, -2},
    };
    static int[][] dp = new int[30][30];
    static boolean[][] horseControl = new boolean[100][100];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        int hx = sc.nextInt();
        int hy = sc.nextInt();
        horseControl[hx][hy] = true;
        for (int i = 0; i < dir.length; i++) {
            int dx = hx + dir[i][0];
            int dy = hy + dir[i][1];
            if (dx >= 0 && dx <= n && dy >= 0 && dy <= m) {
                horseControl[dx][dy] = true;
            }
        }
        dp[0][0] = 1;
        for (int i = 0; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                if (!horseControl[i][j]) {
                    if (i != 0) {
                        dp[i][j] += dp[i - 1][j];
                    }
                    if (j != 0) {
                        dp[i][j] += dp[i][j - 1];
                    }
                }
            }
        }
        System.out.println(dp[n][m]);
    }
}

【注意】边界条件 i <= n , j<=m

【为什么DP索引从1开始】为了不越界,方便判断

5.2 回家

要么往上走,要么往右走----->动态规划DP

import java.util.Scanner;

public class GoHome_DP {
    /*
    用例
    输入
    0 3 4
    6 2 5
    5 4 3
    输出
    12
     */

    // 左下角走到右上角,数字代表代价
    static int[][] map = new int[10][10];
    static int n = 3;
    static int[][] dp = new int[10][10];

    //    static int [][] dir = {
//            {1,0},
//            {0,1}
//    }; // 注意这是DP而不是DFS,不需要方向
    public static void main(String[] args) {
        dp[1][1] = 0; // dp从1,1开始
        Scanner sc = new Scanner(System.in);
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                map[i][j] = sc.nextInt();
            }
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (i == 1 && j == 1) {
                    continue;
                } else if (i == 1) {
                    dp[i][j] = dp[i][j - 1] + map[i][j];
                } else if (j == 1) {
                    dp[i][j] = dp[i - 1][j] + map[i][j];
                } else {
                    dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + map[i][j];
                }
            }
        }
        System.out.println(dp[n][n]);

    }
}

【注意】处理边界条件

最后12为答案

5.3 捡水果

import java.util.Scanner;

public class PickFruit_DP {
    /*
    4

    3
    1 2
    6 2 3
    3 5 4 1
     */
    static int[][] map = new int[100][100];
    static int[][] dp = new int[100][100];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int max = 0;
        int n = sc.nextInt();
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                map[i][j] = sc.nextInt();
            }
        }
        
        dp[1][1] = map[1][1];
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                if (i == 1 && j == 1) {
                    continue;
                } else if (i == 1) {
                    dp[i][j] += map[i][j]; // 这行居然没有运行,原因是i等于1的时候j必等于1
                    // 所以可见初始化需要自己好好判断
                } else if (j == 1) {
                    dp[i][j] += dp[i - 1][j] + map[i][j];
                } else {
                    dp[i][j] += Math.max(dp[i - 1][j - 1], dp[i - 1][j]) + map[i][j];
                }
                // 判断最后一行的最大值
                if (i == n) {
                    max = Math.max(max, dp[i][j]);
                }
            }
        }
        System.out.println(max);
    }
}

5.4 爬楼梯

LeetCode 70

import java.util.Scanner;

public class UpFloor_DP {
    /*
    用例
    输入4
    输出5

    输出答案需要mod 100007
     */
    static int[] dp = new int[100]; // dp数组记录的是【方法数】

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n= sc.nextInt();
        dp[1]=1;
        dp[2]=2;
        for (int i = 3; i <= n; i++) {
            dp[i] = dp[i-1]+dp[i-2];
        }
        System.out.println(dp[n] % 100007);
    }

}

6 背包

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值