算法 第25周 136题 只出现一次的数字 和 287题 寻找重复数

279 阅读3分钟

136题 只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4

利用 260题:只出现一次的数字 III 所学的异或知识可以轻松解决该问题

class Solution {
    public int singleNumber(int[] nums) {
        int temp = 0;
        for(int num: nums){
            temp = temp^num ;
        }
        return temp;
    }
}

287题 寻找重复数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2

示例 2:

输入: [3,1,3,4,2]
输出: 3

说明:

1、不能更改原数组(假设数组是只读的)。

2、只能使用额外的 O(1) 的空间。

3、时间复杂度小于 O(n2) 。

4、数组中只有一个重复的数字,但它可能不止重复出现一次。

暴力解法

    public int findDuplicate(int[] nums) {
                for(int i=0;i<nums.length;i++) {
             for(int j=i+1;j<nums.length;j++) {
                      if(nums[i]==nums[j]) {
                         return nums[i];
                      }
             }
         }
         return 0; 
    }

结果可想而知

其他思路(只能针对重复一次,该题目无法通过)

该思路利用数组的数字在1到N。所有存在一个值被替换,则通过正常无重复状态与改被替换的差值求出重复的值。

    public int findDuplicate(int[] nums) {
          int temp = 0;
          for(int num:nums){
              temp = temp+num;
          }
          return nums.length+(temp-(1+nums.length)*nums.length/2);
    }

官方解法

非常想吐槽官方的说明,都说好了不能修改原数组,为什么可以使用排序

方法一:排序 思路就不需要说清楚,就是排序之后规则

class Solution {
    public int findDuplicate(int[] nums) {
        Arrays.sort(nums);
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] == nums[i-1]) {
                return nums[i];
            }
        }

        return -1;
    }
}

方法二:集合 该方法其他不可取,说明里面明确了只能使用额外的 O(1) 的空间

class Solution {
    public int findDuplicate(int[] nums) {
        Set<Integer> seen = new HashSet<Integer>();
        for (int num : nums) {
            if (seen.contains(num)) {
                return num;
            }
            seen.add(num);
        }

        return -1;
    }
}

方法三:弗洛伊德的乌龟和兔子(循环检测)

class Solution {
    public int findDuplicate(int[] nums) {
        // Find the intersection point of the two runners.
        int tortoise = nums[0];
        int hare = nums[0];
        do {
            tortoise = nums[tortoise];
            hare = nums[nums[hare]];
        } while (tortoise != hare);

        // Find the "entrance" to the cycle.
        int ptr1 = nums[0];
        int ptr2 = tortoise;
        while (ptr1 != ptr2) {
            ptr1 = nums[ptr1];
            ptr2 = nums[ptr2];
        }

        return ptr1;
    }
}

官方解释:

本算法关键就是重复数字会导致形成环,环解决就是通过快慢指针。然后该重复数组就是换的入口。

参考资料: 快慢指针判断单向链表是否有环及找环入口

通过参考资料可以弄明白,索引和数值可以认为链表,由于数值存在重复所以会回到原来相同所以位置。所以形成的环。

所以代码分成两个部分

第一次相遇

        do {
            tortoise = nums[tortoise];
            hare = nums[nums[hare]];
        } while (tortoise != hare);

第二次快指针改为步数为1,直到相遇

        int ptr1 = nums[0];
        int ptr2 = tortoise;
        while (ptr1 != ptr2) {
            ptr1 = nums[ptr1];
            ptr2 = nums[ptr2];
        }