目录
一 . 打印字符串序列
总体思路:将字符串按照深度优先遍历,将不要当前字符的跑一遍;再将要当前字符的跑一遍,再加上当前字符;形成一个二叉树结构的样子。代码实现较为简单,主要是理解起来,如果理解的太深,反而会迷惑,所以对于递归这一类的,就去给局部的流程整理清楚,然后放大到全局都是这个流程,就是正确的。以下是代码实现。分为重复和无重复的实现。
//一、打印字符串的子序列 包含重复的
//参数:字符串 起始位置 存储结果的集合 结果串
public void printSequences(char[] arr, int index, List<String> result, String path) {
//递归出口 如果当前index等于arr的长度了 就将这个path结果存到result集合中
if (index == arr.length) {
result.add(path);
return;
}
//将要和不要的字符 都通通跑一遍 然后将需要的字符加到path上面
//不能去太深的理解 容易乱
//不要当前字符的 将其深度跑一遍
printSequences(arr, index + 1, result, path);
//要当前字符的 将其深度跑一遍 并加上当前字符
printSequences(arr, index + 1, result, path + arr[index]);
}
//二、打印字符串序列 不包含重复元素 直接使用set去重即可
public void printSequences(char[] arr, int index, HashSet<String> result, String path) {
if (index == arr.length) {
result.add(path);
return;
}
//不要当前位置上的元素
printSequences(arr, index + 1, result, path);
//需要当前位置上的元素
printSequences(arr, index + 1, result, path + arr[index]);
}
二. 打印字符串的全排列
总体思路:循环字符串,每次交换相邻两个元素,然后递归去操作他(继续交换,或者将其放入到结果集合中),操作完毕之后,将元素位置调换回来。分为去重复和不去重复两个实现。
不去重复版:
public static List<String> per1(char[] arr){
List<String> result = new ArrayList<>();
permutation1(arr,result,0);
return result;
}
//未去重复版
//参数:字符数组 结果集合 每次开始的位置
public static void permutation1(char[] arr, List<String> result, int index) {
//递归出口 如果index等于数组长度了 就将其添加到result中
if(index == arr.length){
result.add(String.valueOf(arr));
}else {
//循环这个字符串 将这个字符串的邻近两个元素交换位置 去处理它 然后再将其位置还原 进行下一次交换并处理
//注意:i的位置应该是从index开始 进行每一个局部的相邻字符的交换 最终形成整体
for (int i = index; i < arr.length; i++) {
swap(arr, i, index);
//处理它
permutation1(arr, result, index + 1);
//将原顺序调整回来
swap(arr, i, index);
}
}
}
去重复版:
//去重复版
public static List<String> per2(char[] arr){
List<String> result = new ArrayList<>();
permutation2(arr,result,0);
return result;
}
//参数:字符数组 结果集合 每次开始的位置
public static void permutation2(char[] arr,List<String> result,int index){
if(index == arr.length){
result.add(String.valueOf(arr));
}else{
//去重复 给每一个字符做一个标记 如果是true就是已经存在
boolean[] flag = new boolean[256];
for(int i = index;i < arr.length;i++){
if(!flag[i]) { //如果是默认值false 就进来处理这个字符串 如果是true就不处理
flag[i] = true;
swap(arr, i, index);
permutation2(arr, result, index + 1);
swap(arr, i, index);
}
}
}
}
三. 拿纸牌,得分数
题目:给定一个整型数组arr,代表数值不同的纸牌排成一条线 ,玩家A和玩家B依次拿走每张纸牌, 规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌, 玩家A和玩家B都绝顶聪明,请返回最后获胜者的分数。
代码实现:
public int win(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
//先手先拿
int firstValue = first(arr,0,arr.length - 1);
int lastValue = last(arr,0,arr.length - 1);
return Math.max(firstValue,lastValue);
}
//参数:起始位置 终止位置 在这个范围内去拿纸牌
public int first(int[] arr, int left, int right) {
//如果只剩下一个牌 肯定是我先拿
if(left == right){
return arr[left];
}
//两种情况:
//情况一:我先拿左边的 拿完之后我的每次都变成了后手 但是我多了一个arr[left]值
//范围变成了 left + 1 ~ right
int leftValue = arr[left] + last(arr, left + 1, right);
//情况二:先拿右边的 拿完之后我变成了后手 同时我也多了一个arr[right]
int rightValue = arr[right] + last(arr, left, right - 1);
//我每次获得两个之中的最大值
return Math.max(leftValue,rightValue);
}
//后手拿牌
public int last(int[] arr, int left, int right) {
//如果只剩下一个牌 肯定是属于先手 我什么也得不到
if(left == right){
return 0;
}
//两种情况:
//情况一:左边的牌已经被先手拿了 那我只剩下右边的牌 即范围变成了left + 1 ~ right
int rightValue = first(arr, left + 1, right);
//情况二:右边的牌被先手拿了 我只剩下左边的牌 范围变成了 left ~ right - 1;
int leftValue = first(arr, left, right - 1);
//每次获得最小的那个
return Math.min(rightValue,leftValue);
}
四. 逆序栈(不可以使用额外空间)
总体思路:每次先从栈低拿出一个数,然后将原数压入栈中,保持栈的顺序,栈空之后,使用递归将其反向压入栈中。
代码实现:
//将一个栈逆序 不能使用额外的存储空间
public void reverseStack(Stack<Integer> stack){
//递归出口 如果栈为空 停止
if(stack.isEmpty()){
return ;
}
//我需要一个方法 每次调用他 给我返回一个栈低元素
//每次得到一个栈低元素
int base = returnBase(stack);
//使用递归 将获取到的栈低元素按照反方向存入
reverseStack(stack);
//将这个元素压入栈
stack.push(base);
}
//返回栈低元素 并保持原栈顺序
public int returnBase(Stack<Integer> stack){
//先拿到栈顶元素 当最后一个栈顶元素被弹出时 栈为空 进入到if里面 且top不会记录最后一个元素的值 即不会将其压入栈 会直接返回
int top = stack.pop();
//拿完之后 判断栈是否为空 如果为空 直接返回这个top
if(stack.isEmpty()){
return top;
}else{
//如果不是空 继续执行此操作 并记录栈低的值
int last = returnBase(stack);
//栈为空后 依次返回 将上面的元素重新压回栈中 保持原栈顺序
stack.push(top);
return last;
}
}
五. 背包问题
题目描述:给定两个长度都为N的数组weights和values,weights[i]和values[i]分别代表,i号物品的重量和价值 ,给定一个正数bag,表示一个载重bag的袋子,装的物品不能超过这个重量,返回能装下的最大价值。
总体思路:使用暴力递归,将不要当前货物的路线跑一遍,再跑一遍要当前货物的路线,得到两者的最大值,就是最大价值。
//参数:背包数组 价值数组 起始位置(i号物品) 载重袋子 已经装了多少货物
//返回值:能装下的最大价值
public int bagMaxCapacity(int[] weights,int[] values,int i,int bag,int alreadyWeight){
//递归出口
//1. 如果背包中的物品已经大于最大载重了 返回0
if(alreadyWeight > bag){
return 0;
}
//2. 如果物品数组已经空了 什么也拿不到 返回0
if(i == weights.length){
return 0;
}
//将要当前物品的和不要当前物品的都跑一遍 然后返回其最大值
// 不要当前物品的
return Math.max(bagMaxCapacity(weights,values,i + 1,bag,alreadyWeight),
// 要当前物品的 当前物品的价值 加上下一个要的物品的价值
values[i] + bagMaxCapacity(weights,values,i + 1,bag,alreadyWeight + weights[i]));
}
暴力递归改动态规划版本
//暴力递归法
//index 是w中的第index个货物
//rest 是背包还能装入的货物量
public static int bag1(int[] w, int[] v, int index, int rest) {
//如果rest被撑爆了 则不能装了
if (rest < 0) {
return -1;
}
//如果走到了最后一个货物 则也没有了
if (index == w.length) {
return 0;
}
//先找不要当前货物的
int p1 = bag1(w, v, index + 1, rest);
//找要当前货物的
int p2 = 0;
int next = bag1(w, v, index + 1, rest - w[index]);
//如果要当前货物了,并且背包不被撑爆的前提下 将后面一个货物临时接收
if (next != -1) {
p2 = v[index] + next;
}
//返回最大价值
return Math.max(p1, p2);
}
//背包问题改动态规划
public static int dpBag(int[] w, int[] v, int bag) {
//如果bag被撑爆了 则不能装了
//循环从0开始 所以没有小于0一说
/*if (bag < 0) {
return -1;
}*/
//如果走到了最后一个货物 则也没有了
//因为二维表初始每个值都是0 所以可以省去这一步
/*if (index == w.length) {
return 0;
}*/
//去掉无效数据
if (w == null || v == null || w.length != v.length || w.length == 0) {
return -1;
}
//二维数组表
//只有遍历货物和背包是可变的
//得到货物的个数
int N = w.length;
//N 存储货物的个数 bag存储背包的容量
int[][] dp = new int[N + 1][bag + 1];
//双循环 填表
//从下往上 从左往右
for (int index = N - 1; index >= 0; index--) {
for (int rest = 0; rest <= bag; rest++) {
//先找不要后面一个货物的
int p1 = dp[index + 1][rest];
//找要要后面一个货物的
int p2 = 0;
//要后面一个货物的前提是背包能不能装下
//如果能装下 则后面一个货物的值就是表中的对应位置上的值
int next = rest - w[index] < 0 ? -1 : dp[ index + 1 ][ rest - w[index] ];
//如果要当前货物了,并且背包不被撑爆的前提下 将后面一个货物临时接收
if (next != -1) {
p2 = v[index] + next;
}
//将p1和p2较大价值的装入到表中
dp[index][rest] = Math.max(p1,p2);
}
}
//因为是从下往上开始填表 所以返回最大价值是第一行的最后一个
return dp[0][bag];
}
六. 数字数组转换成字符
题目描述:规定1和A对应、2和B对应、3和C对应...26和Z对应,那么一个数字字符串比如"111”就可以转化为: "AAA"、"KA"和"AK",给定一个只有数字字符组成的字符串str,返回有多少种转化结果。
总体思路:暴力递归 尝试每一中可能。分情况讨论
//参数:字符串数组 起始位置
//返回值:返回N中转化结果
public static int convertNumToString(char[] str, int i) {
//如果已经走到头了 则解法加一
if (i == str.length) {
return 1;
}
//如果碰到0了 直接跳过
//如果碰到了1 就正常转
if (str[i] == '1') {
//将当前的i进行单转
int res = convertNumToString(str, i + 1);
//在不越界的条件下 将当前i和其i+1的位置进行组合转
if (i + 1 < str.length) {
res += convertNumToString(str, i + 2);
}
//返回i在1时的转换结果
return res;
}
//如果碰到了2 单转正常转
if (str[i] == '2') {
//单转
int res = convertNumToString(str, i + 1);
//如果i+1的位置不越界 并且在0~6的范围内(字母只有26个)
//将当前i和i+1进行组合转
if (i + 1 < str.length && (str[i + 1] >= '0' && str[i + 1] <= '5')) {
res += convertNumToString(str, i + 2);
}
//返回i==2时的转换结果
return res;
}
//如果i在 3~9 的范围内 则直接单转
return convertNumToString(str, i + 1);
}
七.N皇后问题
题目描述:
N皇后问题是指在N*N的棋盘上要摆N个皇后,
* 要求任何两个皇后不同行、不同列, 也不在同一条斜线上
* 给定一个整数n,返回n皇后的摆法有多少种。 n=1,返回1
* n=2或3,2皇后和3皇后问题无论怎么摆都不行,返回0
* n=8,返回92
采用暴力递归的方式
//给定一个N 返回能找出的有效摆法数
public static int num(int N) {
if (N < 1) {
return 0;
}
if (N == 1) {
return 1;
}
//记录行列的数组 索引代表行,该索引位置上的值代表列
int[] record = new int[N];
int result = process(N, 0, record);
return result;
}
//给定N 当前行 以及记录摆入皇后的列数组(按照深度优先进行遍历,不会出现行重复的情况,所以只需要记录列数组)
public static int process(int N, int curRow, int[] record) {
//递归出口
//curRow代表行 当皇后数和curRow数相等了 再停止
if (N == curRow) {
return 1;
}
int result = 0;
//循环每一个皇后 列索引
for (int j = 0; j < N; j++) {
//判断 是否有共列 或者共斜线的情况
if (isValid(record, curRow, j)) {
//可以存
//将对应的列索引值存到当前位置上 即 第0行 存入j=0
record[curRow] = j;
//继续找别的皇后 并加入解法中 继续从下一行开始找
result += process(N, curRow + 1, record);
}
}
return result;
}
//判断是否有共列 共斜线的情况 只需要看 i - 1 之前的那些行 i及其之后的还没有放入过值
//参数:当前数组 处理到了第几行 当前列值多少
public static boolean isValid(int[] record, int curRow, int j) {
//依次查看每一列的每一个值 是否等于 当前位置(因为存储的时候是将对应的位置索引存入到对应的位置)
for (int k = 0; k < curRow; k++) {
//条件一:比较两个皇后是否共列 即:record[k] 代表的列值 是否等于 j代表的列值 如果共列 不能放
//条件二:是否是同斜线的 计算方法:如果行-行 == 列-列 就代表是同一条斜线上面的
//如果当前不是同一条斜线上面的 就往里面缩小范围 k代表了行 record[k]就是列
//缩小范围理解: 因为k和curRow这两个行形成了一个范围
// 在这个范围内去逐步缩小范围去比较是否有共斜线的现象
if (record[k] == j || Math.abs(curRow - k) == Math.abs(record[k] - j)) {
//不能存
return false;
}
}
return true;
}