package class49; // 这道题在leetcode上,所有题解都只能做到O( (logN) 平方)的解 // 我们课上讲的是O(logN)的解 // 打败所有题解 public class Problem_0440_KthSmallestInLexicographicalOrder { public static int[] offset = { 0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; public static int[] number = { 0, 1, 11, 111, 1111, 11111, 111111, 1111111, 11111111, 111111111, 1111111111 }; public static int findKthNumber(int n, int k) { // 数字num,有几位,len位 // 65237, 5位,len = 5 int len = len(n); // 65237, 开头数字,6,first int first = n / offset[len]; // 65237,左边有几个? int left = (first - 1) * number[len]; int pick = 0; int already = 0; if (k <= left) { // k / a 向上取整-> (k + a - 1) / a pick = (k + number[len] - 1) / number[len]; already = (pick - 1) * number[len]; return kth((pick + 1) * offset[len] - 1, len, k - already); } int mid = number[len - 1] + (n % offset[len]) + 1; if (k - left <= mid) { return kth(n, len, k - left); } k -= left + mid; len--; pick = (k + number[len] - 1) / number[len] + first; already = (pick - first - 1) * number[len]; return kth((pick + 1) * offset[len] - 1, len, k - already); } public static int len(int n) { int len = 0; while (n != 0) { n /= 10; len++; } return len; } public static int kth(int max, int len, int kth) { // 中间范围还管不管的着! // 有任何一步,中间位置没命中,左或者右命中了,那以后就都管不着了! // 但是开始时,肯定是管的着的! boolean closeToMax = true; int ans = max / offset[len]; while (--kth > 0) { max %= offset[len--]; int pick = 0; if (!closeToMax) { pick = (kth - 1) / number[len]; ans = ans * 10 + pick; kth -= pick * number[len]; } else { int first = max / offset[len]; int left = first * number[len]; if (kth <= left) { closeToMax = false; pick = (kth - 1) / number[len]; ans = ans * 10 + pick; kth -= pick * number[len]; continue; } kth -= left; int mid = number[len - 1] + (max % offset[len]) + 1; if (kth <= mid) { ans = ans * 10 + first; continue; } closeToMax = false; kth -= mid; len--; pick = (kth + number[len] - 1) / number[len] + first; ans = ans * 10 + pick; kth -= (pick - first - 1) * number[len]; } } return ans; } }