题目:在非负数组(乱序)中找到最小的可分配的id(从1开始编号),数据量10000000。
题目解读:在一个不重复的乱序的自然数组中找到最小的缺失的那个数,比如1,2,3,6,4,5,8,11。那么最小可用id就为7。
个人总结有三个方法来进行解答,但是算法性能不一样:
第一种,暴力解决,设置一个变量,依次遍历数组中每个元素,如果哪个元素没有直接return
public static int g(int a[]){
int i=1;
for (int j = 0; j < a.length; j++) {
if(judge(a,i)==true){//判断数组a中是否有i这个数字,没有return
return i;
}
i++;
}
return -1;
}
private static boolean judge(int[] a, int i) {//判断数组a中是否有i
// TODO 自动生成的方法存根
for (int j = 0; j < a.length; j++) {
if(a[j]==i){
return false;
}
}
return true;
}
该算法复杂度O(n^2);
第二种类似于计数排序算法的思想,就是把数组中的值转下标,把值+1,有数字的里面的值+1,只需判断数组中哪些地方没有1就行,典型的空间换区时间复杂度算法
public static int f(int a[]){
int max=0;
for (int i = 0; i < a.length; i++) {//找出数组中的最大值
if(max<a[i]){
max=a[i];
}
}
int b[]=new int[max];//建立新数组
for (int i = 0; i < a.length; i++) {//数字换成下标减1
b[a[i]-1]++;
}
for (int i = 0; i < b.length; i++) {//扫面辅助数组
if(b[i]==0){//为0就是没有对应的数字
return i+1;
}
}
return -1;
}
时间复杂度O(n),但是空间资源浪费的很大。
第三种:使用快排里面的一种思想,就是在乱序中,可以知道下标的思想;
在将这种方法之前,可以先做一下如何在乱序中找到指定下标顺序中所对应的数字?
这个就是快排里面的分区思想:
具体代码如下:
public static int select(int a[],int p,int r,int index){//index就是想要知道的下标
int q=partition(a,p,r);//partition方法就是快排分区算法
if(index==q){//相同直接返回
return a[q];
}else if(index<q){//index在左边
return select(a, p, q-1, index);//向左找
}else{
return select(a, q+1, r, index);//向右找
}
}
接下来就是partition代码:
private static int partition(int[] a, int p, int r) {
// TODO 自动生成的方法存根
int povit=a[p];//定主元
int left=p+1;//左指针
int right=r;//右指针
while(left<=right){
while(left<=right&&a[left]<=povit){//左边小于主元
left++;
}
while(left<=right&&a[right]>povit){//右边大于主元
right--;
}
if(left<=right){//左边大,右边小,交换
swap(a,left,right);
}
}
swap(a,p,right);//把主元放法主元应该放到的位置上面
return right;//返回主元下标
}
上面代码就是如何在乱序中找到指定下标顺序中所对应的数字,但是和最小id有什么关系呢?
关系就是,每次返的主元下标+1是否等于主元,如果等于主元说明主元的左边包括主元是稠密的,那么位置上面数字不同的情况就在主元的右边,如果主元和下标不对应,说明主元的左侧是稀疏的,接下来的算法类似于二分思想:
public static int minid(int a[],int p,int r){
if(p<r){
int mid=(p+r)/2;//每次找区间的中间下标
int middle=select(a, 0, a.length-1, mid);//找到下标对应的数字
if((mid+1)==middle){//开始比较==就在右边
return minid(a, mid+1, r);
}else{//不等于就在左边
return minid(a, p, mid);
}
}else{//当p>=r时候
return p+1;
}
}
该算法复杂度O(NlogN);
具体完整测试代码:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int a[]={1,2,3,6,4,5,8,11};
int test1=f(a);
System.out.println(test1);
int test2=g(a);
System.out.println(test2);
int test3=minid(a, 0, a.length-1);
System.out.println(test3);
}
public static int f(int a[]){//方法二
int max=0;
for (int i = 0; i < a.length; i++) {
if(max<a[i]){
max=a[i];
}
}
int b[]=new int[max];
for (int i = 0; i < a.length; i++) {
b[a[i]-1]++;
}
for (int i = 0; i < b.length; i++) {
if(b[i]==0){
return i+1;
}
}
return -1;
}
public static int g(int a[]){//方法一
int i=1;
for (int j = 0; j < a.length; j++) {
if(judge(a,i)==true){
return i;
}
i++;
}
return -1;
}
private static boolean judge(int[] a, int i) {
// TODO 自动生成的方法存根
for (int j = 0; j < a.length; j++) {
if(a[j]==i){
return false;
}
}
return true;
}
public static int minid(int a[],int p,int r){//方法三
if(p<r){
int mid=(p+r)/2;
int middle=select(a, 0, a.length-1, mid);
if((mid+1)==middle){
return minid(a, mid+1, r);
}else{
return minid(a, p, mid);
}
}else{
return p+1;
}
}
public static int select(int a[],int p,int r,int index){
int q=partition(a,p,r);
if(index==q){
return a[q];
}else if(index<q){
return select(a, p, q-1, index);
}else{
return select(a, q+1, r, index);
}
}
private static int partition(int[] a, int p, int r) {
// TODO 自动生成的方法存根
int povit=a[p];
int left=p+1;
int right=r;
while(left<=right){
while(left<=right&&a[left]<=povit){
left++;
}
while(left<=right&&a[right]>povit){
right--;
}
if(left<=right){
swap(a,left,right);
}
}
swap(a,p,right);
return right;
}
private static void swap(int[] a, int left, int right) {
// TODO 自动生成的方法存根
int temp=a[left];
a[left]=a[right];
a[right]=temp;
}
}
如果喜欢记得点个关注哟!