【剑指Offer】面试题3:数组中重复的数字——JS实现

题目描述:

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出应该是2或3。

代码如下:

解法一:

// 从第一个数开始,每一个数依次与其后面的数字进行比较,若相等,则将其赋值到duplication[0],并返回 true 。
function duplicate(numbers, duplication){
    //这里要特别注意:找到任意重复的一个值并赋值到duplication[0]
    //函数返回True/False
    
    let len = numbers.length;
    for(let i=0; i<len-1; i++){
        for(let j=i+1; j<len; j++){
            if(numbers[i]===numbers[j]){
                duplication[0] = numbers[i];
                return true;
            }
        }
    }
    
    return false;
}

解法二(冒泡排序):

// 首先将数组进行排序,然后再从头到尾扫描一次排序后的数组即可。
function duplicate(numbers, duplication){
    //这里要特别注意:找到任意重复的一个值并赋值到duplication[0]
    //函数返回True/False

    let len = numbers.length;
    // 冒泡排序:平均时间复杂度O(n^2)
    for(let i=0; i<len-1; i++){
        for(let j=0; j<len-i-1; j++){
            if(numbers[j]>numbers[j+1]){
                let temp = numbers[j];
                numbers[j]  = numbers[j+1];
                numbers[j+1] = temp;
            }
        }
    }

    for(let i=0; i<len-1; i++){
        if(numbers[i]===numbers[i+1]){
            duplication[0] = numbers[i];
            return true;
        }
    }
    
    return false;
}

解法二(快速排序):

function duplicate(numbers, duplication){
    //这里要特别注意:找到任意重复的一个值并赋值到duplication[0]
    //函数返回True/False

    let len = numbers.length;
    // 快速排序:平均时间复杂度O(nlogn)
    numbers = quickSort(numbers);

    for(let i=0; i<len-1; i++){
        if(numbers[i]===numbers[i+1]){
            duplication[0] = numbers[i];
            return true;
        }
    }
    
    return false;
}

function quickSort(n){
    if(n.length<=1){
        return n;
    }
    let midIndex = Math.floor(n.length / 2);
    let mid = n.splice(midIndex,1)[0];
    console.log(n);  // 如果原来的 n = [4,7,2,9,3], 则现在的 n =[ 4, 7, 9, 3 ]
    console.log(n.splice(midIndex,1));  // [2]
    console.log(mid);  // 2
    let left = [];
    let right = [];
    for(let i=0; i<n.length; i++){
        if(n[i] < mid){
            left.push(n[i])
        } else {
            right.push(n[i])
        }
    }
    return quickSort(left).concat([mid],quickSort(right))
}

解法三:

// 将数组中的第i个数字(从第0个数开始,和数组的下角标保持一致)与其下角标比较。
// 若相等,比较下一个;否则把它交换到下角标与它相同的位置上。
// 如果那个相同位置上原本的值与它相等,说明出现重复数字,此时则将其赋值到duplication[0],并返回 true 。 
function duplicate(numbers, duplication){
    //这里要特别注意:找到任意重复的一个值并赋值到duplication[0]
    //函数返回True/False

    let len = numbers.length; 
    
    if(len<=0){
        return false;
    }

    for(let i=0; i<len; i++){
        if(numbers[i]<0 || numbers[i]>len-1){
            return false;
        }
    }

    for(let i=0; i<len; i++){
        while(numbers[i]!==i){
            if(numbers[i] === numbers[numbers[i]]){
                duplication[0] = numbers[i];
                return true;
            }
            let temp = numbers[i];
            numbers[i] = numbers[temp];
            numbers[temp] = temp;
        }
    }
    
    return false;
}

解法四:

哈希表。从头到尾扫描数组的每一个数字,每扫描到一个数字,都可以用O(1)的时间来判断哈希表里是否已经包含了该数字。如果哈希表里还没有这个数字,就把它加入哈希表。如果哈希表已经存在该数字,就找到一个重复的数字。

解法比较:

第一种方法:时间复杂度为O(n^2),空间复杂度为O(1)。

代码中有一个两重循环,因此,总的时间复杂度为O(n^2)。另外,所有的操作都只是将输入数组进行比较,没有额外分配内存,因此空间复杂度为O(1)。

第二种方法(冒泡排序):时间复杂度为O(n^2),空间复杂度为O(1)。

第二种方法(快速排序):时间复杂度为O(nlogn),空间复杂度为O(logn)。

第三种方法:时间复杂度为O(n),空间复杂度为O(1)。

代码中尽管有一个两重循环,但每个数字最多只要交换两次就能找到属于它自己的位置,因此,总的时间复杂度为O(n)。另外,所有的操作都是在输入数组上进行的,不需要额外分配内存,因此空间复杂度为O(1)。

第四种方法:时间复杂度为O(n),空间复杂度为O(n)。

拓展练习:

题目描述:不修改数组找出重复的数字。

在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8 的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或3。

代码如下:

function getDuplication(numbers){
    if(numbers.length<=0){
        return -1;
    }
    let start = 1;
    let end = numbers.length-1;
    while(end>=start){
        let middle = ((end-start)>>1)+start;
        let count = countRange(numbers,start,middle);
        if(end===start){
            if(count>1){
                return start;
            } else
                break;
        }
        if(count>middle-start+1){
            end = middle;
        } else {
            start = middle + 1;
        }
    }
    return -1;
}

function countRange(numbers,start,end){
    if(numbers.length<=0){
        return -1;
    }
    let count = 0;
    for(let i=0; i<numbers.length; i++){
        if(numbers[i]>=start && numbers[i]<=end){
            count++;
        }
    }
    return count;    
}

// 测试
console.log(getDuplication([2,3,5,4,3,2,6,7]))

上述代码按照二分查找的思路,如果输入长度为n的数组,那么函数countRange将被调用O(logn)次,每次需要O(n)的时间,因此总的时间复杂度为O(nlogn),空间复杂度为O(1)。

思考:

在不同的功能要求(找出任意一个重复的数字、找出所有重复的数字)或者性能要求(时间效率优先、空间效率优先)的条件下,应该选择哪种算法?

END

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值