输入数组类似于:
[0,0,1,2,4,7,9,6,4,4,2,1,0,0,0,0,0,3,5,5,10,3,2,1,4,5,7,7,12,11,8,4,2,
1,1,1,2,4,9,4,2,2,0,1,3,6,13,11,5,5,2,2,3,4,7,11,8...]
正如我们所看到的,阵列中有丘陵和山谷,同一个山谷的重复(可能)最小值。我将This(具有不同元素的数组)想法扩展到上面的解决方案:
/// <summary>
/// Returns index array where valley lies
/// </summary>
/// <param name="arraySmoothed"></param>
/// <returns></returns>
private static List<int> GetValleys(List<int> arraySmoothed) {
List<int> valleys = new List<int>();
List<int> tempValley = new List<int>();
bool contdValleyValues = false;
for (int i = 0; i < arraySmoothed.Count; i++) {
// A[i] is minima if A[i-1] >= A[i] <= A[i+1], <= instead of < is deliberate otherwise it won't work for consecutive repeating minima values for a valley
bool isValley = ((i == 0 ? -1 : arraySmoothed[i - 1]) >= arraySmoothed[i])
&& (arraySmoothed[i] <= (i == arraySmoothed.Count - 1 ? -1 : arraySmoothed[i + 1]));
// If several equal minima values for same valley, average the indexes keeping in temp list
if (isValley) {
if (!contdValleyValues)
contdValleyValues = true;
tempValley.Add(i);
} else {
if (contdValleyValues) {
valleys.Add((int)tempValley.Average());
tempValley.Clear();
contdValleyValues = false;
}
}
}
return valleys;
}
这种方法陷入了... 7,9,6,4,4,2,1,0,0,0,0,0,3,5,5,10,3 ...它抛出的地方三个最小值,但应该有一个(五个中间的0)。复杂性对我来说不是问题,会崇拜 O(n)。我只想要一个通用的解决方案。任何提示/帮助将不胜感激。
答案 0 :(得分:6)
您可以使用一个简单的状态机对其进行编码,其中三个状态代表曲线的最近历史记录:
状态转换图如下所示:
图片看起来有点复杂,但描述很简单:
- 当曲线向上倾斜时,下一个状态为NotGoingDown
- 当曲线向下倾斜时,下一个状态为GoingDown
- 当拉伸为水平时,如果曲线下降,则状态变为EqGoingDown
;如果曲线变平或变高,状态变为NotGoingDown
。
以下是C#中的实现:
enum CurveState {
GoingDown=0, EqGoingDown=1, NotGoingDown=2
}
private static IList<int> GetValleys(IList<int> a) {
var res = new List<int>();
if (a.Count < 2) {
return res;
}
int lastEq = 0;
CurveState s = CurveState.NotGoingDown;
for (var i = 1 ; i != a.Count ; i++) {
switch(Math.Sign(a[i]-a[i-1])) {
case -1:
s = CurveState.GoingDown;
break;
case 0:
if (s == CurveState.GoingDown) {
lastEq = i;
}
s = (s==CurveState.NotGoingDown)
? CurveState.NotGoingDown
: CurveState.EqGoingDown;
break;
case 1:
if (s == CurveState.GoingDown) {
res.Add(i-1);
} else if (s == CurveState.EqGoingDown) {
res.Add((lastEq+i-1)/2);
}
s = CurveState.NotGoingDown;
break;
}
}
return res;
}
Demo使用您的数字,用星号标记山谷。
lastEq
变量是当前相等范围开始的位置的索引。请注意,转换表的设置方式是{I}处于lastEq
状态时始终设置CurveState.EqGoingDown
。 (lastEq+i-1)/2
公式计算值相等的最后一个位置与i-1
之间的平均值。
答案 1 :(得分:2)
问题在于,在持续的山谷结束时,您需要检查下一个值是否较低,这意味着您实际上并不在山谷的底部。此外,您必须跟踪平坦区域之前的坡度,以确定您是否在平坦区域之前上升或下降。另外,您不需要contdValleyValues
,因为您只需检查tempValley
,看看它是否有任何值。
private static List<int> GetValleys(List<int> arraySmoothed)
{
List<int> valleys = new List<int>();
List<int> tempValley = new List<int>();
int slope = 0;
for (int i = 1; i < arraySmoothed.Count - 1; i++)
{
// A[i] is minima if A[i-1] >= A[i] <= A[i+1], <= instead of < is deliberate
// otherwise it won't work for consecutive repeating minima values for a
// valley
bool isValley = arraySmoothed[i - 1] >= arraySmoothed[i]
&& arraySmoothed[i] <= arraySmoothed[i + 1];
// If several equal minima values for same valley, average the indexes
// keeping in temp list
if (isValley)
{
tempValley.Add(i);
}
else
{
if (tempValley.Any())
{
if (arraySmoothed[i - 1] < arraySmoothed[i] && slope == -1)
{
valleys.Add((int)tempValley.Average());
}
tempValley.Clear();
}
}
if (arraySmoothed[i - 1] > arraySmoothed[i])
{
slope = -1;
}
else if (arraySmoothed[i - 1] < arraySmoothed[i])
{
slope = 1;
}
}
return valleys;
}