题目描述
我们将整数 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]
之间的整数按照它们的权重 升序排序 ,如果大于等于 2
个整数有 相同 的权重,那么按照数字自身的数值 升序排序 。
请你返回区间 [lo, hi]
之间的整数按权重排序后的第 k
个数。
注意,题目保证对于任意整数 x
(lo <= x <= hi)
,它变成 1 所需要的步数是一个 32
位有符号整数。
样例
输入: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 同理。
输入:lo = 1, hi = 1, k = 1
输出:1
输入:lo = 7, hi = 11, k = 4
输出:7
解释:区间内整数 [7, 8, 9, 10, 11] 对应的权重为 [16, 3, 19, 6, 14] 。
按权重排序后得到的结果为 [8, 10, 11, 7, 9] 。
排序后数组中第 4 个数字为 7 。
输入:lo = 10, hi = 20, k = 5
输出:13
输入:lo = 1, hi = 1000, k = 777
输出:570
提示:
-
1 <= lo <= hi <= 1000
-
1 <= k <= hi - lo + 1
算法1
记忆化搜索
-
1、枚举
[lo,hi]
区间的每个元素,由于某个数它到1
的最短距离是固定的,因此搜索过的元素用哈希表对它进行记录步数,可以让重复的元素不用搜索 -
2、把
[lo,hi]
区间中的每个元素全部放在小根堆中,按权值进行排序(权值相同按坐标排序),找出第k
小个
时间复杂度 $O(nlogn)$
Java 代码
class Solution {
static Map<Integer,Integer> map = new HashMap<Integer,Integer>();
static int INF = 0x3f3f3f3f;
static int dfs(int x)
{
if(x == 1) return 0 ;
if(map.containsKey(x)) return map.get(x);
int res = INF;
if(x % 2 == 0 && x / 2 > 0) res = Math.min(res,dfs(x / 2) + 1);
if(x % 2 != 0) res = Math.min(res,dfs(3 * x + 1) + 1);
map.put(x,res);
return res;
}
public int getKth(int lo, int hi, int k) {
PriorityQueue<Pair> q = new PriorityQueue<Pair>();
for(int i = lo;i <= hi;i ++)
{
int val = dfs(i);
q.add(new Pair(i,val));
}
while(k -- > 1)
{
q.poll();
}
return q.peek().idx;
}
}
class Pair implements Comparable<Pair>
{
int idx,val;
Pair(int idx,int val)
{
this.idx = idx;
this.val = val;
}
@Override
public int compareTo(Pair o) {
// TODO 自动生成的方法存根
return val == o.val ? Integer.compare(idx,o.idx) : Integer.compare(val, o.val);
}
}
算法2
时间复杂度 $O(nlogn)$
最短路模型
题目类似和 Acwing1100 抓住那头牛 思路类似,是一个最短路模型
从1
开始bfs
找到所有数离1
点的最短距离,由原来的
-
1、如果
x
是偶数,那么x = x / 2
,变成x = 2 * x
; -
2、如果
x
是奇数,那么x = 3 * x + 1
,变成x = (x - 1) / 3
;
注意:2
中必须在x - 1
能整除3
的前提下才有效
却有不相同之处,Acwing1100 抓住那头牛 必须用给定的范围的数的前提下才能找到,而这个题目,给定的区间的右端点最大值是1000
,即使超过1000
,却还能利用1000
以外的数,可以知道只要数足够大,找到的最短路的极限值越逼近真实值(没证明),下面代码中,当所有数的最大值取到10万
的时候,答案有效
Java 代码
class Solution {
static int N = 100010;
static int[] dist = new int[N];
static int INF = 0x3f3f3f3f;
static void bfs(int x,int lo,int hi)
{
Queue<Integer> q = new LinkedList<Integer>();
dist[x] = 0;
q.add(x);
while(!q.isEmpty())
{
int t = q.poll();
if((t * 2) % 2 == 0 && 2 * t <= 100000 && dist[2 * t] == INF)
{
dist[2 * t] = dist[t] + 1;
q.add(2 * t);
}
int temp = (t - 1) / 3;
if((t - 1) % 3 == 0 && temp % 2 != 0 && temp >= 1 && dist[temp] == INF)
{
dist[temp] = dist[t] + 1;
q.add(temp);
}
}
}
public int getKth(int lo, int hi, int k) {
PriorityQueue<Pair> q = new PriorityQueue<Pair>();
Arrays.fill(dist,INF);
bfs(1,lo,hi);
for(int i = lo;i <= hi;i ++) q.add(new Pair(i,dist[i]));
while(k -- > 1)
{
Pair t = q.poll();
System.out.println(t.idx + " " + t.val);
}
return q.peek().idx;
}
}
class Pair implements Comparable<Pair>
{
int idx,val;
Pair(int idx,int val)
{
this.idx = idx;
this.val = val;
}
@Override
public int compareTo(Pair o) {
// TODO 自动生成的方法存根
return val == o.val ? Integer.compare(idx,o.idx) : Integer.compare(val, o.val);
}
}