修改数组";“到位”;在C#中?
有一个提示,如 给定数组“nums”和值“val”,请删除该数组的所有实例 值并返回新的长度 不要为另一个数组分配额外空间,必须通过 使用O(1)个额外内存就地修改输入阵列 元素的顺序可以更改。不管你留下什么 超出新的长度 例1: 给定nums=[3,2,2,3],val=3 函数应返回length=2,其中前两个元素为 nums为2修改数组";“到位”;在C#中?,c#,arrays,.net,algorithm,C#,Arrays,.net,Algorithm,有一个提示,如 给定数组“nums”和值“val”,请删除该数组的所有实例 值并返回新的长度 不要为另一个数组分配额外空间,必须通过 使用O(1)个额外内存就地修改输入阵列 元素的顺序可以更改。不管你留下什么 超出新的长度 例1: 给定nums=[3,2,2,3],val=3 函数应返回length=2,其中前两个元素为 nums为2 这个问题在C#(站点上解决此问题的支持语言之一)中有意义吗?您不能修改数组的长度,只能分配一个新数组。一种完全没有意义的方法,使用不安全的、固定的和指针。。。为什
这个问题在C#(站点上解决此问题的支持语言之一)中有意义吗?您不能修改数组的长度,只能分配一个新数组。一种完全没有意义的方法,使用
不安全的、固定的和指针。。。为什么?为什么不呢。此外,任何leet代码问题都应该使用leet指针来解决
给定的
public static unsafe int Filter(this int[] array, int value, int length)
{
fixed (int* pArray = array)
{
var pI = pArray;
var pLen = pArray + length;
for (var p = pArray; p < pLen; p++)
if (*p != value)
{
*pI = *p;
pI++;
}
return (int)(pI - pArray);
}
}
仅供参考,正常的安全解决方案是这样的
var result = 0;
for (var i = 0; i < length; i++)
if (array[i] != value) array[result++] = array[i];
return result;
测试1
--- Standard input ------------------------------------------------------------
| Value | Average | Fastest | Cycles | Garbage | Test | Gain |
--- Scale 100 -------------------------------------------------- Time 9.902 ---
| Array | 434.091 ns | 300.000 ns | 3.731 K | 0.000 B | Base | 0.00 % |
| UnsafeOpti | 445.116 ns | 300.000 ns | 3.662 K | 0.000 B | N/A | -2.54 % |
| Unsafe | 448.286 ns | 300.000 ns | 3.755 K | 0.000 B | N/A | -3.27 % |
--- Scale 1,000 ----------------------------------------------- Time 10.161 ---
| UnsafeOpti | 1.421 µs | 900.000 ns | 7.221 K | 0.000 B | N/A | 17.70 % |
| Array | 1.727 µs | 1.200 µs | 8.230 K | 0.000 B | Base | 0.00 % |
| Unsafe | 1.740 µs | 1.200 µs | 8.302 K | 0.000 B | N/A | -0.78 % |
--- Scale 10,000 ---------------------------------------------- Time 10.571 ---
| UnsafeOpti | 10.910 µs | 9.306 µs | 39.518 K | 0.000 B | N/A | 20.03 % |
| Array | 13.643 µs | 12.007 µs | 48.849 K | 0.000 B | Base | 0.00 % |
| Unsafe | 13.657 µs | 12.007 µs | 48.907 K | 0.000 B | N/A | -0.10 % |
--- Scale 100,000 --------------------------------------------- Time 15.443 ---
| UnsafeOpti | 105.386 µs | 84.954 µs | 362.969 K | 0.000 B | N/A | 19.93 % |
| Unsafe | 130.150 µs | 110.771 µs | 447.383 K | 0.000 B | N/A | 1.12 % |
| Array | 131.621 µs | 113.773 µs | 452.262 K | 0.000 B | Base | 0.00 % |
--- Scale 1,000,000 ------------------------------------------- Time 22.183 ---
| UnsafeOpti | 1.556 ms | 1.029 ms | 5.209 M | 0.000 B | N/A | 23.13 % |
| Unsafe | 2.007 ms | 1.303 ms | 6.780 M | 0.000 B | N/A | 0.85 % |
| Array | 2.024 ms | 1.354 ms | 6.841 M | 0.000 B | Base | 0.00 % |
-------------------------------------------------------------------------------
使用不安全
,固定
,以及指针,这是一种完全没有意义的方法。。。为什么?为什么不呢。此外,任何leet代码问题都应该使用leet指针来解决
给定的
public static unsafe int Filter(this int[] array, int value, int length)
{
fixed (int* pArray = array)
{
var pI = pArray;
var pLen = pArray + length;
for (var p = pArray; p < pLen; p++)
if (*p != value)
{
*pI = *p;
pI++;
}
return (int)(pI - pArray);
}
}
仅供参考,正常的安全解决方案是这样的
var result = 0;
for (var i = 0; i < length; i++)
if (array[i] != value) array[result++] = array[i];
return result;
测试1
--- Standard input ------------------------------------------------------------
| Value | Average | Fastest | Cycles | Garbage | Test | Gain |
--- Scale 100 -------------------------------------------------- Time 9.902 ---
| Array | 434.091 ns | 300.000 ns | 3.731 K | 0.000 B | Base | 0.00 % |
| UnsafeOpti | 445.116 ns | 300.000 ns | 3.662 K | 0.000 B | N/A | -2.54 % |
| Unsafe | 448.286 ns | 300.000 ns | 3.755 K | 0.000 B | N/A | -3.27 % |
--- Scale 1,000 ----------------------------------------------- Time 10.161 ---
| UnsafeOpti | 1.421 µs | 900.000 ns | 7.221 K | 0.000 B | N/A | 17.70 % |
| Array | 1.727 µs | 1.200 µs | 8.230 K | 0.000 B | Base | 0.00 % |
| Unsafe | 1.740 µs | 1.200 µs | 8.302 K | 0.000 B | N/A | -0.78 % |
--- Scale 10,000 ---------------------------------------------- Time 10.571 ---
| UnsafeOpti | 10.910 µs | 9.306 µs | 39.518 K | 0.000 B | N/A | 20.03 % |
| Array | 13.643 µs | 12.007 µs | 48.849 K | 0.000 B | Base | 0.00 % |
| Unsafe | 13.657 µs | 12.007 µs | 48.907 K | 0.000 B | N/A | -0.10 % |
--- Scale 100,000 --------------------------------------------- Time 15.443 ---
| UnsafeOpti | 105.386 µs | 84.954 µs | 362.969 K | 0.000 B | N/A | 19.93 % |
| Unsafe | 130.150 µs | 110.771 µs | 447.383 K | 0.000 B | N/A | 1.12 % |
| Array | 131.621 µs | 113.773 µs | 452.262 K | 0.000 B | Base | 0.00 % |
--- Scale 1,000,000 ------------------------------------------- Time 22.183 ---
| UnsafeOpti | 1.556 ms | 1.029 ms | 5.209 M | 0.000 B | N/A | 23.13 % |
| Unsafe | 2.007 ms | 1.303 ms | 6.780 M | 0.000 B | N/A | 0.85 % |
| Array | 2.024 ms | 1.354 ms | 6.841 M | 0.000 B | Base | 0.00 % |
-------------------------------------------------------------------------------
这个问题在C#中有意义吗?
对
[在C#中]您不能修改数组的长度,只能分配一个新数组。
绝对正确。因此,重新阅读问题,看看你能做些什么。我不想给出答案,因为很明显,你会从自己解决这个问题中学到很多东西。因此,停止阅读这里;如果需要提示,请继续阅读
提示:关注这部分
元素的顺序可以更改。你在新的长度之外留下什么并不重要
这个问题在C#中有意义吗?
对
[在C#中]您不能修改数组的长度,只能分配一个新数组。
绝对正确。因此,重新阅读问题,看看你能做些什么。我不想给出答案,因为很明显,你会从自己解决这个问题中学到很多东西。因此,停止阅读这里;如果需要提示,请继续阅读
提示:关注这部分
元素的顺序可以更改。你在新的长度之外留下什么并不重要
看起来你在寻找算法的解释,所以我会尽力解释
您正在原地“重写”数组。想象一下让光标遍历数组读取数据,同时让光标在数组后面写入值。在这种情况下,“读取”光标可以是带有索引器的foreach
循环或for
循环
测试数据:
[12,34,56,34,78]
我们要删除34
,我们从两个光标的位置0开始(即值[0]
),其中“newLength=0”表示“新”数组的长度,因此“write”光标当前所在的位置:
[12,34,56,34,78]
^r
^w
newLength: 0
读取的第一个元素,12
,不匹配,因此我们通过将该元素写入数组中由“新”数组的当前长度表示的位置,将其包含在“新”数组中(开始时,长度为0,因此这是值[0]
。在本例中,我们正在向该元素写入相同的值,因此没有任何变化,我们通过增加“新”数组的长度将写入光标移动到下一个位置
现在,下一个元素与要删除的值匹配,因此我们不希望将其包含在新数组中。我们通过不增加“新”数组的长度,并在读取光标移动到下一个元素时将写入光标保留在原来的位置来实现这一点:
[12,34,56,34,78]
^r
^w
newLength: 1
如果这个数组只有两个元素,我们就完成了,数组中的值没有改变,但是返回的长度只有1
。因为我们有更多的元素,让我们继续看看会发生什么。我们读取56
,它与要删除的值不匹配,所以我们在新的“长度”指定的位置“写入”它,之后我们增加长度:
[12,56,56,34,78]
^r
^w
newLength: 2
[12,56,78,34,78]
^r
^w
newLength: 3
现在,我们读取一个匹配的值,因此跳过写入:
[12,56,56,34,78]
^r
^w
newLength: 2
最终值不匹配,因此我们将其写入“长度”指定的位置,并增加长度:
[12,56,56,34,78]
^r
^w
newLength: 2
[12,56,78,34,78]
^r
^w
newLength: 3
“读取”光标现在超过了数组的长度,所以我们完成了。新的“长度”值现在是3
,因为我们将三个值“写入”到数组中。将数组与newLength
值结合使用在功能上会产生“新”数组[12,56,78]
下面是@TheGeneral使用指针的功能正确但不安全的安全实现:
public static int DoStuffSafely( int[] values, int valueToRemove, int length )
{
var newLength = 0;
// ~13.5% slower than @TheGeneral's unsafe implementation
foreach( var v in values )
{
// if the value matches the value to remove, skip it
if( valueToRemove != v )
{
// newLength tracks the length of the "new" array
// we'll set the value at that index
values[ newLength ] = v;
// then increase the length of the "new" array
++newLength;
// I never use post-increment/decrement operators
// it requires a temp memory alloc that we toss immediately
// which is expensive for this simple loop, adds about 11% in execution time
}
}
return newLength;
}
编辑:适用于@richardisimo
values[ newLength ] = v;
00007FFBC1AC8993 cmp eax,r10d
00007FFBC1AC8996 jae 00007FFBC1AC89B0
00007FFBC1AC8998 movsxd rsi,eax
00007FFBC1AC899B mov dword ptr [r8+rsi*4+10h],r11d
++newLength;
00007FFBC1AC89A0 inc eax
values[ newLength++ ] = v;
00007FFBC1AD8993 lea esi,[rax+1]
00007FFBC1AD8996 cmp eax,r10d
00007FFBC1AD8999 jae 00007FFBC1AD89B3
00007FFBC1AD899B movsxd rax,eax
00007FFBC1AD899E mov dword ptr [r8+rax*4+10h],r11d
00007FFBC1AD89A3 mov eax,esi
看起来你在寻找算法的解释,所以我会尽力解释
您正在原地“重写”数组。请考虑让光标穿过数组读取数据,同时让光标在数组后面写入值。在这种情况下,“读取”光标可以是foreach
循环或for
循环,并带有索引器
测试数据:
[12,34,56,34,78]
我们要删除34
,我们从两个光标的位置0开始(即值[0]
),其中“newLength=0”表示“新”数组的长度,因此“write”光标当前所在的位置:
[12,34,56,34,78]
^r
^w
newLength: 0
读取的第一个元素,12
,不匹配,因此我们通过将该元素写入数组中由“新”数组的当前长度表示的位置,将其包含在“新”数组中(开始时,长度为0,因此这是值[0]
。在本例中,我们正在向该元素写入相同的值,因此没有任何变化,我们通过增加“新”数组的长度将写入光标移动到下一个位置
现在,下一个元素与要删除的值匹配,因此我们不希望包含它