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。
剪枝:
- 画出树图可以发现有大量的重复子结构,因此可以使用备忘录进行剪枝,已找到的数字不再往下搜寻。
- 若数字小于 0,则不必往下搜寻。因为负数再平方的操作数肯定比正数平方的操作数多。
- 若数字大于 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 q = new LinkedList();
Set 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 的大小关系有以下两种情况:
- n >= m。此时只能通过 -1 操作得到 m。因此直接返回 n - m。
- 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,要么选。
注意:
- 这里没有说包裹的数量,为了内存不爆掉,要进行状态压缩。
- 加上包裹里的物品后可能会超过所需的物品数量,这时把它当成所需的物品数量即可。
时间复杂度: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];
}
}
写在最后
大家好,我是往西汪,一位坚持原创的新人博主。
如果本文对你有帮助,请点赞、评论二连。你的支持是我创作路上的最大动力。
谢谢大家!
也欢迎来公众号【往西汪】找我玩耍~