Medium
题目描述
整数 x 的幂值定义为使用以下步骤将 x 转换为 1 所需的步数:
- 如果 x 是偶数,则 x = x / 2
- 如果 x 是奇数,则 x = 3 * x + 1
例如,x = 3 的幂值是 7,因为 3 需要 7 个步骤才能变成 1(3 –> 10 –> 5 –> 16 –> 8 –> 4 –> 2 –> 1)。
给定三个整数 lo、hi 和 k。任务是按照幂值升序排列区间 [lo, hi] 中的所有整数,如果两个或多个整数具有相同的幂值,则按升序排列它们。
返回区间 [lo, hi] 中按幂值排序的第 k 个整数。
注意,对于任何整数 x (lo <= x <= hi),保证 x 将使用这些步骤转换为 1,并且 x 的幂值适合 32 位有符号整数。
示例 1:
输入:lo = 12, hi = 15, k = 2
输出:13
解释:12 的幂值是 9 (12 --> 6 --> 3 --> 10 --> 5 --> 16 --> 8 --> 4 --> 2 --> 1)
13 的幂值是 9
14 的幂值是 17
15 的幂值是 17
按幂值排序的区间 [12,13,14,15]。对于 k = 2,答案是第二个元素,即 13。
注意 12 和 13 有相同的幂值,我们按升序排序它们。14 和 15 也是如此。
示例 2:
输入:lo = 7, hi = 11, k = 4
输出:7
解释:对应区间 [7, 8, 9, 10, 11] 的幂值数组是 [16, 3, 19, 6, 14]。
按幂值排序的区间是 [8, 10, 11, 7, 9]。
排序数组中的第四个数字是 7。
约束:
- 1 <= lo <= hi <= 1000
- 1 <= k <= hi - lo + 1
解题思路
这是一道经典的记忆化搜索+排序问题。
思路分析:
计算幂值(记忆化搜索):对于每个数字,我们需要按照给定规则计算到达1的步数。由于计算过程中会出现重复的中间结果,使用记忆化搜索可以大大提升效率。例如计算12的幂值时,会经过6、3等中间值,这些值的幂值可以被其他数字复用。
排序策略:计算出区间[lo, hi]内所有数字的幂值后,需要按照以下规则排序:
- 主要按幂值升序排列
- 幂值相同时,按数字本身升序排列
返回第k个元素:排序完成后直接返回第k-1个元素(0索引)。
算法步骤:
- 使用哈希表memo存储已计算的幂值
- 实现递归函数计算幂值,利用记忆化避免重复计算
- 生成区间内所有数字及其幂值的配对
- 按照排序规则对配对进行排序
- 返回第k个数字
推荐解法:记忆化搜索,时间复杂度低,代码简洁易懂。
代码实现
class Solution {
public:
unordered_map<int, int> memo;
int getPower(int x) {
if (x == 1) return 0;
if (memo.count(x)) return memo[x];
int result;
if (x % 2 == 0) {
result = 1 + getPower(x / 2);
} else {
result = 1 + getPower(3 * x + 1);
}
return memo[x] = result;
}
int getKth(int lo, int hi, int k) {
vector<pair<int, int>> nums;
for (int i = lo; i <= hi; i++) {
nums.push_back({getPower(i), i});
}
sort(nums.begin(), nums.end());
return nums[k - 1].second;
}
};
class Solution:
def getKth(self, lo: int, hi: int, k: int) -> int:
memo = {}
def getPower(x):
if x == 1:
return 0
if x in memo:
return memo[x]
if x % 2 == 0:
result = 1 + getPower(x // 2)
else:
result = 1 + getPower(3 * x + 1)
memo[x] = result
return result
nums = []
for i in range(lo, hi + 1):
nums.append((getPower(i), i))
nums.sort()
return nums[k - 1][1]
public class Solution {
private Dictionary<int, int> memo = new Dictionary<int, int>();
private int GetPower(int x) {
if (x == 1) return 0;
if (memo.ContainsKey(x)) return memo[x];
int result;
if (x % 2 == 0) {
result = 1 + GetPower(x / 2);
} else {
result = 1 + GetPower(3 * x + 1);
}
memo[x] = result;
return result;
}
public int GetKth(int lo, int hi, int k) {
List<(int power, int num)> nums = new List<(int, int)>();
for (int i = lo; i <= hi; i++) {
nums.Add((GetPower(i), i));
}
nums.Sort((a, b) => a.power == b.power ? a.num.CompareTo(b.num) : a.power.CompareTo(b.power));
return nums[k - 1].num;
}
}
var getKth = function(lo, hi, k) {
const memo = new Map();
function getPower(x) {
if (memo.has(x)) return memo.get(x);
let original = x;
let steps = 0;
while (x !== 1) {
if (x % 2 === 0) {
x = x / 2;
} else {
x = 3 * x + 1;
}
steps++;
}
memo.set(original, steps);
return steps;
}
const nums = [];
for (let i = lo; i <= hi; i++) {
nums.push([i, getPower(i)]);
}
nums.sort((a, b) => {
if (a[1] === b[1]) {
return a[0] - b[0];
}
return a[1] - b[1];
});
return nums[k - 1][0];
};
复杂度分析
| 复杂度类型 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 总体 | O(n log n + n·m) | O(m) |
说明:
- n = hi - lo + 1(区间内数字个数)
- m = 记忆化过程中遇到的不同数字个数
- 时间复杂度:计算所有幂值O(n·m),排序O(n log n)
- 空间复杂度:记忆化哈希表O(m),存储结果数组O(n)