劍指offer題解(Java實現)—— 面試題3:數組中重複的數字

前言

本系列的文章爲筆者學習《劍指offer第二版》後的筆記整理以及Java實現,解法不保證與書上一致。

另外,我最近在系統整理一些 Java 後臺方面的面試題和參考答案,有找工作需求的童鞋,歡迎關注我的 Github 倉庫,如果覺得不錯可以點個 star 關注 :

題目描述

在一個長度爲 n 的數組裏的所有數字都在 0~n-1 的範圍內。 數組中某些數字是重複的,但不知道有幾個數字重複了,也不知道每個數字重複幾次。請找出數組中任意一個重複的數字。例如,如果輸入長度爲 7 的數組 {2,3,1,0,2,5,3},那麼對應的輸出是第一個重複的數字 2 或者 3。

解題思路

解決這個問題主要有三種方法:

思路一

先將輸入的數組排序,然後從頭到尾遍歷排序後的數組,如果有重複的數字,那麼一定會存在相鄰元素相等的情況。從頭到尾遍歷的過程種,只需相鄰元素涼涼比較,有相等的情況就找到了重複數字。很明顯,掃描數組需要的時間複雜度爲 O(n),而排序數組則需要 O(nlogn) 的時間複雜度,所以該方法需要總的時間複雜度爲 O(nlogn)。

	/**
     * 方法一:先排序後遍歷查找看是否有相鄰元素相等
     * @param nums 待查找的數組
     * @param duplication 用於保存重複數字,第一個被找到的重複數字存放在duplication[0]中
     * @return 數組中的重複數字(如果有的話)
     */
    public boolean duplicate1(int[] nums, int[] duplication) {
        int length = nums.length;
        if (nums == null || length <= 0) {
            return false;
        }

        // 排序數組,時間複雜度爲 O(nlogn)
        Arrays.sort(nums);
        for (int i = 0; i < length - 1; i++) {
            if (nums[i] == nums[i + 1]) {
                // 排序後相鄰元素相等,說明有重複元素,將其保存到 duplication[0] 中
                duplication[0] = nums[i];
                return true;
            }
        }
        return false;
    }

思路二

通過哈希表來解決。從頭到尾按順序掃描數組中的每個數字,可以通過 O(1) 的時間複雜度來判斷哈希表中是否包含某個數字。每掃描到到一個數字時,如果哈希表中不存在該數字,就將其加入哈希表中。反之,表中已存在則說明找到了一個重複的數字。

這個算法的時間複雜度爲 O(n),但需要消耗大小爲 O(n) 的哈希表的空間,也就是說空間複雜度爲 O(n)。

代碼暫略...

思路三

根據題幹中的信息“長度爲 n 的數組裏的所有數字都在 0 到 n-1 的範圍內”可知,如果數組中沒有重複的數字,那麼數組經過排序之後數字 i 將出現在下標爲 i 的位置,即 nums[i] = i。但現在由於數組中存在重複的數字,有些數字可能有多個,而有些數字可能空缺了。

如果數組中存在重複數字,就肯定會存在某個 j != i時,nums[j] = nums[i]的情況,只要能找到滿足這個條件的數字,則找到了重複的數字。現在我們可以來這麼做:

  • 1、掃描到下標 i 時,如果 nums[i] == i,則繼續接着掃描下一個數;
  • 2、如果 nums[i] != i,則令 nums[i] = j,比較 nums[i]nums[j]的值是否相等,如果相等,說明在與 i 不同的位置 ‘j’ 找到了一個重複的數字。
  • 在第 2 步的基礎上,如果 nums[i] != nums[j],則將第 i 個數字和第 ‘j’ 個數字交換。接下來不斷重複這個比較、交換的過程,直到找到一個重複的數字(除非不存在重複數字)。
package com.offers.chapter2.question03;

import java.util.Arrays;

/**
 * @author Rotor
 * @since 2019/9/21 0:35
 */
public class FindDuplicate {
    /**
     * 方法三:利用題幹信息"長度爲n的數組裏的所有數字都在0到n-1的範圍內"推導
     *
     * @param nums 待查找的數組
     * @param duplication 用於保存重複數字,第一個被找到的重複數字存放在duplication[0]中
     * @return 數組中的重複數字(如果有的話)
     */
    public boolean duplicate3(int[] nums, int[] duplication) {
        int length = nums.length;
        if (nums == null || length <= 0) {
            return false;
        }

        // 數組中包含 0~n-1 之外的無效的數字
        for (int i = 0; i < length; i++) {
            if (nums[i] < 0 || nums[i] > length - 1) {
                return false;
            }
        }

        for (int i = 0; i < length; i++) {
            while (nums[i] != i) {
                /**
                 * nums[i] != i,設numbers[i] = j,所以如果下面的 if 條件成立,即numbers[i] == numbers[j],
                 * 說明第 i 位與第 nums[i] 位數字相等,找到重複數字
                 */
                int j = nums[i];
                if (nums[i] == nums[j]) {
                    duplication[0] = nums[i];
                    return true;
                }
                swap(nums, i, nums[i]);
            }
        }
        return false;
    }

    // 交換 nums[i] 與 nums[nums[i]]
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    public static void main(String[] args) {

    }

}

代碼中有一個兩重循環,但每個數字最多隻要交換兩次就能找到屬於它自己的位置,因此總的時間複雜度爲 O(n)。另外,所有的操作步驟都是在輸入數組上進行的,不需要額外分配內存。所以空間複雜度爲 O(1)。

總結

  • 本體主要考察對一維數組的理解及編程能力。一維數組在內存中佔據連續的存儲空間,因此可以根據下標定位對應的元素;
  • 遇到類似的問題時,可以結合題幹給出的信息進行分析。

後記

如果你同我一樣想要努力學好數據結構與算法、想要刷 LeetCode 和劍指 offer,歡迎關注我 GitHub 上的 LeetCode 題解:awesome-java-notes

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章