1.數組介紹
首先對數組進行基本的介紹,數組的特點就是連續存儲。在C++中,我們一般有兩種類型的數組:一種是靜態數組,我們事先知道其大小。一種是動態數組,也就是vector,事先可以不規定大小。
2.題目介紹
這一類型題目有很多種類型,比如數組是否有序,重複元素的個數等等。
2.1 找出數組中的重複元素
在一個長度爲n的數組裏的所有數字都在0到n-1的範圍內。 數組中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每個數字重複幾次。請找出數組中任意一個重複的數字。 例如,如果輸入長度爲7的數組{2,3,1,0,2,5,3},那麼對應的輸出是第一個重複的數字2。
根據題意,我們可以知道數組元素的範圍是確定的,以及最後的要求是輸出任意一個元素。
2.1.1排序,然後遍歷
已知數組是無序的(沒有說有序就是無序),我們可以先對數組進行排序,比如 ,排序之後得到,這樣一來,我們就可以通過判斷相鄰元素是否相等,得到重複元素。這種時間複雜度爲,空間複雜度爲。因爲排序是在數組上進行的。代碼如下:
bool duplicate1(int numbers[], int length, int* duplication) {
sort(numbers,numbers+length);
bool flag=false;
for(int i=0;i<length-1;i++)
{
if(numbers[i]==numbers[i+1])
{
*duplication= numbers[i];
flag=true;
break;
}
}
return flag;
}
2.1.2 使用哈希表
因爲數組中的每個元素都在確定的範圍裏,並且都爲正數。所以我們可以直接使用數組當作哈希表。因此我們額外創建一個長度爲n的數組,使用輸入數組的元素作爲下標。以爲例,我們初始化一個全是0的哈希數組,,我們最終要求的哈希表是,即;所以當的時候,我們就將對應元素放入哈希表。比如,;此時爲0,所以將;當我們遍歷到的時候,我們發現此時已經等於2了,則說明之前已經遇到過2了,那麼2爲重複元素。空間複雜度爲,時間複雜度爲.
bool duplicate2(int numbers[], int length, int* duplication) {
bool flag=false;
//array initialize
int hash_t[length];
for(int i=0;i<length;i++)
{
hash_t[i]=0;
}
for(int i=0;i<length;i++)
{
if(hash_t[numbers[i]]!=0)
{
flag=true;
*duplication=numbers[i];
break;
}
else
hash_t[numbers[i]]=numbers[i];
}
return flag;
}
2.1.3 手動重排數組
使用哈希表,空間複雜度過高;使用排序,時間複雜度過高。因爲採取一種折中的辦法,通過交換手動將輸入數組排序爲哈希表的樣子,也就是把每個數組元素放在以該元素爲下標的位置。
- 對於第0個位置,num[0]!=0,我們不知道0在哪裏,但是我們知道num[0]=2 這個2應該放在哪裏。所以我們先查看第2個位置的值,發現第二個位置的值爲1,不爲2,因此我們交換第0個位置的元素和第二個位置的元素,即
- 交換之後,其實第0個位置的元素依然不符合要求,因此我們進行如上操作,交換第0個位置和第1個位置的元素,即
- 依然不符合要求,進行操作,即
- 操作完成之後,發現第0~3位置都符合要求,因此我們對於第4個位置元素進行操作,第4個位置元素爲2,我們查看發現此位置的元素已經是2了,也就是說明此時不需要交換了,出現了一個多餘的2。依次類推,可以找出所有多餘元素。
時間複雜度爲,空間複雜度.
bool duplicate3(int numbers[], int length, int* duplication) {
bool flag=false;
for(int i=0;i<length;i++)
{
while(numbers[i]!=i)
{
if(numbers[numbers[i]]==numbers[i])//satisfied hash condition
{
flag=true;
*duplication=numbers[i];
return flag;
}
//exchange
int temp=numbers[i];
numbers[i]=numbers[temp];
numbers[temp]=temp;
}
}
return flag;
}
第三種方法效率最高,但是修改了數組本身,如果題目要求不修改數組呢?
2.2 不修改數組找到重複數字(Leetcode.287尋找重複數)
給定一個包含 n + 1 個整數的數組 nums,其數字都在 1 到 n 之間(包括 1 和 n),可知至少存在一個重複的整數。假設只有一個重複的整數,找出這個重複的數。
示例 1:
輸入: [1,3,4,2,2]
輸出: 2
示例 2:
輸入: [3,1,3,4,2]
輸出: 3
說明:
不能更改原數組(假設數組是隻讀的)。
只能使用額外的 O(1) 的空間。
時間複雜度小於 O(n2) 。
數組中只有一個重複的數字,但它可能不止重複出現一次。
2.2.1 使用hash表
因爲給定了數組元素的範圍,所以可以直接使用數組做hash表。和上面一樣,有的空間開銷。
int findDuplicate(vector<int>& nums) {
int n=nums.size();
int result;
vector<int>hash_n(n,0);
for(int i=0;i<n;i++)
{
if(hash_n[nums[i]]!=0)
{
result=nums[i];
break;
}
else
{
hash_n[nums[i]]=nums[i];
}
}
return result;
}
2.2.2 使用二分查找
因爲不可以修改數組,所以我們應該就是查找重複元素。並且只有一個重複元素,所以目標就是找到那個重複元素。
因爲所有元素都在1-n之間,所以我們可以劃分前後兩個區間,比如n=5,我們分成兩份1-2,3-5,我們統計所有元素在這兩個區間的個數,以爲例,在[1,2]之間的有3個,但是區間長度爲2,說明那個重複元素在[1,2]之間。然後再次進行劃分,因爲長度爲1,也就是統計1,2的次數,可以找到重複元素爲2. 時間複雜度爲,空間複雜度爲。
public:
int CountNumber(vector<int>& nums,int start,int end_)
{
int n=nums.size();
int ans=0;
for(int i=0;i<n;i++)
{
if(nums[i]>=start&&nums[i]<=end_)
++ans;
}
return ans;
}
int findDuplicate(vector<int>& nums) {
int length=nums.size();
int start=1;
int end_=length;
while(start<=end_)
{
int mid=(start+end_)/2;
int count_s_m=CountNumber(nums,start,mid);
if(end_==start&&count_s_m>1)
return start;
if(count_s_m>mid-start+1)
end_=mid;
else
start=mid+1;
}
return -1;
}