1 位运算
1.1 位运算符
&(与),|(或),^(异或),~(非)
1.2 基础练习
- 找到数组中唯一存在的那个数
性质: aabbc=c
- 二进制中1的个数
while(n!=0){
n=(n-1)&n;
count++;
}
- 用一个语句判断一个整数是不是2的整数次方
一个数是2的整数次方,则把它转化为2进制时,只有一个1
if((n-1)&n==0){
flag=true;
}
2 递归
2.1 定义
递归:程序调用自身的编程技巧称为递归( recursion)。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
2.2 递归设计经验
——找重复(子问题)
——找重复中的变化量(参数)
——找参数变化趋势(设计出口)
2.3 基础练习
求阶乘
注意在方法前面加上static
static int f(int n){
if(n==1)
return 1;
return n*f(n-1);
}
打印从i到j的数字
static void f(int i,int j ){
if(i>j)
return;
System.out.println(i);
f(i+1,j);
}
翻转字符串
static String f(String str,int end){
if(end==0){
return "jieshu"+str.charAt(0);
}
return str.charAt(end)+f(str,end-1);
}
斐波拉契数列
static int f(int n){
if(n==1||n==2){
return 1;
}
return f(n-1)+f(n-2);
}
最大公约数
static int f(int m,int n){
//辗转相除法
if(n==0){
return m;
}
return f(n,m%n);
}
插入排序改递归
汉诺塔问题
static void f(int N,String from,String to,String help){
/*
把1-N从A移动到B,C作为辅助,等价于:
把1-N-1从A移动到C,B为辅助
把N从A移动到B
把1-N-1从C移动到B,A为辅助
from 原始柱子
to 辅助柱子
help 目标柱子
*/
if(N==1){
System.out.println("move "+N+" from "+from+" to "+to);
}else{
f(N-1,from,help,to);
System.out.println("move "+N+" from "+from+" to "+to);
f(N-1,help,to,from);
}
}
折半查找改递归
static int f(int arr[],int low,int high,int key){
if(low>high){
return -1;
}
int mid=low+((high-low)>>1);
if(arr[mid]<key){
return f(arr,mid+1,high,key);
}else if(arr[mid]>key){
return f(arr,low,mid-1,key);
}else{
return mid;
}
}
3 排序算法
4 多维数组和矩阵
5 字符串问题
5.1 StringBuffer
String是不变类,java提供了一个StringBuffer类,这个类在修改字符串方面的效率比String高了很多。
类 | 方法 | 作用 |
---|---|---|
StringBuffer | append(String str) | 将指定的字符串追加到此字符序列 |
StringBuffer | delete(int start,int end) | 移除此序列的子字符串中的字符 |
StringBuffer | insert(int offset,String str) | 将字符串插入此字符序列中 |
StringBuffer | replace(int start,int end,String str) | 使用给定String中的字符替换此序列的子字符串中的字符 |
StringBuffer | reverse() | 将此字符序列用其反转形式取代 |
String | toString() | 返回此序列中数据的字符串表示形式 |
5.2应用
5.2.1 indexOf
返回指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。
String str = "123456789";
System.out.print(str.indexOf('9'));
5.2.2 str.charAt(i)
是获取字符串中i位置的字符
7 深入递归
7.1概念
回溯:
- 递归调用代表开启一个分支,如果希望这个分支返回后某些数据恢复到分支开启前的状态以便重新开始,就要使用回溯技巧
- 全排列的交换法,数独,部分和,用到了回溯
剪枝:
- 深搜时,如已明确从当前状态无论如何转移都不会存在(更优)解,就应该中断往下的继续搜索,这种方法称为剪枝
- 数独里面有剪枝
- 部分和里面有剪枝
DFS深度优先搜素
-
使用回溯法,一条路走到黑。不断回溯,直到全部结点都被搜完。
-
public static void DFS(int v) {// 图的深搜 visited[v] = true; for (int i = 0; i < a[0].length; i++) { if (check() && visited[i] == false) { DFS(i);// 回溯 } } } public static void DFS(TreeNode head) {// 树的深搜使用回溯 if (head == null) return; check();// 满足条件的check(); if (head.left != null) { DFS(head.left); } if (head.right != null) { DFS(head.right); } }
BFS广度优先搜索
-
使用队列
-
①定义队列q,并将起点s入队。
②写一个 while循环,循环条件是队列q非空。
③在while循环中,先取出队首元素top,然后访问它(访问可以是任何事情,例如将其输出)。访问完后将其出队。
④将top的下一层结点中所有未曾入队的结点入队,并标记它们的层号为now的层号加1,同时设置这些入队的结点已入过队。
⑤返回②继续循环。
-
实例见练习—岛屿问题和迷宫
-
// 计算从起点 start 到终点 target 的最近距离 int BFS(Node start, Node target) { Queue<Node> q; // 核心数据结构 Set<Node> visited; // 避免走回头路 q.offer(start); // 将起点加入队列 visited.add(start); int step = 0; // 记录扩散的步数 while (q not empty) { int sz = q.size(); /* 将当前队列中的所有节点向四周扩散 */ for (int i = 0; i < sz; i++) { Node cur = q.poll(); /* 划重点:这里判断是否到达终点 */ if (cur is target) return step; /* 将 cur 的相邻节点加入队列 */ for (Node x : cur.adj()) if (x not in visited) { q.offer(x); visited.add(x); } } /* 划重点:更新步数在这里 */ step++; } }
7.2 练习
爬楼梯
- 有个小孩正在上楼梯,楼梯有n阶台阶,小孩—次可以上1阶、2阶、3阶。请实现一个方法,计算小孩有多少种上楼的方式。为了防止溢出,请将结果mod 1000000007。
递归
public static long recursion(int n) {
if (n < 0) return 0;
if (n == 0 || n == 1) return 1;
if (n == 2) return 2;
return recursion(n - 1) % mod + recursion(n - 2) % mod + recursion(n - 3) % mod;
}
迭代
public static long recursion(int n) {
if (n < 0) return 0;
if (n == 0 || n == 1) return 0;
if (n == 2) return 2;
if (n == 3) return 4;
int x1 = 1;
int x2 = 2;
int x3 = 4;
for (int i = 4; i <= n; i++) {
int temp = x1;
x1 = x2 % mod;
x2 = x3 % mod;
x3 = ((x1 + x2) % mod + temp) % mod;
}
return x3;
}
机器人走网格
-
有一个X*Y的网格,一个机器人只能走格主且只能向右或向下走,要从左上角走到右下角。请设计一个算法,
计算机器人有多少种走法。给定两个正整数int x,int y,请返回机器人的走法数目。保证x+y小于等于12。
递归
public static int solve(int x, int y) {
if (x == 1 || y == 1) {
return 1;
}
return solve(x - 1, y) + solve(x, y - 1);
}
迭代
public static int solve(int x, int y) {
int state[][] = new int[x + 1][y + 1];
for (int i = 1; i <= y; i++) {
state[1][i] = 1;
}
for (int i = 1; i <= x; i++) {
state[i][1] = 1;
}
for (int i = 2; i <= x; i++) {
for (int j = 2; j <= y; j++) {
state[i][j] = state[i][j - 1] + state[i - 1][j];
}
}
return state[x][y];
}
分硬币
- 有1分,2分,5分,10分四种硬币,每种硬币数量无限,给定n分钱,有多少中组合可以组成n分钱
public static int countWays(int n) {
if (n <= 0)
return 0;
return Ways(n, new int[]{1, 5, 10, 25}, 3);
}
public static int Ways(int n, int arr[], int cur) {
if (cur == 0)
return 1;
int count = 0;
for (int i = 0; i * arr[cur] <= n; i++) {
int odd = n - i * arr[cur];
count = count + Ways(odd, arr, cur - 1);
}
return count;
}
部分和
-
给定整数序列a1,a2, . . . , an,判断是否可以从中选出若干数,使它们的和恰好为k.
输入:
n = 4
a = {1,2,4,7}k = 13
输出:
Yes(13 = 2 + 4 + 7)
public class Main {
static int K;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int num = in.nextInt();
int arr[] = new int[num];
for (int i = 0; i < num; i++) {
arr[i] = in.nextInt();
}
int k = in.nextInt();
K = k;
dfs(arr, k, 0, new ArrayList<Integer>());
}
static void dfs(int arr[], int k, int cur, ArrayList<Integer> list) {
if (k == 0) {
System.out.println("yes");
System.out.print(K + " = ( ");
for (int i = 0; i < list.size(); i++) {
if (i == list.size() - 1) {
System.out.println(list.get(i) + " )");
} else
System.out.print(list.get(i) + " + ");
}
System.exit(0);
}
if (k < 0 || cur == arr.length)
return;
list.add(arr[cur]);
dfs(arr, k - arr[cur], cur + 1, list);
int index = list.size() - 1;
list.remove(index);
dfs(arr, k, cur + 1, list);
}
}
*水洼数
-
有一个大小为N乘M的园子,雨后积起了水。八连通的积水被认为是连接在一起的。请求出园子里总共有多少水洼?(八连通指的是下图中相对W的*部分)
***
*w*
***
限制条件
N, M <=100
样例:
输入
N = 10, M = 12园子如下图('W’表示积水, '*'表示没有积水)
W********WW* *WWW*****WWW ****WW***WW* *********WW* *********W** **W******W** *W*W*****WW* W*W*W*****W* *W*W******W* **W*******W*
输出
3
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int N = in.nextInt();
int M = in.nextInt();
char ch[][] = new char[N][M];
for (int i = 0; i < N; i++) {
ch[i] = in.next().toCharArray();
}
int count = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (ch[i][j] == 'W') {
dfs(ch, i, j);
count++;
}
}
}
System.out.println(count);
}
public static void dfs(char ch[][], int i, int j) {
//不需要回溯
ch[i][j] = '*';
for (int m = -1; m <= 1; m++) {
for (int n = -1; n <= 1; n++) {
if (m == 0 && n == 0) {
continue;
}
if (i + m >= 0 && i + m <= ch.length - 1 && j + n >= 0 && j + n <= ch[i].length - 1) {
if (ch[i + m][j + n] == 'W') {
dfs(ch, i + m, j + n);
}
}
}
}
}
}
n皇后问题
- 请设计一种算法,解决著名的n皇后问题。这里的n皇后问题指在一个n*n的棋盘上放置n个棋子,使得每行每列和每条对角线上都只有一个棋子,求其摆放的方法数。
public class Main {
static int num;
static int count = 0;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
num = in.nextInt();
int nums[] = new int[num];
dfs(0, nums);
System.out.println(count);
}
static void dfs(int row, int nums[]) {
if (row == num) {
count++;
return;
}
for (int col = 0; col < num; col++) {
boolean check = true;
for (int i = 0; i < row; i++) {
if (nums[i] == col || i + nums[i] == row + col || nums[i] - i == col - row) {
check = false;
}
}
if (check) {
nums[row] = col;
dfs(row + 1, nums);
nums[row] = 0;
}
}
}
}
素数环
- 输入正整数n,对1-n进行排列,使得相邻两个数之和均为素数,输出时从整数1开始,逆时针排列。同一个环应恰好输出一次。n<=16
困难的环
*数组子集
- 求不含重复元素的数组的所有子集
二进制子集枚举
public class Main {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
ArrayList<ArrayList<Integer>> List = new ArrayList<ArrayList<Integer>>();
Scanner in = new Scanner(System.in);
int num = in.nextInt();
int nums[] = new int[num];
for (int i = 0; i < num; i++) {
nums[i] = in.nextInt();
}
for (int i = 0; i < (1 << num); i++) {
list.clear();
for (int j = 0; j < num; j++) {
if ((i & (1 << j)) != 0) {
list.add(nums[j]);
}
}
List.add(list);
System.out.println(List.get(i));
}
}
}
迭代法
import java.util.*;
public class Main {
static int index = 0;
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
ArrayList<ArrayList<Integer>> List = new ArrayList<ArrayList<Integer>>();
Scanner in = new Scanner(System.in);
int num = in.nextInt();
int nums[] = new int[num];
for (int i = 0; i < num; i++) {
nums[i] = in.nextInt();
}
dfs(0, nums, list, List);
}
static void dfs(int begin, int nums[], ArrayList<Integer> list, ArrayList<ArrayList<Integer>> List) {
if (begin == nums.length) {
List.add(new ArrayList<>(list));
System.out.println(List.get(index));
index++;
return;
}
list.add(nums[begin]);
dfs(begin + 1, nums, list, List);
list.remove(list.size() - 1);
dfs(begin + 1, nums, list, List);
}
}
数组子集II
-
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
二进制子集枚举
对于当前选择的数 x,若前面有与其相同的数 y,且没有选择 y,此时包含 xx的子集,必然会出现在包含 y 的所有子集中。
我们可以通过判断这种情况,来避免生成重复的子集。代码实现时,可以先将数组排序;迭代时,若发现没有选择上一个数,且当前数字与上一个数相同,则可以跳过当前生成的子集。
import java.util.*;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
ArrayList<ArrayList<Integer>> List = new ArrayList<ArrayList<Integer>>();
Scanner in = new Scanner(System.in);
int num = in.nextInt();
int nums[] = new int[num];
int index = 0;
for (int i = 0; i < num; i++) {
nums[i] = in.nextInt();
}
Arrays.sort(nums);
for (int i = 0; i < (1 << num); i++) {
boolean flag = true;
list.clear();
for (int j = 0; j < num; j++) {
if ((i & (1 << j)) != 0) {
if (j > 0 && nums[j] == nums[j - 1] && (i >> (j - 1) & 1) == 0) {
flag = false;
break;
}
list.add(nums[j]);
}
}
if (flag) {
List.add(new ArrayList<>(list));
System.out.println(List.get(index));
index++;
}
}
}
}
递归法
import java.util.*;
public class Main {
static int index = 0;
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
ArrayList<ArrayList<Integer>> List = new ArrayList<ArrayList<Integer>>();
Scanner in = new Scanner(System.in);
int num = in.nextInt();
int nums[] = new int[num];
for (int i = 0; i < num; i++) {
nums[i] = in.nextInt();
}
Arrays.sort(nums);
dfs(0, nums, list, List);
}
static void dfs(int begin, int nums[], ArrayList<Integer> list, ArrayList<ArrayList<Integer>> List) {
if (begin == nums.length) {
List.add(new ArrayList<>(list));
System.out.println(List.get(index));
index++;
return;
}
list.add(nums[begin]);
dfs(begin + 1, nums, list, List);
list.remove(list.size() - 1);
while (begin < nums.length - 1 && nums[begin] == nums[begin + 1]) {
begin++;
}
dfs(begin + 1, nums, list, List);
}
}
*数组全排列
- 给定一个没有重复数字的序列,返回其所有可能的全排列。
public class Main {
static int index = 0;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
ArrayList<Integer> list = new ArrayList<>();
ArrayList<ArrayList<Integer>> List = new ArrayList<ArrayList<Integer>>();
int num = in.nextInt();
int nums[] = new int[num];
for (int i = 0; i < num; i++) {
nums[i] = in.nextInt();
}
boolean used[] = new boolean[num];
dfs(nums, used, list, List);
}
public static void dfs(int nums[], boolean used[], ArrayList<Integer> list, ArrayList<ArrayList<Integer>> List) {
if (list.size() == nums.length) {
List.add(new ArrayList<>(list));
System.out.println(List.get(index));
index++;
}
for (int i = 0; i < nums.length; i++) {
if (!used[i]) {
used[i] = true;
list.add(nums[i]);
dfs(nums, used, list, List);
used[i] = false;
list.remove(list.size() - 1);
}
}
}
}
import java.util.ArrayList;
public class Main {
static int i = 0;
public static void main(String[] args) {
int num[] = { 1, 2, 3 };
ArrayList<Integer> list = new ArrayList<>();
ArrayList<ArrayList<Integer>> List = new ArrayList<ArrayList<Integer>>();
dfs(num, list, List);
}
public static void dfs(int num[], ArrayList<Integer> list, ArrayList<ArrayList<Integer>> List) {
if (list.size() == num.length) {
List.add(list);
System.out.println(List.get(i));
i++;
}
for (int i = 0; i < num.length; i++) {
if (!list.contains(num[i])) {
list.add(num[i]);
dfs(num, list, List);
list.remove(list.size() - 1);
}
}
}
}
数组全排列II
- 给定一个可包含重复数字的序列 nums,按任意顺序返回所有不重复的全排列。
Arrays.sort() 排序
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static int index = 0;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
ArrayList<Integer> list = new ArrayList<>();
ArrayList<ArrayList<Integer>> List = new ArrayList<ArrayList<Integer>>();
int num = in.nextInt();
int nums[] = new int[num];
for (int i = 0; i < num; i++) {
nums[i] = in.nextInt();
}
Arrays.sort(nums);
boolean used[] = new boolean[num];
dfs(nums, used, list, List);
}
public static void dfs(int nums[], boolean used[], ArrayList<Integer> list, ArrayList<ArrayList<Integer>> List) {
if (list.size() == nums.length) {
List.add(list);
System.out.println(List.get(index));
index++;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) {
continue;
}
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
//重点
//剪枝条件:i > 0 是为了保证 nums[i - 1] 有意义
//写 !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择
continue;
}
used[i] = true;
list.add(nums[i]);
dfs(nums, used, list, List);
used[i] = false;
list.remove(list.size() - 1);
}
}
}
组合
- 给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
二进制法 重点:Integer.bitCount(i)
import java.util.*;
public class Main {
static int index = 0;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int k = in.nextInt();
ArrayList<ArrayList<Integer>> List = new ArrayList<ArrayList<Integer>>();
ArrayList<Integer> list = new ArrayList<Integer>();
int nums[] = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = i + 1;
}
for (int i = 0; i < (1 << n); i++) {
list.clear();
if (Integer.bitCount(i) == k) {
for (int j = 0; j < n; j++) {
if ((i & (1 << j)) != 0) {
list.add(nums[j]);
}
}
List.add(new ArrayList<>(list));
System.out.println(List.get(index));
index++;
}
}
}
}
深搜(dfs)
import java.util.*;
public class Main {
static int index = 0;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int k = in.nextInt();
ArrayList<Integer> list = new ArrayList<>();
ArrayList<ArrayList<Integer>> List = new ArrayList<ArrayList<Integer>>();
dfs(n, k, 1, list, List);
}
static void dfs(int n, int k, int start, ArrayList<Integer> list, ArrayList<ArrayList<Integer>> List) {
if (list.size() == k) {
List.add(new ArrayList<Integer>(list));
System.out.println(List.get(index));
index++;
return;
}
for (int i = start; i <= n; i++) {
list.add(i);
dfs(n, k, start + 1, list, List);
list.remove(list.size() - 1);
}
}
}
组合总和
-
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
import java.util.*;
public class Main {
static int index = 0;
public static void main(String[] args) {
int num[] = { 2, 3, 6, 7 };
int target = 7;
ArrayList<ArrayList<Integer>> List = new ArrayList<ArrayList<Integer>>();
ArrayList<Integer> list = new ArrayList<Integer>();
Arrays.sort(num);
dfs(0, target, num, list, List);
}
static void dfs(int begin, int target, int num[], ArrayList<Integer> list, ArrayList<ArrayList<Integer>> List) {
if (target == 0) {
List.add(new ArrayList<>(list));
System.out.println(List.get(index));
index++;
}
for (int i = begin; i < num.length; i++) {
if (num[i] > target) {
break;
}
list.add(num[i]);
dfs(i, target - num[i], num, list, List);
//如果写成dfs(begin, target - num[i], num, list, List)
//就会出现重复输出
list.remove(list.size() - 1);
}
}
}
组合总和II
-
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
import java.util.*;
public class Main {
static int index = 0;
public static void main(String[] args) {
int num[] = { 2, 5, 2, 1, 2 };
int target = 5;
ArrayList<ArrayList<Integer>> List = new ArrayList<ArrayList<Integer>>();
ArrayList<Integer> list = new ArrayList<Integer>();
Arrays.sort(num);
boolean used[] = new boolean[num.length];
dfs(0, target, num, used, list, List);
}
static void dfs(int begin, int target, int num[], boolean used[], ArrayList<Integer> list,
ArrayList<ArrayList<Integer>> List) {
if (target == 0) {
List.add(new ArrayList<>(list));
System.out.println(List.get(index));
index++;
}
for (int i = begin; i < num.length; i++) {
if (!used[i]) {
if (num[i] > target) {
break;
}
if (i > 0 && num[i] == num[i - 1] && !used[i - 1]) {
continue;
}
used[i] = true;
list.add(num[i]);
dfs(i, target - num[i], num, used, list, List);
used[i] = false;
list.remove(list.size() - 1);
}
}
}
}
组合总和III
-
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。
示例 :
输入: k = 3, n = 7
输出: [[1,2,4]]
import java.util.*;
public class Main {
static int index = 0;
public static void main(String[] args) {
int nums[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int target = 9;
int num = 3;
ArrayList<ArrayList<Integer>> List = new ArrayList<ArrayList<Integer>>();
ArrayList<Integer> list = new ArrayList<Integer>();
boolean used[] = new boolean[nums.length];
dfs(0, target, num, nums, used, list, List);
}
static void dfs(int begin, int target, int num, int nums[], boolean used[], ArrayList<Integer> list,
ArrayList<ArrayList<Integer>> List) {
if (target == 0 && num == 0) {
List.add(new ArrayList<>(list));
System.out.println(List.get(index));
index++;
}
for (int i = begin; i < nums.length; i++) {
if (!used[i]) {
if (nums[i] > target) {
break;
}
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
continue;
}
used[i] = true;
list.add(nums[i]);
dfs(i, target - nums[i], num - 1, nums, used, list, List);
used[i] = false;
list.remove(list.size() - 1);
}
}
}
}
*岛屿问题(块问题 BFS)
-
给出一个n*m的矩阵,矩阵中的元素为0或1。称位置(x,y)与其上下左右四个位置(x,y+1)、(x,y-1)、(x+1,y)、(x-1,y)是相邻的。如果矩阵中有若干个1是相邻的(不必两两相邻),那么称这些1构成了一个“块”。求给定的矩阵中“块”的个数。
6 7
0 1 1 1 0 0 1
0 0 1 0 0 0 0
0 0 0 0 1 0 0
0 0 0 1 1 1 0
1 1 1 0 1 0 0
1 1 1 1 0 0 0输出: 4
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
class Node {
int x, y;// 位置(x,y)
}
public class 岛屿问题_块问题 {
static Node node = new Node();
// static int n,m;//矩阵大小
static int[][] a;// 01矩阵
static Boolean[][] inq;// 记录是否入过队
// 增量数组,组合分别代表上下右左
static int X[] = { 0, 0, 1, -1 };
static int Y[] = { 1, -1, 0, 0 };
// 判断坐标(x,y)是否需要访问
static Boolean judge(int x, int y, int n, int m) {
// 越界
if (x >= n || x < 0 || y >= m || y < 0) {
return false;
}
// 当前位置为0或已入过队
if (a[x][y] == 0 || inq[x][y] == true) {
return false;
}
return true;
}
// BFS函数访问位置(x,y)所在的块,将该块中所有 "1" 的inq都设置为true
static void bfs(int x, int y, int n, int m) {
Queue<Node> queue = new LinkedList<Node>();// 定义队列
node.x = x;
node.y = y;
queue.offer(node);// 结点入队
inq[x][y] = true;// 设置(x,y)已入过队
while (!queue.isEmpty()) {
Node top = queue.poll();// 返回队首元素并将其出队
for (int i = 0; i < 4; i++) {
int newX = top.x + X[i];
int newY = top.y + Y[i];
if (judge(newX, newY, n, m)) {// 如果新位置(newX,newY)需要访问
// 设置node的坐标为(newX,newY)
Node node1 = new Node();// 为什么每次都得生成新结点???
node1.x = newX;
node1.y = newY;
queue.offer(node1);// 将node加入对列
inq[newX][newY] = true;// 设置位置(newX,newY)已入过队
}
}
}
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
a = new int[n][m];
inq = new Boolean[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
inq[i][j] = false;
a[i][j] = in.nextInt();
}
}
int ans = 0;// 岛屿个数
for (int i = 0; i < n; i++) {// 枚举每一个位置
for (int j = 0; j < m; j++) {
// 如果元素为1且未入队
if (a[i][j] == 1 && inq[i][j] == false) {
ans++;// 岛屿个数加1
bfs(i, j, n, m);// 访问整个块,将该块所有 "1"的inq都标记为true
}
}
}
System.out.println(ans);
}
}
import java.util.*;
class Node {
int x;
int y;
public Node(int x, int y) {
this.x = x;
this.y = y;
}
}
public class Main {
static int num[][];
static int visited[][];
static int X[] = { 0, 0, 1, -1 };
static int Y[] = { 1, -1, 0, 0 };
static Node node = new Node();
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int count = 0;
num = new int[n][m];
visited = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
num[i][j] = in.nextInt();
visited[i][j] = 0;
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (num[i][j] == 1 && visited[i][j] == 0) {
count++;
bfs(i, j);
}
}
}
System.out.println(count);
}
public static void bfs(int i, int j) {
Queue<Node> queue = new LinkedList<Node>();
node.x = i;
node.y = j;
queue.offer(node);
while (!queue.isEmpty()) {
Node topNode = queue.poll();
for (int a = 0; a < 4; a++) {
int x = topNode.x + X[a];
int y = topNode.y + Y[a];
if (x < num.length && x >= 0 && y >= 0 && y < num[0].length) {
if (visited[x][y] == 0 && num[x][y] == 1) {
queue.offer(new Node(x,y));
visited[x][y] = 1;
}
}
}
}
}
}
迷宫(最短路径问题)
-
给定一个n*m大小的迷宫,其中*代表不可通过的墙壁,而“.”代表平地,S表示起点,T代表终点。移动过程中,如果当前位置是(x,y)(下标从0开始),且每次只能前往上下左右、(x,y+1)、(x,y-1)、(x-1,y)、(x+1,y)四个位置的平地,求从起点S到达终点T的最少步数。
输入:
5 5 ..... .*.*. .*S*. .***. ...T* 2 2 4 3
输出:
11
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
class Node {
int x, y;// 位置(x,y)
int step;// step为从起点S到达该位置的最少步数(即层数)
}
public class Main {
static String[][] a;// 迷宫
static Boolean[][] inq;
static int[] X = { 0, 0, 1, -1 };// 增量数组
static int[] Y = { 1, -1, 0, 0 };
static Node S = new Node();
static Node T = new Node();
// 检测位置(x,y)是否有效
static Boolean test(int x, int y, int n, int m) {
if (x >= n || x < 0 || y >= m || y < 0) { // 越界
return false;
}
if (a[x][y] == "*") { // 墙壁
return false;
}
if (inq[x][y] == true) { // 已入过队
return false;
}
return true;
}
static int bfs(int n, int m) {
Queue<Node> q = new LinkedList<Node>();
q.offer(S);
while (!q.isEmpty()) {
Node top = q.poll();
if (top.x == T.x && top.y == T.y) {
return top.step;
}
Node node = new Node();// ?????????
for (int i = 0; i < 4; i++) {// 循环四次,得到四个相邻位置
int newX = top.x + X[i];
int newY = top.y + Y[i];
if (test(newX, newY, n, m)) {// 位置 (newX,newY)有效
node.x = newX;
node.y = newY;// 设置Node的坐标是(newX,newY)
node.step = top.step + 1;// Node层数为top的层数+1
q.offer(node);// 将结点Node加入队列
inq[newX][newY] = true;// 设置位置 (newX,newY) 已经入过队
}
}
}
return -1;// 无法到达终点T时返回-1
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
in.nextLine();
a = new String[n][m];
inq = new Boolean[n][m];
for (int i = 0; i < n; i++) {
String[] str = in.nextLine().split("");
for (int j = 0; j < m; j++) {
a[i][j] = str[j + 1];
inq[i][j] = false;
}
}
// 起点和终点的坐标
S.x = in.nextInt();
S.y = in.nextInt();
T.x = in.nextInt();
T.y = in.nextInt();
S.step = 0;// 初始化起点的层数为0,即S到S的最小步数为0
System.out.println(bfs(n, m));
}
}
8 动态规划
8.1步骤
动态规划的的四个解题步骤是:
- 定义子问题
- 写出子问题的递推关系
- 确定 DP 数组的计算顺序
- 空间优化(可选)
- 动态规划本质是递推,核心是找到状态转移的方式,写出dp方程
8.2 经典题解
打家劫舍
-
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
状态转移方程:dp[i]=max(dp[i−2]+nums[i],dp[i−1])
class Solution {
public int rob(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int length = nums.length;
if (length == 1) {
return nums[0];
}
int[] dp = new int[length];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < length; i++) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[length - 1];
}
}
零钱兑换
-
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1示例 2:
输入:coins = [2], amount = 3
输出:-1
01背包问题
-
有n个重量和价值分别为wi, vi的物品,从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
1<n<100 , 1<wi , vi<100, -1≤W≤10000
输入∶
n=4
(w,v)={(2,3),(1,2),(3,4),(2,2)}W=5
输出:
7(选择第0,1,3号物品)
因为对每个物品只有选和不选两种情况,所以这个问题称为01背包。
重叠型子问题可以使用带记忆型的递归,或者说是带备忘录的递归求解。
import java.util.*;
public class DynamicProgramming {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
/* 1.读取数据 */
int number = sc.nextInt(); // 物品的数量
// 注意:我们声明数组的长度为"n+1",并另score[0]和time[0]等于0。
// 从而使得 数组的下标,对应于题目的序号。即score[1]对应于第一题的分数,time[1]对应于第一题的时间
int[] weight = new int[number + 1]; // {0,2,3,4,5} 每个物品对应的重量
int[] value = new int[number + 1]; // {0,3,4,5,6} 每个物品对应的价值
weight[0] = 0;
for (int i = 1; i < number + 1; i++) {
weight[i] = sc.nextInt();
}
value[0] = 0;
for (int i = 1; i < number + 1; i++) {
value[i] = sc.nextInt();
}
int capacity = sc.nextInt(); // 背包容量
/* 2.求解01背包问题 */
int[][] v = new int[number + 1][capacity + 1];// 声明动态规划表.其中v[i][j]对应于:当前有i个物品可选,并且当前背包的容量为j时,我们能得到的最大价值
// 填动态规划表。当前有i个物品可选,并且当前背包的容量为j。
for (int i = 0; i < number + 1; i++) {
for (int j = 0; j < capacity + 1; j++) {
if (i == 0) {
v[i][j] = 0; // 边界情况:若只有0道题目可以选做,那只能得到0分。所以令V(0,j)=0
} else if (j == 0) {
v[i][j] = 0; // 边界情况:若只有0分钟的考试时间,那也只能得0分。所以令V(i,0)=0
} else {
if (j < weight[i]) {
v[i][j] = v[i - 1][j];// 包的容量比当前该物品体积小,装不下,此时的价值与前i-1个的价值是一样的,即V(i,j)=V(i-1,j);
} else {
v[i][j] = Math.max(v[i - 1][j], v[i - 1][j - weight[i]] + value[i]);// 还有足够的容量可以装当前该物品,但装了当前物品也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}。
}
}
}
}
System.out.println();
System.out.println("动态规划表如下:");
for (int i = 0; i < number + 1; i++) {
for (int j = 0; j < capacity + 1; j++) {
System.out.print(v[i][j] + "\t");
}
System.out.println();
}
System.out.println("背包内最大的物品价值总和为:" + v[number][capacity]);// 有number个物品可选,且背包的容量为capacity的情况下,能装入背包的最大价值
/* 3.价值最大时,包内装入了哪些物品? */
int[] item = new int[number + 1];// 下标i对应的物品若被选中,设置值为1
Arrays.fill(item, 0);// 将数组item的所有元素初始化为0
// 从最优解,倒推回去找
int j = capacity;
for (int i = number; i > 0; i--) {
if (v[i][j] > v[i - 1][j]) {// 在最优解中,v[i][j]>v[i-1][j]说明选择了第i个商品
item[i] = 1;
j = j - weight[i];
}
}
System.out.print("包内物品的编号为:");
for (int i = 0; i < number + 1; i++) {
if (item[i] == 1) {
System.out.print(i + " ");
}
}
System.out.println("----------------------------");
}
}
}