使用 Linq 连续总和大于 0 的连续元素
Posted
技术标签:
【中文标题】使用 Linq 连续总和大于 0 的连续元素【英文标题】:Consecutive elements with consecutive sum greater than 0 using Linq 【发布时间】:2021-08-31 09:47:34 【问题描述】:好的,这只是我几天前解决的一个有趣问题的一小部分。想象一下,我们有一个包含 N 个元素的数组。该数组仅由 1 和 -1 组成。它看起来像这样:
int[] arr = new int[] -1, 1, -1, 1, 1, -1 ;
我们的任务是找到保持(假想)计数器 >= 0 的连续元素的最大数量(从左到右),然后对数组进行切割,以便只剩下这些元素。
在我们的例子中,我们从计数器 0 开始:
计数器 = 0
第一个元素:-1 -> Counter = -1(低于0,所以我们重置了计数器)
秒:1 -> 计数器 = 1
第三个:-1 -> Counter = 0
第四个:1 -> Counter = 1
等等。
在上述例子中,答案是:
Solve(new int[] -1, 1, -1, 1, 1, -1 ); // => new int[] 1, -1, 1, 1, -1 ;
现在,我的做法是这样的:
public static List<int> Solve(int[] input)
List<List<int>> compare = new List<List<int>>();
List<int> temp = new List<int>();
for(int i = 0, c = 0; i < input.Length; i++)
if(input[i] == 1)
c++;
temp.Add(input[i]);
else if(input[i] == -1)
c--;
if (c >= 0)
temp.Add(input[i]);
else
c = 0;
compare.Add(temp);
temp = new List<int>();
compare.Add(temp);
return compare.OrderByDescending(x => x.Count).First();
更多测试用例:
Solve(new int[] -1, 1, -1, 1, 1, -1 ); // => new int[] 1, -1, 1, 1, -1 ;
Solve(new int[] 1, 1, -1, -1, 1, 1 ); // => new int[] 1, 1, -1, -1, 1, 1 ;
Solve(new int[] 1, -1, 1, -1, 1, -1, -1, 1, 1, 1 ); // => new int[] 1, -1, 1, -1, 1, -1 ;
Solve(new int[] 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, -1
// => new int[] 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1 ;
Solve(new int[] -1, -1, -1 ); // => new int[] ;
但我对你们所有 Linq 策划者的问题是:有没有什么方法可以在 1 行代码中使用我们心爱的 Linq 来做到这一点?
那将不胜感激:D
【问题讨论】:
对于“从右到左”的事情非常抱歉,我可能让你们中的一些人感到困惑。相应地编辑了问题。 "consecutive elements..." 在这一点上,我已经知道没有简单的 LINQ 单行。如果您想知道上一个/下一个项目是什么,Linq 并不是真正合适的工具,那是普通 for 循环的域。 @TimSchmelter 啊,真可惜,我希望有办法做到这一点。很公平,不过感谢您指出!也许其他人有其他想法?也许是一些晦涩的语言功能? 【参考方案1】:如果您必须使用 Linq,并且您可以作弊并使用 MoreLinq,那么这种丑陋的混乱会起作用(它会返回多个集合,因为理论上您可以多次运行相同的最大大小)
Enumerable.Range(0, input.Count())
.Select(i =>
input.Skip(i)
.Scan((Current: 0, Total: 0), (x, y) => (y, x.Total + y))
.Skip(1)
.TakeWhile(x => x.Total >= 0))
.MaxBy(x => x.Count())
.Select(x => x.Select(y => y.Current));
【讨论】:
确实是 Linq 的主谋。非常感谢,这个烂摊子正是我所需要的!【参考方案2】:在这种特殊情况下,由于条件逻辑相对复杂,我会说使用迭代方法是更好的选择,但一些类似的任务可以通过使用 Aggregate
函数来解决,该函数在序列上应用累加器函数并允许您传递状态步骤之间。对于您的情况,它可能看起来像这样(使用一些 C# 9 features):
var result = arr
.Select((i, index) => (i, index))
.Aggregate(
(Sum: 0, Ranges: new List<List<int>> new()), // state holder
(agg, curr) =>
var sum = curr.i + agg.Sum;
if (sum >= 0) agg.Ranges.Last().Add(curr.i);
if (sum < 0)
sum = 0;
agg.Ranges.Add(new());
return (sum, agg.Ranges);
)
.Ranges
.Aggregate((agg, curr) => curr.Count > agg.Count ? curr : agg); // MaxBy via Aggregate
【讨论】:
似乎没有返回最长的连续整数列表,其运行总和大于或等于 0。 这似乎没有通过测试:/ 不过,感谢您的努力和解释! @Plus 抱歉,有点误解了条件。在这种情况下,使用Aggregate
不是一个好的选择,但有些地方非常适合。
@Plus 更新了答案以匹配条件。【参考方案3】:
我同意其他建议 LINQ 可能不是最佳方法的答案。这是我的看法。此版本仅将较短的序列替换为较长的序列以避免一些内存开销,然后也可以跳过 OrderByDescending
步骤。
public static List<int> Solve(int[] input)
var temp = new List<int>();
var longestSequence = new List<int>();
for (int i = 0, c = 0; i < input.Length; i++)
c += input[i];
if (c >= 0)
temp.Add(input[i]);
else
c = 0;
if (temp.Count > longestSequence.Count)
longestSequence = temp;
temp = new List<int>();
return longestSequence;
如果您真的关心内存分配,例如复制到临时列表和隐藏的列表大小调整操作,那么您可以只跟踪序列的开始和结束,然后循环返回子序列。
【讨论】:
【参考方案4】:我最近在申请工作时必须进行的技能测试中遇到了这个问题。您需要很少的代码来解决它,并且不需要 LINQ。
int currentLevel = 0;
List<int> lst = new List<int>();
for(int x = 0; x < input.length; x++)
currentLevel += input[x];
if (currentLevel >= 0)
lst.Add(input[x]);
return lst.ToArray();
【讨论】:
以上是关于使用 Linq 连续总和大于 0 的连续元素的主要内容,如果未能解决你的问题,请参考以下文章