一、打印一个字符串的全部排列
题目描述:打印一个字符串的全部排列,要求不要出现重复的排列
思路分析:对于一个字符串char[] str
,从0
到length - 1
依次决定每个位置的字符,整体是一个包含循环的递归,每次循环都是未决定字符的选择,选定的字符swap到str数组前,直到选定了所有的字符
public class PrintAllPermutations {
public static ArrayList<String> permutation(String str) {
ArrayList<String> res = new ArrayList<>();
if (str == null || str.length() == 0) {
return res;
}
char[] chs = str.toCharArray();
process(chs, 0, res);
return res;
}
/**
* @param str 原字符数组
* @param i str[0..i-1]范围上是之前做出的选择,str[i..]范围上所有的字符都可以在i这个位置上尝试
* @param res 把所有字符串形成的全排列放到res中
*/
public static void process(char[] str, int i, ArrayList<String> res) {
if (i == str.length) {
// 此时已经决定了所有字符的顺序,得到一种全排列。
res.add(String.valueOf(str));
}
// 记录在str[i]这个位置上各个字母的出现情况。
boolean[] visit = new boolean[26];
for (int j = i; j < str.length; j++) {
// 如果字母str[j]之前出现过,就不再排列该字母了
if (!visit[str[j] - 'a']) {
visit[str[j] - 'a'] = true;
// 将j位置上的字母放到i位置上
swap(i, j, str);
// 现在要决定i+1位置上的字母取值
process(str, i + 1, res);
// 将i位置上的字母还原,接下来再次选择
swap(i, j, str);
}
}
}
public static void swap(int src, int dest, char[] str) {
char temp = str[src];
str[src] = str[dest];
str[dest] = temp;
}
}
实战练习
- LeetCode原题:剑指 Offer 38. 字符串的排列 - 力扣(LeetCode)
- 难度:Medium
二、预测赢家
题目描述:给定一个整型数组arr,代表数值不同的纸牌排成一条线。 玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家B都绝顶聪明。请返回最后获胜者的分数。
【举例】
arr=[1,2, 100, 4]
:
开始时,玩家A只能拿走1或4。如果开始时玩家A拿走1,则排列变为[2, 100, 4],接下来玩家B可以拿走2或4,然后继续轮到玩家A…
如果开始时玩家A拿走4,则排列变为[1, 2, 100],接下来玩家B可以拿走1或100,然后继续轮到玩家A. …
玩家A作为绝顶聪明的人不会先拿4,因为拿4之后,玩家B将拿走100。所以玩家A会先拿1,让排列变为[2, 100, 4],接下来玩家B不管怎么选,100都会被玩家A拿走。玩家A会获胜,分数为101。所以返回101。
arr=[1, 100, 2]
:
开始时,玩家A不管拿1还是2,玩家B作为绝顶聪明的人,都会把100拿走。玩家B会获胜,分数为100。所以返回100。
思路分析:俩玩家都绝顶聪明,整个游戏可以分成先手和后手两个过程
- 先手:先手玩家拿完一张牌后就变成后手了,所以该玩家肯定会拿对自己最有利的牌
- 后手:后手玩家等对方拿完牌就变成先手了,但对方留下的肯定是最差的情况
public class CardsInLine {
public static int win1(int[] arr){
if (arr == null || arr.length == 0) {
return 0;
}
return Math.max(f(arr, 0, arr.length - 1), s(arr, 0, arr.length - 1));
}
/**
* 先手,在[i, j]范围上开始拿牌,返回胜者的分数
*/
public static int f(int[] arr, int i, int j) {
// Base case: 只剩下一张牌了,我先手,必须拿
if (i == j) {
return arr[i];
}
// 拿走最左边或最右边的牌,我变为后手,取最大值
return Math.max(arr[i] + s(arr, i + 1, j), arr[j] + s(arr, i, j - 1));
}
/**
* 后手,在[i, j]范围上拿牌,返回胜者的分数
*/
public static int s(int[] arr, int i, int j) {
// Base case: 只剩下一张牌了,我后手,不能拿,返回0
if (i == j) {
return 0;
}
// 对方拿走最左或最右边的牌,我变为先手,取最小值(因为对方会留给我最不利的情况)
return Math.min(f(arr, i + 1, j), f(arr, i, j - 1));
}
}
确实难想🤯
实战练习
- LeetCode原题:486. 预测赢家 - 力扣(LeetCode)
- 难度:Medium
三、逆序栈
题目五、逆序栈
题目描述:给你一个栈,请你逆序这个栈,不能申请额外的数据结构,只能使用递归函数。如何实现?
思路分析:不让用额外的数据结构,但其实可以利用系统的栈来完成栈反转的操作,而递归函数的本质就是利用系统栈。两个操作:
- 获取栈底元素:不断把栈顶元素拿出,如果不是最后一个元素,就把它放到系统栈保存,直到拿到最后一个元素,就弹栈返回,同时把系统栈中保存的元素放回原始栈,这样就实现了把栈底元素拿出来的效果。
- 反转栈:不断把栈底元素拿出,并放到系统栈中,直到原始栈为空,在系统弹栈的过程中,把保存的额栈底元素放回原始栈中。
public class ReverseStackUsingRecursion {
public static void reverse(Stack<Integer> stack) {
if (stack.isEmpty()) {
return;
}
// 取到栈底元素,再把剩余的栈反转,然后把之前的栈底元素放入栈顶。
int i = getLast(stack);
reverse(stack);
stack.push(i);
}
public static int getLast(Stack<Integer> stack) {
int result = stack.pop();
if (stack.isEmpty()) {
// 此时已经取到栈中最后一个元素,直接返回
return result;
} else {
// 栈不为空,接着取
int last = getLast(stack);
// 取完把之前的元素放回去
stack.push(result);
// 返回最后一个元素
return last;
}
}
}
总结:很奇妙,妙就完了🤤
四、字符串转化
题目描述:规定1和A对应、2和B对应、3和C对应.……那么一个数字字符串比如"111",就可以转化为"AAA"、“KA"和"AK” 。给定一个只有数字字符组成的字符串str,返回有多少种转化结果。
思路分析:经典爬楼梯问题,但要注意str[i]='0'
和str[i]+str[i+1]>'26'
的情况
public class ConvertToLetterString {
public static int typeOfTransform(String str) {
if (str == null || str.length() == 0) {
return 0;
}
return process(str.toCharArray(), 0);
}
/**
* i之前的位置,如何转化已经做过决定了
* @return i...有多少种转化的结果
*/
public static int process(char[] str, int i) {
// 已经转化完了,返回一种转化结果
if (i == str.length) {
return 1;
}
// 开头为0,不能转化,直接返回0种
if (str[i] == '0') {
return 0;
}
if (str[i] == '1') {
// 开头为1,可以一位转化,也可以两位转化
// i自己作为单独的部分,后续有多少种方法
int res = process(str, i + 1);
if (i + 1 < str.length) {
// (i和i+1) 作为单独的部分,后续有多少种方法
res += process(str, i + 2);
}
return res;
}
if (str[i] == '2') {
// 开头为2,可以一位转化,也可以两位转化,但不能超过26
// i自己作为单独的部分,后续有多少种方法
int res = process(str, i + 1);
if (i + 1 < str.length && str[i + 1] <= '6') {
// (i和i+1) 作为单独的部分并且没有超过26,后续有多少种方法
res += process(str, i + 2);
}
return res;
}
return process(str, i + 1);
}
}