每日算法总结——打印一个字符串的全部排列、预测赢家、逆序栈、字符串转化

一、打印一个字符串的全部排列

题目描述:打印一个字符串的全部排列,要求不要出现重复的排列

思路分析:对于一个字符串char[] str,从0length - 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;
    }
}
实战练习

二、预测赢家

题目描述:给定一个整型数组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));
    }
}

确实难想🤯

实战练习

三、逆序栈

题目五、逆序栈

题目描述:给你一个栈,请你逆序这个栈,不能申请额外的数据结构,只能使用递归函数。如何实现?

思路分析:不让用额外的数据结构,但其实可以利用系统的栈来完成栈反转的操作,而递归函数的本质就是利用系统栈。两个操作:

  • 获取栈底元素:不断把栈顶元素拿出,如果不是最后一个元素,就把它放到系统栈保存,直到拿到最后一个元素,就弹栈返回,同时把系统栈中保存的元素放回原始栈,这样就实现了把栈底元素拿出来的效果。
  • 反转栈:不断把栈底元素拿出,并放到系统栈中,直到原始栈为空,在系统弹栈的过程中,把保存的额栈底元素放回原始栈中。
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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值