原题来自《一道整数求值作业》
题目描述:给定一个整数 X,找到组成的数字和 X 完全相同的,且大于 X 的最小的那个数;若不存在这样的数,输出 0
一开始把它想复杂了,后来想想只需将该数组成的数列从后往前枚举,然后判断当前位置往后的数列是否是降序即可,时间复杂度O(n),详细思路跟上文一致可以参考原文。
var a = 21544221; // 要求的数字 var s = a.toString(); var len = s.length; for(var i = len - 2; i >= 0; i--) { if(s[i] >= s[i + 1]) continue; for(var j = len - 1; j > i; j--) { if(s[j] > s[i]) { var index = j; break; } } break; } var arr = s.split(‘‘); if(i !== -1) { arr[i] = [arr[index], arr[index] = arr[i]][0]; var ans = arr.slice(0, i + 1).concat(arr.slice(i + 1).reverse()).join(‘‘); } console.log(~~ans);
之后我陷入了思考:如果不是要求比原数大的最小的数,而是第k大的呢?
为了不至于复杂度太高以致cpu假死,我把所求数暂定在int32范围内,写了个计算第k大数的暴力程序用来验证,复杂度O(n!)
function dfs(a, index) { if(index === len) { if(hash_ans[a] || a < num) return; ans.push(a); hash_ans[a] = true; return; } for(var i = 0; i < len; i++) { if(hash_pos[i]) continue; hash_pos[i] = true; dfs(~~a * 10 + ~~arr[i], index + 1); hash_pos[i] = false; } } var num = 131443134; // 要求的数字 var k = 818; // 比num大的第k大数 var arr = num.toString().split(‘‘); var len = num.toString().length; var ans = []; // 存放比num大的数 var hash_pos = {}; var hash_ans = {}; dfs(0, 0); ans.sort(); ans.length <= k ? console.log(0) : console.log(ans[k]);
然后开始思索第k大解,发现后向扫描仍然可解,但是复杂度应该不低,在没有想到其他解法前先一试。后向扫描的依据是所组成的新的数从前往后跟原来的数比较,在经过一系列相等的比较后,肯定会出现一个大于原数的相同位置。于是我们根据此枚举该位置,枚举到的位置的数用位置后面的比它大的数代替(以保证新的数比原数大),确定该位置的数之后,后面的位置进行全排列即是确定该位置后的所有解(注意全排列后需除相同数字的全排列),同时我们需要维护一个变量保存当前枚举位置后的总的解个数,如果个数还没达到k,则继续在该位置枚举更大的数,或者枚举位置继续前移;当发现总个数大于k时,该位置就确定是这个数了,接着进行第二轮枚举,枚举过程相似。在枚举的过程中反复地出现某个临界点(大于小于k),而出现临界点的同时该位置的数确定。
var A = []; A[0] = 1; for(var i = 1; i <= 10; i++) A[i] = A[i - 1] * i; var a = 131443134; var k = 818; var total = 0; var arr = a.toString().split(‘‘); // 字符串数组 var len = arr.length; var hash = []; // 记录后向扫描中0~9的数量 for(var i = 0; i <= 9; i++) hash[i] = 0; hash[a % 10] = 1; var maxn = a % 10; // 维护后向扫描中最大的数字 for(var i = len - 2; i >= 0; i--) { hash[~~arr[i]]++; if(maxn > ~~arr[i]) { // 可以交换 for(var j = ~~arr[i] + 1; j <= 9; j++) { // 该位置可以换的数字 if(!hash[j]) continue; var add = A[len - i - 1]; // 全排列 for(var l = 0; l <= 9; l++) { l === j ? add /= A[hash[l] - 1] : add /= A[hash[l]]; // 除去相同数字的全排列 } if(total + add >= k) { // 就是这个交换 hash[j]--; // 第i位变成了j var ans = a.toString().substring(0, i) + j + getAns(i); } else { total += add; } if(ans) break; } } maxn = Math.max(maxn, ~~arr[i]); if(ans) break; } function getAns(a) { // 第a位变成了b var res = ‘‘; for(var i = a + 1; i < len; i++) { for(var j = 0; j <= 9; j++) { // 从小到大枚举i位置的数字 if(!hash[j]) continue; var add = A[len - i - 1]; // 全排列 for(var l = 0; l <= 9; l++) l === j ? add /= A[hash[l] - 1] : add /= A[hash[l]]; // 除去相同数字的全排列 if(total + add >= k) { // 就是这个交换 hash[j]--; res += j; break; // i位置确定是j } else { total += add; } } } return res; } ans ? console.log(ans) : console.log(0);
解是有了,好像没涉及什么算法,倒是觉得像个复杂的模拟,看了下时间复杂度,最坏情况下应该会达到O(n*10*10),似乎效率还可以,但是我一开始想到这个问题的时候,似乎觉得会是一道动态规划,或者是按位dp?无奈对于算法了解不多,只能日后接触了再进行思考,也希望有更高效解法的博友留言交流
时间: 2024-11-10 05:31:13