字典序

给定整数n和m, 将1到n的这n个整数按字典序排列之后, 求其中的第m个数。
对于n=11, m=4, 按字典序排列依次为1, 10, 11, 2, 3, 4, 5, 6, 7, 8, 9, 因此第4个数是2. 
对于n=200, m=25, 按字典序排列依次为1 10 100 101 102 103 104 105 106 107 108 109 11 110 111 112 113 114 115 116 117 118 119 12 120 121 122 123 124 125 126 127 128 129 13 130 131 132 133 134 135 136 137 138 139 14 140 141 142 143 144 145 146 147 148 149 15 150 151 152 153 154 155 156 157 158 159 16 160 161 162 163 164 165 166 167 168 169 17 170 171 172 173 174 175 176 177 178 179 18 180 181 182 183 184 185 186 187 188 189 19 190 191 192 193 194 195 196 197 198 199 2 20 200 21 22 23 24 25 26 27 28 29 3 30 31 32 33 34 35 36 37 38 39 4 40 41 42 43 44 45 46 47 48 49 5 50 51 52 53 54 55 56 57 58 59 6 60 61 62 63 64 65 66 67 68 69 7 70 71 72 73 74 75 76 77 78 79 8 80 81 82 83 84 85 86 87 88 89 9 90 91 92 93 94 95 96 97 98 99 因此第25个数是120… 

输入描述:
输入仅包含两个整数n和m。

数据范围: 

对于20%的数据, 1 <= m <= n <= 5 ; 

对于80%的数据, 1 <= m <= n <= 10^7 ; 

对于100%的数据, 1 <= m <= n <= 10^18.


输出描述:
输出仅包括一行, 即所求排列中的第m个数字.
示例1

输入

11 4

输出

2

思路:刚开始思路是dfs,TLE

public class TLE {
	
	static boolean f = false;
	static long cnt;
	
	public static void main(String[] args) {
		f = false;
		Scanner sc = new Scanner(System.in);
		long n = sc.nextLong(), m = sc.nextLong();
		if(n <= 9)	{
			System.out.println(m);
			return;
		}
		
		cnt = m;
		for(int i=1; i<=9; i++) {
			cnt --;
			dfs(n, m, i);
		}
	}

	private static void dfs(long n, long m, long num) {
		if(f)	return;
		if(cnt == 0) {
			System.out.println(num);
			f = true;
			return;
		}
		
		for(int i=0; i<=9; i++) {
			if(10*num+i <= n) {
				cnt--;
				dfs(n, m, 10*num+i);
			}
		}
	}
}


既然是字典序,那么很自然,我们可以考虑使用字典树来实现,
但是,这里并不需要真的生成这个字典树,而只需要计算对应分支的节点数就行了。(但是要想象自己已经建立了Trie树)

计算分支节点数,那么很简单,节点数就是上级节点*10,总的节点数= 1 + (1 * 10) + (1 * 10 * 10) + (1 * 10  * 10 * 10) +……,这里需要注意最后的边界,n以内的节点数,那么,最后相加的时候必须要把n+1 ~ (1 * 10 * 10 *……)这几个数去掉。
比如当前遍历Tire数到12, 就是遍历了2层,1和2,则计算方式为:



既然知道了如何计算字典树分支的节点数,要想知道第m个数是什么,那么也就是找第m个节点,
1. 首先从1开始,如果1分支的节点数>m,那么第m个数肯定是以1开头,进一步搜索其子节点,搜索子节点时不用再搜索1了,所以是搜索1分支的第m-1个节点。
2. 如果1分支的节点数<m, 那么所查找的数肯定不是1开头,那么开始搜索2分支,在2分支中,所要找的数应该是第(m-(1分支节点数))个数。

重复这个过程,要么搜索子节点,要么搜索兄弟节点,知道最终m==0,也就是当前节点就是所要搜索的节点。
package l3;


import java.util.Scanner;

/*
 * 既然是字典序,那么很自然,我们可以考虑使用字典树来实现,
 * 但是,这里并不需要真的生成这个字典树,而只需要计算对应分支的节点数就行了。
 * 
 * 写的时候想象一下自己建立了trie树
 */
public class Main {
	
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		long n = sc.nextLong(), m = sc.nextLong();
		
		long ret = 1;
		while(m != 0) {
			long cntOfChild = getCntOfChild(ret, n);
			if(cntOfChild >= m) {
				//当子节点数大于等于m时,第m个数就在子节点中寻找,res*10为子节点的第一个,m递减直到m=0就找到了那个数
				m --;
				if(m == 0)	break;
				ret = ret * 10;
				
			} else {
				//当子节点数小于m时,第m个数就要在右边的节点中寻找子节点,m减掉当前子节点数,结果加1就到了右边相邻的节点
				m -= cntOfChild;
				ret ++;
			}
		}
		
		System.out.println(ret);
	}

	// 找到小于n的,以ret开头的树的个数
	// 就是遍历trie数到ret的时候的子数中节点的个数
	private static long getCntOfChild(long ret, long n) {
		long sum = 1, t = 10;
		for(; ret * t <=n; t *= 10) {
			if(ret * t + t - 1 <= n)
				sum += t;
			else
				sum += n - ret * t + 1;
		}
		return sum;
	}

}


LC类似的题:386 http://blog.csdn.net/zjucor/article/details/71809557

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值