在扩展字符串中查找第 k 个元素
Find kth element in an expanding string
给定一个形式为 AB2C3
的字符串和一个 int k
.将字符串展开为 ABABC3
然后ABABCABABCABABC
.任务是找到第 k个元素。 您的内存有限,因此无法扩展整个字符串。你只需要找到第 k个元素。
我不确定该怎么做。这是在一次编码面试中向我的朋友提出的,我已经考虑了很多,但我没有得到一个有效的解决方案。
更新:接下来是O(1)
空间和O(N)
时间版本。见下文。
原始解决方案使用O(1)
空间和O(N log k)
时间,其中n
是未展开字符串的大小:
char find_kth_expanded(const char* s, unsigned long k) {
/* n is the number of characters in the expanded string which we've
* moved over.
*/
unsigned long n = 0;
const char *p = s;
for (;;) {
char ch = *p++;
if (isdigit(ch)) {
int reps = ch - '0';
if (n * reps <= k)
n *= reps;
else {
/* Restart the loop. See below. */
k = k % n;
p = s;
n = 0;
}
}
else if (ch == 0 || n++ == k)
return ch;
}
}
该函数只是在字符串中从左向右移动,跟踪它已传递的扩展字符串中的字符数。如果该值达到 k
,则我们在扩展字符串中找到了第 k
个字符。如果重复会跳过字符k
,那么我们将k
减少到重复中的索引,然后重新启动扫描。
很明显,它占用了O(1)
空间。为了证明它在O(N log k)
中运行,我们需要计算循环重新启动的次数。如果我们要重新启动,则k≥n
,因为否则我们之前会在 n
.如果k≥2n
那么n≤k/2
如此k%n≤k/2
.如果k<2n
则k%n = k-n
.但是n>k/2
,所以k-n<k-k/2
,因此k%n<k/2
.
因此,当我们重新启动时,k
的新值最多是旧值的一半。因此,在最坏的情况下,我们将重新启动log2k
次。
虽然上述解决方案很容易理解,但我们实际上可以做得更好。无需重新启动扫描,我们可以在扫描过去k
(展开(字符后向后扫描。在向后扫描期间,我们需要始终通过取其模数基数来校正k
当前段中的范围:
/* Unlike the above version, this one returns the point in the input
* string corresponding to the kth expanded character.
*/
const char* find_kth_expanded(const char* s, unsigned long k) {
unsigned long n = 0;
while (*s && k >= n) {
if (isdigit(*s))
n *= *s - '0';
else
++n;
++s;
}
while (k < n) {
--s;
if (isdigit(*s)) {
n /= *s - '0';
k %= n;
}
else
--n;
}
return s;
}
上述函数都没有正确处理乘数为 0 且 k
小于段长度乘以 0 的情况。如果0
是合法乘数,一个简单的解决方案是反向扫描字符串以查找最后一个0
,并从以下字符开始find_kth_expanded。由于反向扫描是O(N)
的,时间复杂度不会改变。
首先,看一下字符串。字符串由两部分组成:数据部分和信息部分。数据部分包含要重复的实际字符串,信息部分包含实际重复次数。
如果您了解这一点,那么您已经了解了数据的模式。
下一步是处理特殊情况,例如负重复数,实数重复数而不是整数。您实际上可以说 repeat 是在最后找到的字符串的子字符串,并由它只能包含数字的规则定义。如果你这样想,那么你将有两种情况:字符串要么以数字结尾,要么字符串不以数字结尾。在第一种情况下,我们有一个有效的重复编号,在第二种情况下,我们必须抛出异常。
如果我们仍然有一个有效的重复数字,那么它可能有多个数字,因此,您必须浏览字符串以找到未与数字关联的最后一个索引。该索引后面的子字符串是信息部分,即 rp(重复数字(。另外,这个索引实际上等于你的数据部分的长度 - 1,我们称之为长度 L。
如果您有有效的 rp,则结果字符串的实际长度为 L * rp。
现在,如果 k 是一个 int,如果它是负数,你仍然必须抛出异常,而且,k
如果一切都有效,则实际值的索引计算公式为:
k % L
您不必实际计算结果字符串来确定第 k 个字符,因为您可以使用具有重复模式的事实。
这实际上是一个有趣的益智程序。
下面是用 C# 编写的答案。这是一种皈依C++的练习!有 2 个递归函数,一个计算扩展字符串的长度,另一个查找给定字符串的第 k个字符。它从右到左向后工作,一次剥离一个字符。
using System;
using System.Collections.Generic;
using System.Text;
namespace expander
{
class Program
{
static void Main(string[] args)
{
string y = "AB2C3";
Console.WriteLine("length of expanded = {0} {1}", y, length(y));
for(uint k=0;k<length(y);k++)
{
Console.WriteLine("found {0} = {1}",k,find(k,y));
}
}
static char find(uint k, string s)
{
string left = s.Substring(0, s.Length - 1);
char last = s[s.Length - 1];
uint len = length(left);
if (last >= '0' && last <= '9')
{
if (k > Convert.ToInt32(last -'0') * len) throw new Exception("k out of range");
uint r = k % len;
return find(r, left );
}
if (k < len) return find(k, left);
else if (k == len) return last;
else throw new Exception("k out of range");
}
static uint length(string s)
{
if (s.Length == 0) return 0;
char x = s[s.Length - 1];
uint len = length(s.Substring(0, s.Length - 1));
if (x >= '0' && x <= '9')
{
return Convert.ToUInt32(x - '0') * len;
}
else
{
return 1 + len;
}
}
}
}
下面是示例输出,它表明,如果迭代 k 的所有有效值(0 到 len-1(,find
函数将复制扩展。
length of expanded AB2C3 is 15
if k=0, the character is A
if k=1, the character is B
if k=2, the character is A
if k=3, the character is B
if k=4, the character is C
if k=5, the character is A
if k=6, the character is B
if k=7, the character is A
if k=8, the character is B
if k=9, the character is C
if k=10, the character is A
if k=11, the character is B
if k=12, the character is A
if k=13, the character is B
if k=14, the character is C
该程序的内存使用量仅限于堆栈使用量。堆栈深度将等于字符串的长度。在这个 C# 程序中,我一遍又一遍地复制字符串,这样会浪费内存。但即使管理不善,它也应该使用 O(N^2( 内存,其中 N 是字符串的长度。实际的扩展字符串可能要长得多。 例如,"AB2C999999"只有 N=10,因此应使用 O(100( 内存元素,但扩展字符串的长度超过 200 万个字符。
这个问题的重点是弄清楚在你能够获得第 k
个元素之前,你必须扩展多远。
在此示例中,假设第一个字符是索引 1,则0 < k <= 2
根本不需要展开。
对于2 < k <= 5
,您只需要展开第一部分。
对于5 < k <= 10
,您将需要扩展联伊ABABCABABC
,对于10 < k <= 15
,您将需要进行全面扩展。
在第一种情况下,字符串是"AB2C3",其中"2"从"AB2C3"中删除,字符串"AB2C3"中"2"("AB"(的左侧重复"2"次。它变成了"ABABC3"。
在第二种情况下,字符串是"ABABC3",其中"3"从"ABABC3"中删除,字符串"ABABC3"中"3"(">ABABC"(的左侧重复"3"次。它变成了"ABABCABABCABABC"。
算法是这样的:
1) READ ONE CHAR AT A TIME UNTIL END OF STRING
IF CHAR IS AN INT THEN k := k - CHAR + 1
2) RETURN STRING[k]
给出这个问题的代码。
public String repeater(String i_string, int k){
String temp = "";
for (int i=0; i < k; ++i)
temp = temp + i_string.substring(0,k);
temp = temp + i_string.substring(k, i_string.length());
return temp;
}
我没有考虑有限的内存问题,因为没有提到任何明确的信息。
您不需要任何额外的内存。您可以根据用户要求将数据打印到控制台。如果只是显示,则方法的返回类型可能被排除在外:)你只需要一个临时字符串来保存处理后的数据。
public void repeater2(String i_string, int k){
String temp = i_string.substring(0,k);
// Repeat and Print the first half as per requirements.
for (int i=0; i < k; ++i)
System.out.print(temp);
// Print the second half of the string AS - IS.
System.out.print(i_string.substring(k, i_string.length()));
}
如果 K 值为 1,则字符串将打印一次。根据要求。我们需要两次迭代。C++或Java的代码几乎相同,只是进行了微小的更改,我希望您能获得实际的逻辑。
- 使用堆查找第K个最大元素的时间复杂性
- 查找矩阵C++中每一列和每一行的最小和最大元素
- C++如何在向量中查找最常见的元素
- 查找两个排序向量中共有的元素
- 在对向量中查找元素的索引
- 查找数组中第一个最小值和最后一个最大值元素之间的算术平均值
- 查找矩阵中单元格的相邻元素
- 为什么使用数组元素查找最大数字的程序不起作用?
- 查找第一个数组中不存在的元素
- 查找最小的下一个更大的元素
- 在最小堆中查找最大元素
- 在向量中查找大于 0(或通常为 k)的最小元素的最佳方法是什么?
- set::find 查找不存在的元素
- 在集合中查找使用结构C++的元素
- 从斐波那契序列 c++ 中的数组中查找正确的元素时出错
- 用于查找数组中最大元素的出现次数的代码,给出分段错误
- 查找数组中指示性较大但数组中值较小的元素
- C++结构集无法按元素查找/擦除
- 如何在OpenCV中使用Matlab的512元素查找表数组?
- 使用数组的元素查找总和'k'