这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战
把数组排成最小的数
剑指Offer 45.把数组排成最小的数
难度:中等
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
示例1:
输入: [10,2]
输出: "102"
示例2:
输入: [3,30,34,5,9]
输出: "3033459"
提示:0 < nums.length <= 100
说明:
- 输出结果可能非常大,所以你需要返回一个字符串而不是整数
- 拼接起来的数字可能会有前导0,最后结果不需要去掉前导0
题解
例如[10, 2],设a = 10,b = 2,则a+b = “102”,b+a=“210”,要拼接成最小的数,则:
-
如果
a + b < b + a,则ba比ab大,a应该在b的左边 -
如果
a + b < b + a,则ab比ba大,a应该在b的右边
因此a在b的左边,可以看出本题可以使用排序的方法解决。
法一 内置函数
/**
* @param {number[]} nums
* @return {string}
* 数组内排序后,再将数组转字符串。
*/
var minNumber = function(nums) {
return nums.sort((a, b) =>
('' + a + b) - ('' + b + a)
).join('');
};
- 时间复杂度:O(),sort函数使用了快排
- 空间复杂度:O()
法二 快排
避免被面试官打死,还是手写一下快排叭~
function quickSort(array, start, end) {
if (end - start < 1) return;
const target = array[start];
let l = start;
let r = end;
while (l < r) {
while (l < r && array[r] + target >= target + array[r]) {
r--;
}
array[l] = array[r];
while (l < r && array[l] + target < target + array[l]) {
l++;
}
array[r] = array[l];
}
array[l] = target;
quickSort(array, start, l - 1);
quickSort(array, l + 1, end);
return array;
}
var minNumber = function (nums) {
if (nums.length < 2) return String(nums);
// 将nums转为字符串数组
return quickSort(nums.map(String), 0, nums.length - 1).join("");
}
- 时间复杂度:O()
- 空间复杂度:O()
把数字翻译成字符串
剑指Offer 46.把数字翻译成字符串
难度:中等
给定一个数字,我们按照如下规则把它翻译为字符串:0翻译成“a”,1翻译成“b”,......,11翻译成“l”,......,25翻译成“z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
提示:0 <= num < 2^31
题解
法一 动态规划
思路:
定义dp[i]为翻译前i个数的方法数,翻译前0个数将dp[0]设为1,以满足动态规划需要。
这道题的转移方程为:
/**
* @param {number} num
* @return {number}
*/
var translateNum = function (num) {
const str = num.toString();
const len = str.length;
const dp = new Array(len + 1);
dp[0] = 1, dp[1] = 1;
for (let i = 2; i < len + 1; i++) {
const temp = Number(str[i - 2] + str[i - 1]);
if (temp >= 10 && temp <= 25) {
dp[i] = dp[i - 1] + dp[i - 2];
} else {
dp[i] = dp[i - 1];
}
}
return dp[len];
};
- 时间复杂度:O(),N为状态转移的次数
- 空间复杂度:O(),空间复杂度还能继续优化,不使用dp这个数组去存储结果,而是用两个变量去存dp项,优化如下:
var translateNum = function (num) {
const str = num.toString();
let pre = 1,
cur = 1;
for (let i = 2; i < str.length + 1; i++) {
const temp = Number(str[i - 2] + str[i - 1]);
if (temp >= 10 && temp <= 25) {
const t = cur; // 保存上个状态,即dp[i - 1]
cur = pre + cur; // dp[i] = dp[i - 2] + dp[i - 1]
pre = t; // 更新上上个状态,即dp[i - 2] = dp[i - 1];
} else {
pre = cur; // 将dp[i - 2]更新为dp[i - 1],即dp[i] = dp[i - 1],cur不用改变
}
}
return cur;
};
- 时间复杂度:O(),N为状态转移的次数
- 空间复杂度:O()
法二 数字求余
思路:
- 利用求余数运算num%10和求整运算num / 10,可以获得数字num的各位数字(获取顺序为个位、十位、百位...) 。
- 通过求余和求整方式实现从右向左的遍历运算。
转移方程为:
var translateNum = function (num) {
let a = 1,
b = 1,
x, y = num % 10; // b在a的后面,y在x的后面
while (num) {
num = Math.floor(num / 10);
x = num % 10;
let tmp = 10 * x + y; // tmp代表的是从右到左一直组合成两位数的那个数,用于判断
let c = (tmp >= 10 && tmp <= 25) ? a + b : a;
b = a;
a = c;
y = x;
}
return a;
};
- 时间复杂度:O()
- 空间复杂度:O()
法三 DFS
这道题抽象可以理解为树模型,即求一棵二叉树从根节点到达叶子结点的路径总数。
var translateNum = function (num) {
const str = num.toString();
const dfs = (str, pos) => { // pos用于指向某个字符
let len = str.length;
if (pos >= len - 1) return 1;
const temp = Number(str[pos] + str[pos + 1]);
if (temp >= 10 && temp <= 25) {
return dfs(str, pos + 1) + dfs(str, pos + 2);
}
return dfs(str, pos + 1);
}
return dfs(str, 0);
}
- 时间复杂度:O(),此方法递归时,会产生重复子树,因此我们可以进行优化。
- 空间复杂度:O()
法四 DFS + 备忘录
DFS中会产生重复的子树,当我们在优先递归左子树时,若在右侧遇到重复子树,没有必要重新计算。因此,我们需要将计算过的结果用一个备忘录记录下来,再遇到就直接拿来用。这里我们用map来作备忘录。
var translateNum = function (num) {
const str = num.toString();
const len = str.length;
const map = new Map();
map.set(len - 1, 1); // 叶子节点的父节点,其pos指向的是最后一个
map.set(len, 1); // 叶子节点的值
const dfs = (str, pos, map) => {
if (map.get(pos)) return map.get(pos); // 若有重复,则直接发挥重复的。
const temp = Number(str[pos] + str[pos + 1]);
if (temp >= 10 && temp <= 25) {
map.set(pos, dfs(str, pos + 1, map) + dfs(str, pos + 2, map));
} else {
map.set(pos, dfs(str, pos + 1, map));
}
return map.get(pos);
}
return dfs(str, 0, map);
}
- 时间复杂度:O()
- 空间复杂度:O()
坚持每日一练!前端小萌新一枚,希望能点个赞哇~