剑指offer 打卡计划 | 每日进步一点点 | 第十七天

70 阅读4分钟

图片.png

一起养成写作习惯!这是我参与「掘金日新计划 · 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,按照题目所给定的规则将其翻译成字符串,问一个数字有多少种不同的翻译方法。

样例:

图片.png

我们先来理解一下题目的翻译规则,如样例所示,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开始)

图片.png

  • 2、将s[i]s[i - 1]组合起来翻译(组合的数字范围在10 ~ 25之间)。如果确定了第i个数和第i - 1个数的翻译方式,那么翻译前i个数字和翻译前i - 2个数的翻译方法数就是相同的,即f[i] = f[i - 2]。(s[]数组下标从1开始)

图片.png 最后将两种决策的方案数加起来,因此,状态转移方程为: 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]就表示从棋盘左上角走到棋盘右下角可以拿到的礼物最大价值,即为答案。

状态转移:

图片.png

由于限制了只会向下走或者向右走,因此到达(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];    
     }
 };