这是我最近的面试问题之一。我想知道别人对这个问题的看法。
问题:
您将获得一个结构,其中包含两个元素int
部门和string
名称的员工详细信息。
struct Employee
{
string Name;
int Dept;
}
您将获得N名员工的详细信息,其中N / 2名员工Dept == 0
和N / 2名员工Dept == 1
,按任意顺序排列。您需要根据其Dept
值对员工详细信息进行排序,并且应该为stable,即应保留原始记录中的1和0的顺序。
例如,给出以下样本数据:
Name Dept X1 0 X2 1 X3 0 X4 1 X5 0
排序结果后应该是:
Name Dept X2 1 X4 1 X1 0 X3 0 X5 0
算法应该是稳定的,时间复杂度应该是O(N),其他变量具有恒定的空间(这意味着应该就地进行排序)。
答案 0 :(得分:20)
分配第二个数组(O(N))。迭代第一个数组并按照它们出现的顺序将所有1移动到第二个数组。再次迭代并将以相同顺序保留的0移动到第二个数组。所有操作O(N)。这不是原位(就地)解决方案。通过运行Quicksort分区算法一次获得非稳定的原位解决方案。
经过一些研究,似乎没有任何额外记忆的已知O(N)解决方案不稳定。有关有效0-1稳定原位分选(就地)的学术研究,但解决方案需要一些额外的记忆。我想知道原始问题陈述是否没有以精确的方式再现。没有稳定性要求问题很容易;没有现场要求也很容易。满足要求(原位,稳定),解决方案似乎难以捉摸。
在这里的答案中,有一种算法在O(N)中工作并且是原位的,但只有当关键字段是(1)可变的并且(2)可以包含整数而不是单个位时。这可以工作,但不是原位0-1稳定排序,因为假设每个数组元素有可用的O(log N)可写内存。
答案 1 :(得分:13)
哦,这是我的方法。
例如a [] = {1,0,0,0,1,1,1,0,0,1};
<强>伪代码:强>
count1 = 0
和count2 = (n/2)+1
遍历数组,
if(arr[ i ] == 1)
{
arr[ i ] = count1++;
} else {
arr[ i ] = count2++
};
在遍历结束时,您的数组中填充了数字0到n-1,如:
a[ ] = { 0, 5, 6, 7, 1, 2, 3, 8, 9 4}
现在问题在于对上面得到的数组进行排序,这可以在O(N)中完成,如下所示:
for(j = 0; j <= 1; j++)
{
for(i = 0; i<n; i++)
{
if(arr[ i ] != i)
{
swap(arr[ i ], arr[ arr[ i ] ]);
}
}
}
注意:j循环只运行两次,与“n”无关,并且具有恒定的复杂性。整个循环的顺序是2 * n = O(n)。
对数组进行排序后,再次遍历数组并将元素arr[0]
设置为arr[n/2]
到'1'
,将arr[(n/2)+1]
设置为arr[n]
为'0'
。
空间复杂度是恒定的,时间复杂度是O(步骤2)+ O(步骤4)+ O(步骤5)= n + 2n + n = 4 * n = O(n)。
答案 2 :(得分:6)
将std::stable_partition
与std::equal_to
和std::binder1st
一起使用,应该以一种漂亮,功能强大,类似STL的方式进行操作:
using namespace std
stable_partition(&array[0], &array[N], binder1st(equal_to(), 1));
当然,这假设数组的元素有一些比较运算符定义(即你可以说array[i]==1
...)。如果它们只是整数,维持秩序就没有任何意义......
至于复杂性:为了 O(N),stable_partition
需要额外的内存。如果算法无法分配额外的内存,它将在 O(N log N)中执行。
答案 3 :(得分:2)
原始问题文本没有提及除整数之外的任何其他字段(从那时起已经编辑过)。
在这种情况下,稳定性没有意义,因为否则两个相等的数字是无法区分的。解决方案是遍历数组并将1的n / 2次,然后是0的n / 2次。
答案 4 :(得分:2)
为简单起见,使用ints而不是bit,但基本概念是相同的。不是说不同的1和0的顺序最终会出现问题!
var emps = new[]
{
new Employee(X1, 0),
new Employee(X2, 1),
new Employee(X3, 0),
new Employee(X4, 1),
new Employee(X5, 0),
new Employee(X6, 1)
};
var sortedEmps = new Employee[bits.Length];
var oneIndex = 0;
var zeroIndex = bits.Length/2;
foreach (var employee in employees)
{
if (employee.Dept == 1)
sortedEmps[oneIndex++] = employee;
else
sortedEmps[zeroIndex++] = employee;
}
更新以解决员工问题。增加了一名额外的员工,因为原始问题说每个人都有N / 2,因此必须有一个偶数才能成为现实。否则它是一样的。
现在不确定这会编译,所以将其视为伪代码!
答案 5 :(得分:1)
这将是radix sort的第一步。
答案 6 :(得分:0)
在抽象术语中,您可以使用修改后的插入排序,将所有零值交换到右侧,将所有值都交换到左侧。然而,这将具有大于O(n)的复杂性。
我有点困惑为什么订购事项,如前所述,字节无法区分。
编辑:新示例有帮助。我的算法的好处,即使它在最坏的情况下比线性时间慢,它在内存使用中只有O(n)。由于1和0只是较大对象的键(可能是任意大的),因此可能需要考虑内存。
答案 7 :(得分:0)
到底是什么,这是整个解决方案:
arr是项目列表,item.id为0或1,存储为int。
此代码将0移到前面。
count = { 0:0, 1:len(arr)/2 }
for ii in range(len( arr )):
id = arr[ii].id
arr[ii].id = count[id]
count[id] += 1
for ii in range(len( arr )):
while arr[ii].id != ii:
T = arr[ii]
arr[ii] = arr[arr[ii].id]
arr[T.id] = T
for ii in range(len( arr )):
arr[ii].id = (ii >= len(arr)/2)
答案 8 :(得分:0)
我使用简单加权在Perl中实现。
use Modern::Perl;
my @data;
{
use List::MoreUtils qw'natatime';
my $iter = natatime 2, qw'
X1 0
X2 1
X3 0
X4 1
X5 0
';
while( my($a,$b) = $iter->() ){
push @data, [$a,$b]
}
}
my @sorted = sort {
($a->[1] <=> $b->[1]) * -2 + # gives more weight to second element
($a->[0] cmp $b->[0])
} @data;
say "Name\tDept\n";
say join "\t", @$_ for @sorted;
Name Dept X2 1 X4 1 X1 0 X3 0 X5 0
<=>
和cmp
比较的输出值为-1,0,1
。
通过将其中一个camparisons乘以2,我们得到-2,0,2
之一
添加其他运算符的值后,我们得到-3,-2,-1,0,1,2,3
我想知道它会进行多少次比较,所以我添加了一些调试信息并提出了这个。
a b a1 b1 a0 b0
X1,0 -> X2,1 0 --> 1 X1 <- X2
X3,0 -> X4,1 0 --> 1 X3 <- X4
X2,1 <- X4,1 1 - 1 X2 <- X4
X4,1 <- X1,0 1 <-- 0 X4 -> X1
X1,0 <- X3,0 0 - 0 X1 <- X3
X2,1 <<- X5,0 1 <-- 0 X2 <- X5
X5,0 ->> X4,1 0 --> 1 X5 -> X4
X5,0 -> X1,0 0 - 0 X5 -> X1
X5,0 -> X3,0 0 - 0 X5 -> X3
The arrows point at the earlier value.
答案 9 :(得分:0)
这是适用于int
数组的解决方案。你可以修改它。
sort (int [] a) {
int pos = 0;
for (int i = 1; i < a.length; i++) {
if (a[i] == 0 && a[pos] == 1) {
swap(a, pos, i); // this makes all 0's go to the left.
pos++;
}
}
}
答案 10 :(得分:0)
#include<stdio.h>
//#include<conio.h>
int main()
{
int i,a[20]={0};
int *ptr1,*ptr2;
//clrscr();
//a[2]=a[4]=a[6]=a[8]=a[16]=1;
a[19]=a[18]=1;
for(i=0;i<20;i++)
printf("%d",a[i]);
printf("\n\nafter");
ptr1=ptr2=a;
for(i=0;i<20;i++)
{
if(*ptr1==0&&*ptr2==1)
{
int temp=*ptr1;*ptr1=*ptr2;*ptr2=temp;
ptr1++;ptr2++;
}
else if(*ptr1==1&&*ptr2==0)
{
ptr1++;ptr2++;
}
else if(*ptr1==0&&*ptr2==0)
{
ptr2++;
}
else
{
if(ptr1<ptr2)
ptr1++;
else
{
ptr1++;ptr2++;
}
}
}
for(i=0;i<20;i++)
{
printf("%d",a[i]);
}
// getch();
return 0;
}
答案 11 :(得分:0)
可以单程完成,也可以到位。
例: #包括 #define N 6 int main() { int list [N] = {1,1,0,0,0,0}; int s,end,tmp; S = 0;端= N-1;
while(s less than end)
{
if(list[s]==1)
{
while(list[end] == 1)
end--;
if(list[end] == 0 && s less than end)
{
tmp = list[s];
list[s] = list[end];
list[end] = tmp;
s++;end--;
}
}
else s++;
}
for(s=0;s less than N;s++)
{
printf("%d ",list[s]);
}
return;
}
答案 12 :(得分:0)
left = 0;
right = n-1;
while(left<right){
while(left < right && array[left]==0){
left++;
}
while(left < right && array[right]==1){
right--;
}
while(left<right){
array[left]=0;
array[right]=1;
left++;
right--;
}
}
答案 13 :(得分:-1)
易:
function sort(array):
new_array = new Array(array.length)
x = 0
for(i : array): if(i): new_array(x++) = 1
return new_array
如果你真的想要相同的 1s和0s:
function sort(array):
new_array = new Array(array.length)
x, y = 0, array.length / 2
for(i : array):
if(i): new_array(x++) = i
else: new_array(y++) = i
return new_array
答案 14 :(得分:-1)
这是我在C#中的(完整)刺伤。它生成列表并将随机员工包括在内,因此无论您喜欢什么,都要numberOfEmployees
。我意识到可能有一种更简单的方法可以做到这一点,因为原始海报指出有一定数量的0部门员工作为1-dept员工,但我无法帮助自己。
struct Employee
{
public Employee(string name, int dept)
{
this.name = name;
this.dept = dept;
}
public string name;
public int dept;
}
class Program
{
static void Main(string[] args)
{
int numberOfEmployees = 100;
Random r = new Random();
Employee[] emps = new Employee[numberOfEmployees];
var empBuf = new Employee[numberOfEmployees];
int nextAvail = 0;
// Initialize array of employees with random data
for (int i = 0; i < numberOfEmployees; i++)
{
emps[i] = new Employee("x" + i.ToString(), r.Next(0, 2));
}
Console.WriteLine("Old list:");
foreach (var e in emps)
{
Console.WriteLine("Name: {0}, Dept: {1}", e.name, e.dept);
}
// throw employees with dept == 1 in first
for (int i = 0; i < numberOfEmployees; i++)
{
if (emps[i].dept == 1)
{
empBuf[nextAvail] = emps[i];
nextAvail++;
}
}
// stick the employees with dept == 0 in last
for (int i = 0; i < numberOfEmployees; i++)
{
if (emps[i].dept == 0)
{
empBuf[nextAvail] = emps[i];
nextAvail++;
}
}
Console.WriteLine("New list:");
foreach (Employee e in empBuf)
{
Console.WriteLine("Name: {0}, Dept: {1}", e.name, e.dept);
}
}
}
答案 15 :(得分:-1)
我可以想到一个算法,它将花费O(n + i)时间(最坏情况为O(2n-1)和最佳情况O(n))并且没有额外的空间。我们开始遍历从0到n-1的数组,每次遇到1时我们递增一个计数器并用0替换1。现在当我们到达数组的末尾时,我们将开始向后移动尽可能多的计数器并初始化每个数字都是1。
private static int[] foo(int[] array) {
int counter = 0;
int i;
for(i = 0; i < array.length; i++) {
if (array[i] == 1) {
array[i] = 0;
counter++;
}
}
while(counter > 0) {
array[--i] = 1;
counter--;
}
return array;
}