题目:一个大小为N的数组,里面是N个整数,怎样去除重复,
要求时间复杂度为O(n),空间复杂度为O(1).
//下面的思路没问题,但算法有问题,修正后的算法见后面.
/// <summary>
/// 需要除掉重复的整数的数组,注意这里我没有处理负数情况,
/// 其实负数情况只要先用0快排分一下组,然后各自用以下算法进行处理即可。
/// 另外因为是整数,这里没考虑32位符号位,只考虑31位。
/// 题目分析:从要求来看,如果一个数组是排好序的,除掉重复就很简单,因此就转换成了
/// 排序算法寻找,这种算法需要满足:线性时间,常量内存,原地置换。但纵观这么多算法,比较排序肯定不行,
/// 那么就只有基数排序,桶排序和计数排序,但基数排序依赖于位排序,而且要求位排序是稳定的,
/// 且不能过多使用辅助空间,计数排序排除,因为计数排序无法原地置换,桶排序也需要辅助空间,所以最后考虑用
/// 基数排序。但问题是如何选择位排序,因为位上只有0和1,因此有其特殊性,使用快排的分组就可以达到线性,
/// 但问题是这种算法虽然是线性,原地置换,但不稳定。所以要利用一种机制来确保快排是稳定的。经过一段时间思考,
/// 发现,如果从高位开始排序,假设前K位是排好的,对K+1进行排序时,只针对前K位的相同的进行,前K位不相同,也不可
/// 能相等,第K+1位也不影响结果,而前K位相同的排序,就不怕快排的不稳定了,因为这个不稳定不会影响到最终结果。
/// 下面就是算法:
/// </summary>
/// <param name="A"></param>
private void BitSortAndDelRepeatorsA(int[] A)
{
//获取数组长度
int theN = A.Length;
//从高位到低位开始排序,这里从31位开始,32位是符号位不考虑,或者单独考虑。
for (int i = 31; i >= 1; i--)
{
//当前排序之前的值,只有该值相同才进行快排分组,如果不相同,则重新开始另外一次快排
//这很关键,否则快排的不稳定就会影响最后结果.
int thePrvCB = A[0] >> (i) ;
//快排开始位置,会变化
int theS = 0;
//快排插入点
int theI = theS;
//整数基元,就选择快排开始位置的数.
int theAxNum = A[theI];
//2进制基数,用于测试某一位是否为0
int theBase = 1 << (i-1);
//位基元,
int theAxBit = (theAxNum >> (i-1)) & 1;//(A[theI] & (theBase)) > 0 ? 1 : 0;
//分段快排,但总体上时间复杂度与快排分组一样.
for (int j = 1; j < theN; j++)
{
//获取当前数组值的前面已拍过序的位数值。
int theTmpPrvCB = A[j] >> (i);
//如果前面已排过的位不相同,则重新开始一次快排.
if (theTmpPrvCB != thePrvCB)
{
A[theS] = A[theI];
A[theI] = theAxNum;
theS = j;
theI = theS;
theAxNum = A[theI];
theAxBit = A[theI] & theBase;
thePrvCB = theTmpPrvCB;
continue;
}
//如果相同,则按快排处理
int theAJ = (A[j] >> (i - 1)) & 1;//(A[j] & (theBase)) > 0 ? 1 : 0;
if (theAJ <= theAxBit)
{
theI++;
int theTmp = A[j];
A[j] = A[theI];
A[theI] = theTmp;
}
}
//注意最后一次交换。
A[theS] = A[theI];
A[theI] = theAxNum;
}
}
除掉重复数:只要对上述排序结果进行一次遍历处理即可.
private int[] DeleteRepeatedInt(int[] A)
{
int N = A.Length;
//从低位到高位进行计数排序,因为是整数,这里假设都是正数.
for (int i = 1; i <= 32; i++)
{
CountSort2(A, i);
}
//除掉重复的数
int thePreNum = int.MinValue;
List<int> theRet = new List<int>();
for (int i = 0; i < N; i++)
{
if (A[i] != thePreNum)
{
theRet.Add(A[i]);
thePreNum = A[i];
}
}
return theRet.ToArray();
}
===================================================
排序算法修正部分
/// <summary>
/// 需要除掉重复的整数的数组,注意这里我没有处理负数情况,
/// 其实负数情况只要先用0快排分一下组,然后各自用以下算法进行处理即可。
/// 另外因为是整数,这里没考虑32位符号位,只考虑31位。
/// 题目分析:从要求来看,如果一个数组是排好序的,除掉重复就很简单,因此就转换成了
/// 排序算法寻找,这种算法需要满足:线性时间,常量内存,原地置换。但纵观这么多算法,比较排序肯定不行,
/// 那么就只有基数排序,桶排序和计数排序,但基数排序依赖于位排序,而且要求位排序是稳定的,
/// 且不能过多使用辅助空间,计数排序排除,因为计数排序无法原地置换,桶排序也需要辅助空间,所以最后考虑用
/// 基数排序。但问题是如何选择位排序,因为位上只有0和1,因此有其特殊性,使用快排的分组就可以达到线性,
/// 但问题是这种算法虽然是线性,原地置换,但不稳定。所以要利用一种机制来确保快排是稳定的。经过一段时间思考,
/// 发现,如果从高位开始排序,假设前K位是排好的,对K+1进行排序时,只针对前K位的相同的进行,前K位不相同,也不可
/// 能相等,第K+1位也不影响结果,而前K位相同的排序,就不怕快排的不稳定了,因为这个不稳定不会影响到最终结果。
/// 下面就是算法:
/// </summary>
/// <param name="A"></param>
private void BitSortAndDelRepeatorsA(int[] A)
{
//获取数组长度
int theN = A.Length;
//从高位到低位开始排序,这里从31位开始,32位是符号位不考虑,或者单独考虑。
for (int i = 31; i >= 1; i--)
{
//当前排序之前的值,只有该值相同才进行快排分组,如果不相同,则重新开始另外一次快排
//这很关键,否则快排的不稳定就会影响最后结果.
int thePrvCB = A[0] >> (i) ;
//快排开始位置,会变化
int theS = 0;
//快排插入点
int theI = theS-1;
//2进制基数,用于测试某一位是否为0
int theBase = 1 << (i-1);
//位基元始终为0,
int theAxBit = 0;
//分段快排,但总体上时间复杂度与快排分组一样.
for (int j = 0; j < theN; j++)
{
//获取当前数组值的前面已拍过序的位数值。
int theTmpPrvCB = A[j] >> (i);
//如果前面已排过的位不相同,则重新开始一次快排.
if (theTmpPrvCB != thePrvCB)
{
theS = j;
theI = theS - 1;
theAxBit = 0;
thePrvCB = theTmpPrvCB;
j--;//重新开始排,回朔一位.
continue;
}
//如果前面的数相同,则寻找第1个1,thI指向其
//如果相同,则按快排处理
int theAJ = (A[j] & (theBase)) > 0 ? 1 : 0; ;//(A[j] & (theBase)) > 0 ? 1 : 0;(A[j] >> (i - 1)) & 1
//如果是重新开始排,则寻找第1个1,并人theI指向其.这可以减少交换,加快速度.
if (theI < theS)
{
if (theAJ == 0)
{
continue;
}
theI = j;//Continue保证J从theI+1开始.
continue;
}
//交换.
if (theAJ <= theAxBit)
{
int theTmp = A[j];
A[j] = A[theI];
A[theI] = theTmp;
theI++;
}
}
}
}
经过测试,算法复杂度<32*n。
后记:其实这个面试题的实用价值还是非常大的,这里是整数,如果是字符串排序也可以采用类似算法,而且空间要求比较小。
声明:该算法是本人的原创算法,转载请注明出处,谢谢!
注,本算法也不是稳定的.
另外:面试题集锦,大家可以去July的博客看看,会很有收获:http://blog.csdn.net/v_JULY_v
分享到:
相关推荐
函数`depart`中,数组`a`用来接收从整数`n`中去除某一位后的结果,`sort`和`output`函数则分别对这个数组进行排序和打印。 2. **冒泡排序**:冒泡排序是一种简单的排序算法,它重复地遍历待排序的数列,一次比较两...
JAVA经典算法40面试题 本资源摘要信息涵盖了JAVA经典算法40面试题,包含基本的算法面试代码题。以下是对标题、描述、标签和部分内容的详细解释: 一、标题:“JAVA经典算法40面试题” 该标题表明该资源是关于JAVA...
JAVA经典算法面试39题及答案 本资源总结了39道经典的 JAVA 算法面试题目,每个题目都附带答案,涵盖了常见的算法问题,旨在帮助读者更好地掌握 JAVA 编程语言和算法设计。 算法概述 算法是计算机科学中最重要的...
### 前端面试题:常用算法 #### 时间复杂度 在评估算法性能时,我们通常关注的是**时间复杂度**。它用来描述算法运行时间与输入数据规模之间的关系。通常,我们会关注最糟糕情况下的时间复杂度,因为它提供了一个...
用java编的一个小程序,输入10个整数,去掉其中重复的数后输出
- **题目描述**:给定 n 个非负整数,每个数代表坐标中的一个点 (i, ai),在坐标内画 n 条垂直线,找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 - **示例**:输入 `[1,8,6,2,5,4,8,3,7]`,...
在计算机科学中,将一个十进制整数转换为其他任意进制(如二进制、八进制、十六进制或介于二至九之间的任何其他进制)是常见的编程任务。这种转换通常用于数据表示、计算以及理解计算机内部工作原理。下面我们将详细...
标题中的“纯数数组去重复算法1千万3秒”指的是一个高效的算法,它能在3秒钟内处理含有1000万个唯一数值的数组并去除其中的重复元素。这个算法的性能特点是不受数据重复数量的影响,无论是1千万个还是1亿个重复元素...
用算法去除数组中重复的数字(不使用查重函数)通过嵌套for循环和if条件判断实现,用算法去除数组中重复的数字(不使用查重函数)通过嵌套for循环和if条件判断实现
4. 第k小整数问题:这个算法问题属于选择排序的范畴,关键在于找出一组数中第k小的数。在信息学奥赛中,常用的方法是快速选择算法(Quick Select),这是快速排序的变种,它可以在平均线性时间内找到第k小的元素。 ...
根据给定文件的信息,本文将围绕“去除重复数据”这一主题进行深入探讨,重点解析一个简单易懂且适用性广的去重算法,并通过具体的代码示例来展示其实现过程。 ### 去除重复数据的基本概念 在计算机科学中,“去除...
- **哈希表优化**:可以使用哈希表(如C语言中的字典结构)来记录已出现的数字,这样可以在O(1)时间内检查一个数字是否重复,整体时间复杂度降为O(n)。 - **排序后去重**:先对数组进行排序,然后线性扫描数组去重,...
LMS(Least Mean Squares)算法,全称为最小均方误差算法,是自适应滤波理论中的一个核心算法,主要用于在线估计线性系统的参数。它由斯坦福大学的John W. Widrow和Marcian E. Hoff在1960年提出,因其简单高效而在...
在本篇【源代码】C++算法中,我们主要探讨的是如何在一维数组中去除重复元素,同时确保算法的时间复杂度为O(n),并且在整个处理过程中不额外开辟新的内存空间。这种问题在实际编程中非常常见,特别是在处理大量数据...
Java 面试经典算法是指在 Java 面试中经常会被问到的算法题目,这些题目涵盖了数据结构、算法设计、编程语言基础知识等方面的知识。本文总结了 17 道 Java 面试经典算法题目,并对每道题目进行了详细的分析和解释。 ...
【程序 15】:排序问题,可以使用冒泡排序、选择排序等简单排序算法,这里是最简单的三数取小法,每次比较并交换相邻的两个数,确保每次之后的第一个数都是当前未排序部分的最小值。 【程序 16】:9乘法口诀表,...
Java 算法编程题汇总是本文的主题,本文将对 50 道 Java 算法编程题进行汇总,涵盖了 Fibonacci 序列、素数、水仙花数、质因数分解等多个方面的知识点。 Fibonacci 序列 Fibonacci 序列是一个经典的算法问题,...
### 算法面试题总结 #### 一、两数之和 **题目描述:** ...以上是针对“两数之和”和“回文数”这两个经典的算法面试题的解析及其不同解法的实现。这些方法不仅有助于理解题目,还能够提升解决实际问题的能力。
判断素数的常用算法是埃拉托斯特尼筛法,但在这个问题中,我们可以简单地遍历该范围内的每个数,用2到该数平方根的每个数尝试去除,如果能被整除则不是素数,否则是素数。 【程序 3】"水仙花数"是三位数中的一种...