算法题解 - 牛客编程巅峰赛S1赛季第1场

往西汪

A. 移动字母

题目描述

给定一个只包含小写字母的字符串s,牛牛想将这个字符串中的所有 'a' 字母全部移动到字符串的末尾,而且保证其它字符的相对顺序不变。其中字符串s的长度 <= 1e6。

示例

输入

"abcavv"

输出

"bcvvaa"

解法一:遍历

思路分析

遍历字符串,所有非 'a' 字符前移,剩余位置补 'a' 即可。

时间复杂度:O(n)

空间复杂度:O(n)

代码实现

class Solution {
    /**
     * 
     * @param s string字符串 
     * @return string字符串
     */
    public String change (String s) {
        if(s == null) return s;
        int n = s.length();
        StringBuilder res = new StringBuilder();
        for(int i = 0; i < n; i++){
            if(s.charAt(i) != 'a') res.append(s.charAt(i));    //非 'a' 字符前移
        }
        while(res.length() < n) res.append('a'); // 末位补 'a' 
        return res.toString();
    }
}

B. 魔法数字

题目描述

一天,牛妹找牛牛做一个游戏,牛妹给牛牛写了一个数字 n ,然后又给自己写了一个数字 m ,她希望牛牛能执行最少的操作将他的数字转化成自己的。

操作共有三种,如下:

​ 1. 在当前数字的基础上加一,如:4 转化为 5

​ 2. 在当前数字的基础上减一,如:4 转化为 3

​ 3. 将当前数字变成它的平方,如:4 转化为 16

​ 你能帮牛牛解决这个问题吗?

输入:

给定 n, m 分别表示牛牛和牛妹的数字。

输出:

返回最少需要的操作数。

备注:

(1 ≤ n, m ≤ 1000)(1 ≤ n, m ≤ 1000)

示例

输入

3,10

输出

2

解法一:BFS

思路分析

对于每一个节点都有三个操作,也就是对应分出三个分支。在 i 层第一次找到数字 m,即表示最小需要的操作数为 i。

剪枝:

  1. 画出树图可以发现有大量的重复子结构,因此可以使用备忘录进行剪枝,已找到的数字不再往下搜寻。
  2. 若数字小于 0,则不必往下搜寻。因为负数再平方的操作数肯定比正数平方的操作数多。
  3. 若数字大于 m + (m - n) ,则不必往下搜寻。因为该数字只能通过 -1 操作来得到 m,且操作数比 n 一直 +1 得到 m 的操作数大。

时间复杂度:O(m)。最坏情况下把 1 到 2 * m - n 的数字全遍历了一遍。

空间复杂度:O(m)。同上。

代码实现

class Solution {
    /**
     * 返回最后要输出的答案
     * @param n int整型 表示牛牛的数字
     * @param m int整型 表示牛妹的数字
     * @return int整型
     */
    public int solve (int n, int m) {
        if(m <= n) return n - m;
        Queue<Integer> q = new LinkedList();
        Set<Integer> memo = new HashSet();//备忘录,出现过的数字不再往下搜寻
        q.offer(n);
        int res = 0;
        while(!q.isEmpty()){
            int size = q.size();
            while(size > 0){
                size--;
                int num = q.poll();
                if(num == m) return res;//找到了
                if(memo.contains(num)) continue;//已出现过
                memo.add(num);
                q.offer(num + 1);
                if(num - 1 > 0) q.offer(num - 1);//对负数剪枝
                if(num * num < 2 * m - n) q.offer(num * num);//对大数剪枝
            }
            res++;
        }
        return res;
    }
}

解法二:递归,跳转到离 m 最近的完全平方数

思路分析

m 和 n 的大小关系有以下两种情况:

  1. n >= m。此时只能通过 -1 操作得到 m。因此直接返回 n - m。
  2. n < m。此时要么一直 +1, 要么找到离 m 最近的完全平方数 k^2,先通过一系列操作跳转到 k,平方后再一直 +1 或一直 -1 得到 m。

时间复杂度:O(k)。从 n 到 m 最多经历 k 次平方。

空间复杂度:O(K)。因为是递归,所以要用到 k 个栈空间。

代码实现

class Solution {
    /**
     * 返回最后要输出的答案
     * @param n int整型 表示牛牛的数字
     * @param m int整型 表示牛妹的数字
     * @return int整型
     */
    public int solve (int n, int m) {
        if(m <= n) return n - m;
        int k = (int)Math.sqrt((double)m);
        if(m - k * k > (k + 1) * (k + 1) - m) k++;
        return Math.min(m - n, solve(n, k) + 1 + Math.abs(m - k * k));
    }
}

C. 牛妹的春游

题目描述

众所周知,牛妹要组织公司的出游。要准备面包和饮料。她买到的面包和饮料都是捆绑销售的,也就是说,一个大包装里面 x 个面包 + y个饮料,花费 t 元。

为了满足公司的要求,需要一定数量的面包和饮料。

你的任务就是帮助牛妹计算,为了满足公司需要,一共最少花费多少钱。

备注:

每种大包装只能最多买一个,所需面包 breadNum、饮料的总量 beverageNum 均不超过 2000。
牛妹一定能找到满足要求的方案让大家能够出游。 

示例

输入

5,60,[[3,36,120],[10,25,129],[5,50,250],[1,45,130],[4,20,119]]

输出

249

解法一:动态规划

思路分析

经典的 0-1 背包问题。如果不懂 0-1 背包的,可以自行百度,这里不多加阐述。

状态转移:dp[i][m][n] = min(dp[i - 1][m][n], dp[i][m - a][n - b])。要么不选包裹 i,要么选。

注意:

  1. 这里没有说包裹的数量,为了内存不爆掉,要进行状态压缩。
  2. 加上包裹里的物品后可能会超过所需的物品数量,这时把它当成所需的物品数量即可。

时间复杂度:O(packageNum breadNum beverageNum)

空间复杂度:O(breadNum * beverageNum)

代码实现

class Solution {
    /**
     * 
     * @param breadNum int整型 
     * @param beverageNum int整型 
     * @param packageSum int整型二维数组 每个一维数组中有三个数,依次表示这个包装里面的面包数量、饮料数量、花费
     * @return int整型
     */
    public int minCost (int breadNum, int beverageNum, int[][] packageSum) {
        int[][] dp = new int[breadNum + 1][beverageNum + 1];
        for(int[] arr: dp) Arrays.fill(arr, (int)1e7);
        dp[0][0] = 0;
        for(int[] pakg: packageSum){
            int bread = pakg[0], beverage = pakg[1], money = pakg[2];
            for(int i = breadNum; i >= 0; i--){
                for(int j = beverageNum; j >= 0; j--){
                                dp[i][j] = Math.min(dp[i][j], dp[Math.max(i - bread, 0)][Math.max(j - beverage, 0)] + money);
                }
            }
        }
        return dp[breadNum][beverageNum];
    }
}

写在最后

大家好,我是往西汪,一位坚持原创的新人博主。
如果本文对你有帮助,请点赞、评论二连。你的支持是我创作路上的最大动力。
谢谢大家!
也欢迎来公众号【往西汪】找我玩耍~

阅读 229
47 声望
431 粉丝
0 条评论
47 声望
431 粉丝
宣传栏