一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情。
剑指 Offer 45. 把数组排成最小的数
思路
(排序) O(nlogn)
自定义排序规则,如果拼接ab 可以比拼接 ba更小的话,我们选择拼接ab。
时间复杂度分析: 排序的时间复杂度为O(nlogn)。
c++代码
class Solution {
public:
// 自定义排序规则,如果拼接ab 可以比拼接 ba更小的话,我们选择拼接ab
static bool cmp(int a, int b)
{
string as = to_string(a), bs = to_string(b);
return as + bs < bs + as;
}
string minNumber(vector<int>& nums) {
sort(nums.begin(), nums.end(), cmp);
string res;
for(auto x : nums)
res += to_string(x);
return res;
}
};
剑指 Offer 46. 把数字翻译成字符串
思路
(动态规划) O(logn)
给定我们一个数字num,按照题目所给定的规则将其翻译成字符串,问一个数字有多少种不同的翻译方法。
样例:
我们先来理解一下题目的翻译规则,如样例所示,num = 12258,可以分为两种情况:
- 1、将每一位单独翻译,因此可以翻译成
"bccfi"。 - 2、将相邻两位组合起来翻译(组合的数字范围在
10 ~ 25之间),因此可以翻译成"bwfi","bczi","mcfi"和"mzi"。
两种情况是或的关系,互不影响,将其相加,那么12258共有5种不同的翻译方式。为了可以很方便的将数字的相邻两位组合起来,我们可以先将数字num转化成字符串数组s[],下面来讲解动态规划的做法。
状态表示:
我们定义f[i]表示前i个数字一共有多少种不同的翻译方法。那么,f[n]就表示前n个数字一共有多少种不同的翻译方法,即为答案。
状态计算:
假设字符串数组为s[],对于第i个数字,分成两种决策:
- 1、单独翻译
s[i]。由于求的是方案数,如果确定了第i个数字的翻译方式,那么翻译前i个数字和翻译前i - 1个数的方法数就是相同的,即f[i] = f[i - 1]。(s[]数组下标从1开始)
- 2、将
s[i]和s[i - 1]组合起来翻译(组合的数字范围在10 ~ 25之间)。如果确定了第i个数和第i - 1个数的翻译方式,那么翻译前i个数字和翻译前i - 2个数的翻译方法数就是相同的,即f[i] = f[i - 2]。(s[]数组下标从1开始)
最后将两种决策的方案数加起来,因此,状态转移方程为:
f[i] = f[i - 1] + f[i - 2]。
初始化:
f[0] = 1,翻译前0个数的方法数为1。
为什么一个数字都没有的方案数是1?
f[0]代表翻译前0个数字的方法数,这样的状态定义其实是没有实际意义的,但是f[0]的值需要保证边界是对的,即f[1]和f[2]是对的。比如说,翻译前1个数只有一种方法,将其单独翻译,即f[1] = f[1 - 1] = 1。翻译前两个数,如果第1个数和第2个数可以组合起来翻译,那么f[2] = f[1] + f[0] = 2 ,否则只能单独翻译第2个数,即f[2] = f[1] = 1。因此,在任何情况下f[0]取1都可以保证f[1]和f[2]是正确的,所以f[0]应该取1。
实现细节:
我们将数字num转为字符串数组s[],在推导状态转移方程时,假设的s[]数组下标是从1开始的,而实际中的s[]数组下标是从0开始的,为了一 一对应,在取组合数字的值时,要把s[i - 1] 和 s[i]的值往前错一位,取s[i - 1]和s[i - 2],即组合值t = (s[i - 2] - '0') * 10 + s[i - 1] - '0'。
在推导状态转移方程时,一般都是默认数组下标从1开始,这样的状态表示可以和实际数组相对应,理解起来会更清晰,但在实际计算中要错位一下,希望大家注意下。
时间复杂度分析: O(logn),计算的次数是nums的位数,即logn,以10为底。
c++代码
class Solution {
public:
int translateNum(int num) {
string s = to_string(num);
int n = s.size();
vector<int> f(n + 1);
f[0] = 1; // 初始化
for(int i = 1; i <= n; i++){
f[i] = f[i - 1];
if(i > 1){
int t = (s[i - 2] - '0') * 10 + s[i - 1] - '0';
if(t >= 10 && t <= 25)
f[i] += f[i - 2];
}
}
return f[n];
}
};
剑指 Offer 47. 礼物的最大价值
思路
(动态规划) O(m*n)
状态表示: f[i,j]表示从(0,0)走到(i,j)可以拿到的礼物最大价值。那么,f[n - 1][m - 1]就表示从棋盘左上角走到棋盘右下角可以拿到的礼物最大价值,即为答案。
状态转移:
由于限制了只会向下走或者向右走,因此到达(i,j)有两条路径
- 从上方转移过来,
f[i][j] = f[i-1][j] + grid[i][j] - 从左方转移过来,
f[i][j] = f[i][j-1] + grid[i][j]
因此,状态计算方程为: f[i][j] = max(f[i - 1][j], f[i][j - 1]) + grid[i][j], 从向右和向下两条路径中选择礼物价值最大的转移过来,再加上grid[i][j]的值。
初始化: f[0][0] = grid[0][0]。
c++代码
class Solution {
public:
int maxValue(vector<vector<int>>& grid) {
int n = grid.size(), m = grid[0].size();
vector<vector<int>>f(n + 1, vector<int>(m + 1));
f[0][0] = grid[0][0];
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++){
if(!i && !j) continue;
if(i) f[i][j] = max(f[i][j], f[i - 1][j] + grid[i][j]);
if(j) f[i][j] = max(f[i][j], f[i][j - 1] + grid[i][j]);
}
return f[n - 1][m - 1];
}
};