[英]Algorithm to print all combination of letters of the given string in lexicographical order
[英]Get next number from range in lexicographical order (without container of all stringifications)
您如何/将设计一个函数,该函数在每次调用时以字符串表示的字典顺序按指定的数字范围返回下一个值...?
示例 :范围8..203-> 10,100..109,11,110..119,12,120..129,13,130..139,...,19,190..199,20 ,200..203、30..99。
约束 :索引0 ..〜INT_MAX,固定空间,O(范围长度)性能,最好是“惰性”,因此,如果您中途停止迭代,就不会浪费处理工作量。 在生成要排序的字符串时,请不要发布数字迭代的蛮力“解决方案” 。
实用工具 :如果您要生成最终需要按字典顺序显示或处理的数据,则字典顺序系列可保证根据需要延迟生成,减少内存需求并消除排序。
背景 :今天回答这个问题时 ,我的解决方案按数字顺序(即8、9、10、11、12)输出,而不是按问题按字典顺序(10、11、12、8、8)输出。 我以为写或找到解决方案会很容易,但是我的Google-foo让我失望了,这比我想像的要难,所以我想我可以在这里收集/贡献。
(标记为C ++,因为它是我的主要语言,并且我个人对C ++解决方案特别感兴趣,但欢迎任何欢迎)
有人投票结束了此问题,因为我要么对解决的问题(hmmmm!?!;-P)没有表现出最低的了解,要么没有尝试解决。 我的解决方案被发布为答案,因为我很高兴能在Stack Overflow智慧的残酷风潮中对其进行评论和重新注册。...O_o
这实际上很容易。 首先观察:
定理 :如果两个数字x
和y
使得x < y
在数列中,并且这些数字具有相同的数字数,则x
在y
之前。
证明 :让我们将x
的数字视为xn..x0
并将y
数字视为yn...y0
。 让我们以这两个不同的最左边的数字为例,假设它们位于索引i
。 因此,我们有:
y = yn...yiy(i-1)...y0
x = yn...yix(i-1)...x0
因为从n
到i
所有数字都是相同的。 如果x < y
,则在数学上:
x(i-1) < y(i-1)
从字典上讲,如果数字x(i-1)
小于数字y(i-1)
,则x
在y
之前。
该定理意味着,在指定的[a, b]
范围内,您拥有的数字位数不同,但是具有相同数字位数的数字按其数学顺序排列。
在此基础上,这是一个简单的算法。 首先,假设a
有m
数字, b
有n
数字( n >= m
)
1. create a heap with lexicographical order
2. initially, insert `a` and `10^i` for i in [n + 1, m]
3. while the heap is not exhausted
3.1. remove and yield the top of the heap (`next`) as next result
3.2. if `next + 1` is still in range `[a, b]` (and doesn't increase in digits), insert it in heap
笔记:
假设N = b - a
,该算法使用的额外空间为O(log N)
,其时间复杂度为O(N * log log N)
。
这是我在Python中的尝试:
import math
#iterates through all numbers between start and end, that start with `cur`'s digits
def lex(start, end, cur=0):
if cur > end:
return
if cur >= start:
yield cur
for i in range(0,10):
#add 0-9 to the right of the current number
next_cur = cur * 10 + i
if next_cur == 0:
#we already yielded 0, no need to do it again
continue
for ret in lex(start, end, next_cur):
yield ret
print list(lex(8, 203))
结果:
[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, 20, 200,
201, 202, 203, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 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]
这使用O(log(end))堆栈空间,该空间受INT_MAX限制,因此对于典型的16位int而言,它的深度不超过五个调用。 它以O(end)时间运行,因为它必须遍历小于start
数字,然后才能开始产生有效数字。 如果start
和end
点较大且靠在一起,则这可能比O(end-start)差得多。
在我的机器上遍历lex(0, 1000000)
大约需要六秒钟,因此它似乎比Tony的方法慢,但比Shahbaz的方法快。 当然,由于我使用的是其他语言,因此直接进行比较具有挑战性。
这有点混乱,所以我很好奇看到其他人如何解决它。 在增量运算符中显式处理了很多边缘情况!
从low
到high
范围:
high
数字始终后面跟0的版本(例如12-> 120) high
以外的数字后跟下一个整数 low
数与high
数一样high
,您将在high
数之后完成(返回前哨high + 1
)
999...
结束,比high
少一位 low
template <typename T>
std::string str(const T& t)
{
std::ostringstream oss; oss << t; return oss.str();
}
template <typename T>
class Lex_Counter
{
public:
typedef T value_type;
Lex_Counter(T from, T to, T first = -1)
: from_(from), to_(to),
min_size_(str(from).size()), max_size_(str(to).size()),
n_(first != -1 ? first : get_first()),
max_unit_(pow(10, max_size_ - 1)), min_unit_(pow(10, min_size_ - 1))
{ }
operator T() { return n_; }
T& operator++()
{
if (n_ == 0)
return n_ = 1;
if (n_ < max_unit_ && n_ * 10 <= to_)
return n_ = n_ * 10; // e.g. 10 -> 100, 89 -> 890
if (n_ % 10 < 9 && n_ + 1 <= to_)
return ++n_; // e.g. 108 -> 109
if (min_size_ == max_size_
? n_ == to_
: (n_ == max_unit_ - 1 && to_ < 10 * max_unit_ - 10 || // 99/989
n_ == to_ && to_ >= 10 * max_unit_ - 10)) // eg. 993
return n_ = to_ + 1;
// increment the right-most non-9 digit
// note: all-9s case handled above (n_ == max_unit_ - 1 etc.)
// e.g. 109 -> 11, 19 -> 2, 239999->24, 2999->3
// comments below explain 230099 -> 230100
// search from the right until we have exactly non-9 digit
for (int k = 100; ; k *= 10)
if (n_ % k != k - 1)
{
int l = k / 10; // n_ 230099, k 1000, l 100
int r = ((n_ / l) + 1) * l; // 230100
if (r > to_ && r / 10 < from_)
return n_ = from_; // e.g. from_ 8, r 20...
while (r / 10 >= from_ && r % 10 == 0)
r /= 10; // e.g. 230100 -> 2301
return n_ = r <= from_ ? from_ : r;
}
assert(false);
}
private:
T get_first() const
{
if (min_size_ == max_size_ ||
from_ / min_unit_ < 2 && from_ % min_unit_ == 0)
return from_;
// can "fall" from e.g. 321 to 1000
return min_unit_ * 10;
}
T pow(T n, int exp)
{ return exp == 0 ? 1 : exp == 1 ? n : 10 * pow(n, exp - 1); }
T from_, to_;
size_t min_size_, max_size_;
T n_;
T max_unit_, min_unit_;
};
在标准的Intel机器/单线程,MS编译器为-O2的情况下,我在一秒钟内可以算出0到10亿。
在下面尝试使用Shahbaz解决方案的同一台机器/线束需要3.5秒才能计数到100,000。 也许std::set
不是一个好的堆/堆替代,或者有更好的使用方法? 欢迎任何优化建议。
template <typename T>
struct Shahbaz
{
std::set<std::string> s;
Shahbaz(T from, T to)
: to_(to)
{
s.insert(str(from));
for (int n = 10; n < to_; n *= 10)
if (n > from) s.insert(str(n));
n_ = atoi(s.begin()->c_str());
}
operator T() const { return n_; }
Shahbaz& operator++()
{
if (s.empty())
n_ = to_ + 1;
else
{
s.erase(s.begin());
if (n_ + 1 <= to_)
{
s.insert(str(n_ + 1));
n_ = atoi(s.begin()->c_str());
}
}
return *this;
}
private:
T n_, to_;
};
性能代码供参考...
void perf()
{
DWORD start = GetTickCount();
int to = 1000 *1000;
// Lex_Counter<int> counter(0, to);
Shahbaz<int> counter(0, to);
while (counter <= to)
++counter;
DWORD elapsed = GetTickCount() - start;
std::cout << '~' << elapsed << "ms\n";
}
一些Java代码(从中派生C ++代码应该是微不足道的),与Kevin的Python解决方案非常相似:
public static void generateLexicographical(int lower, int upper)
{
for (int i = 1; i < 10; i++)
generateLexicographical(lower, upper, i);
}
private static void generateLexicographical(int lower, int upper, int current)
{
if (lower <= current && current <= upper)
System.out.println(current);
if (current > upper)
return;
for (int i = 0; i < 10; i++)
generateLexicographical(lower, upper, 10*current + i);
}
public static void main(String[] args)
{
generateLexicographical(11, 1001);
}
如果-语句的顺序并不重要,和一个可以由else
其他的,但在任何的方式很奇怪他们改变使得它需要大约长20%。
这只是从1到10的每个数字开始,然后递归地将每个可能的数字0到10追加到该数字,直到得到的数字大于上限。
类似地,它使用O(log upper)
空间(每个数字都需要一个堆栈帧)和O(upper)
时间(我们从1
到upper
)。
I / O显然是这里最耗时的部分。 如果将其删除并仅通过增加变量来替换,则generateLexicographical(0, 100_000_000);
大约需要4秒钟,但这绝不是一个适当的基准。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.