有一个leetCode problem提示类似
给出一个数组“ nums”和一个值“ val”,删除该数组的所有实例 值就位并返回新长度。
不要为另一个数组分配额外的空间,必须通过 用O(1)额外的内存就地修改输入数组。
可以更改元素的顺序。离开什么都没关系 超出了新的长度。
示例1:
给出数字= [3,2,2,3],val = 3,
您的函数应返回length = 2,其中前两个元素为 nums是2。
此问题在C#(网站上用于解决此问题的受支持语言之一)中是否有意义?您不能修改数组的长度,只能分配一个新数组。
答案 0 :(得分:3)
此问题在C#中是否有意义? 是的。
[在C#中]您不能修改数组的长度,只能分配一个新数组。 绝对正确。因此,请重新阅读问题,然后看看您可以做什么。我不想给出答案,因为您显然会自己解决这个问题。因此,在这里停止阅读;如果想要提示,请继续阅读。
提示:专注于这一部分...
可以更改元素的顺序。超出新长度后剩下的都无所谓。
答案 1 :(得分:1)
使用unsafe
,fixed
和 Pointers 完全荒谬的方法...为什么?好吧,为什么不呢。此外,任何 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 input = new[] {23,4,4,3,32};
var length = input.Filter(4, input.GetLength(0));
for (var i = 0; i < length; i++)
Console.WriteLine(input[i]);
输出
23
3
32
仅供参考,正常的安全解决方案如下所示
var result = 0;
for (var i = 0; i < length; i++)
if (array[i] != value) array[result++] = array[i];
return result;
而且因为我开放了我的基准测试程序并且对病态感到好奇,所以对优化进行测试始终是一件好事,就像你永远不知道的那样。每个测试在每个规模上运行10000次,并且每次运行都收集垃圾。
----------------------------------------------------------------------------
Mode : Release (64Bit)
Test Framework : .NET Framework 4.7.1 (CLR 4.0.30319.42000)
----------------------------------------------------------------------------
Operating System : Microsoft Windows 10 Pro
Version : 10.0.17134
----------------------------------------------------------------------------
CPU Name : Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz
Description : Intel64 Family 6 Model 42 Stepping 7
Cores (Threads) : 4 (8) : Architecture : x64
Clock Speed : 3401 MHz : Bus Speed : 100 MHz
L2Cache : 1 MB : L3Cache : 8 MB
----------------------------------------------------------------------------
测试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 % |
-------------------------------------------------------------------------------
答案 2 :(得分:1)
看起来您正在寻找算法的解释,所以我将尽力解释它。
您正在“重写”阵列。考虑让游标遍历数组读取数据,同时让游标在其后写入值。在这种情况下,“读取”游标可以是foreach
循环或带有索引器的for
循环。
测试数据:
[12,34,56,34,78]
,我们要删除34
,我们从两个光标都在位置0(即values[0]
)开始,其中“ newLength = 0”代表“新”数组的长度,因此“ write”光标当前位于:
[12,34,56,34,78]
^r
^w
newLength: 0
第一个读取的元素12
不匹配,因此我们将其写入“新”数组中的位置,方法是将其写入到“新”数组当前长度所表示的位置(首先,长度为0,因此为values[0]
。在这种情况下,我们向该元素写入了相同的值,因此没有任何变化,我们通过增加该指针的长度将写入光标移动到下一个位置。 “新”数组
[12,34,56,34,78]
^r
^w
newLength: 1
现在,下一个元素确实与要删除的值匹配,因此我们不想将其包含在新数组中。为此,我们不增加“新”数组的长度,而将写游标留在原位置,而读游标移至下一个元素:
[12,34,56,34,78]
^r
^w
newLength: 1
如果此数组只有两个元素,则操作完成,并且数组中的值均未更改,但返回的长度仅为1
。随着元素的增加,让我们继续看看会发生什么。我们读取的56
与要删除的值不匹配,因此我们在新的“长度”指定的位置“写入”它,然后增加长度:
[12,56,56,34,78]
^r
^w
newLength: 2
现在,我们读取了一个匹配的值,因此我们跳过编写它:
[12,56,56,34,78]
^r
^w
newLength: 2
最终值不匹配,因此我们将其写入“ length”指定的位置并增加长度:
[12,56,78,34,78]
^r
^w
newLength: 3
“ read”游标现在超过了数组的长度,所以我们完成了。由于我们将三个值“写入”数组,因此新的“长度”值现在为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;
}
编辑:@Richardissimo
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