LeetCode 面试题 217. Contains Duplicate 找出数组中重复元素

题目: Given an array of integers, find if the array contains any duplicates. Your function should return true if any value appears at least twice in the array, and it should return false if every element is distinct.
给定一个 int 数组, 找出是否含有重复值.

看到此题, 首先想到用二分法, 因为最近刚学完排序算法中的快速排序. 具体操作是: 先把数组打乱, 选择一个元素 a[lo], 下标 i 从小到大, 遇到 a[i] 比 a[lo] 大的停止, 下标 j 从大到小, 遇到 a[j] 比 a[lo] 小的停止, 然后交换 a[i] 和 a[j]. 当 i >= j, 此时 a[lo] 左边的元素全部大于右边的元素. 此过程中如果遇到等于 a[lo] 的, 则含有重复值, 得证, 如果没有遇到, 则对左边子数组和右边的重复上述过程(递归). 具体代码如下:

import java.util.Random;
public class ContainsDuplicate_No217 {           
    static boolean flag;

    public boolean containsDuplicate(int[] nums) {
        flag=false;
        shuffle(nums);
        find(0, nums.length - 1, nums);
        return flag;
    }

    public static void find(int lo, int hi, int[] nums) {
        if (flag || lo >= hi)
            return;
        int p = partition(lo, hi, nums);
        find(lo, p - 1, nums);
        find(p + 1, hi, nums);
    }

    /* 分割数组 */
    public static int partition(int lo, int hi, int[] nums) {
        int comp = nums[lo];
        int i = lo;
        int j = hi + 1;
        // i 从左向右递增,遇到比 nums[lo]大的数停止; j 从右向左递减,遇到比 nums[lo]小的数停止. 然后交换 nums[i]
        // 和 nums[j]. 循环往复,直到 i, j 相遇, 交换 nums[lo], nums[j].至此, nums[lo]左边的数全部小于右边.
        // 在这个过程中, 如果遇到等于 nums[lo] 的,则 flag=true, 得证.
        while (true) {
            while (nums[++i] < comp) {
                if (i == hi)
                    break;
            }
            if (nums[i] == comp) {
                flag = true;
                break;
            }
            while (nums[--j] > comp) {
                if (j == lo)
                    break;
            }
            if (nums[j] == comp && j != lo) {
                flag = true;
                break;
            }
            if (i >= j) 
                break;
            exchange(nums, i, j);
        }
        if (flag)
            return lo;
        exchange(nums, j, lo);
        return j;
    }

    /* 交换两个元素 */
    private static void exchange(int[] nums, int m, int n) {
        int temp = nums[m];
        nums[m] = nums[n];
        nums[n] = temp;
    }
    
    /* 打乱数组     */
    private static void shuffle(int[] nums){
        Random random = new Random();
        int N =nums.length;
        for(int i=0;i<N-1;i++){
            int rd=random.nextInt(N-i);
            exchange(nums,i,rd);
        }
    }  
}

上述代码在 LeetCode 提交后显示, 快于 25% 的人. 看来还有更快的算法. 然后我想到可以用 HashSet, 直接用 HashSet 似乎速度提升有限, 而且直接调用现成算法也不符合锻炼自己的目的. 所以我参照 HashSet 源码后, 自己实现了一个简化版的 HashSet, 提交后显示快于 97% 的人. 代码如下:
HahSet 的原理见: HashMap存储结构浅析-作者:highriver

public class ContainsDuplicate_No217 {
    
    static class Node{
        final int value;
        Node next;
        public Node(int value, Node next) {
            super();
            this.value = value;
            this.next = next;
        }
    }
    
    public boolean containsDuplicate(int[] nums){
        int N=nums.length;
        int cap=2;
        while(N>cap)
            cap*=2;
        Node[] table =new Node[cap];
        boolean flag=false;
        Node p;
        int v;
        for(int i=0;i<N;i++){
            int index=(cap-1)&(v=nums[i]);
            if((p=table[index])==null){
                table[index]=new Node(v,null);
            }else{
                while(true){
                    if(p.value==v){
                        System.out.println(v);
                        i=N;
                        flag=true;
                        break;
                    }
                    if(p.next==null){
                        p.next=new Node(v,null);
                        break;
                    }
                    p=p.next;
                }
            }
        }
        return flag;
    } 
}

Google 搜索后发现更快的算法用的位操作. 不过, 此方法 byte 数组大小15000, 是针对了 LeetCode 的测试用例优化过的. 当数组 nums 更大时不知效率如何.

public class Solution {
    public boolean containsDuplicate(int[] nums) {
        byte[] mark = new byte[150000];
        for (int i : nums) {
            int j = i/8;
            int k = i%8;
            int check = 1<<k;
            if ((mark[j] & check) != 0) {
                return true;
            }
            mark[j]|=check;
        }
        return false;
    }
}
posted @ 2016-01-24 19:19  CloudFlew  阅读(1516)  评论(0编辑  收藏  举报