題目
給你一個整數數組 arr。你可以從中選出一個整數集合,並刪除這些整數在數組中的每次出現。
返回 至少 能刪除數組中的一半整數的整數集合的最小大小。
示例 1:
輸入:arr = [3,3,3,3,5,5,5,2,2,7]
輸出:2
解釋:選擇 {3,7} 使得結果數組爲 [5,5,5,2,2]、長度爲 5(原數組長度的一半)。
大小爲 2 的可行集合有 {3,5},{3,2},{5,2}。
選擇 {2,7} 是不可行的,它的結果數組爲 [3,3,3,3,5,5,5],新數組長度大於原數組的二分之一。
示例 2:
輸入:arr = [7,7,7,7,7,7]
輸出:1
解釋:我們只能選擇集合 {7},結果數組爲空。
示例 3:
輸入:arr = [1,9]
輸出:1
示例 4:
輸入:arr = [1000,1000,3,7]
輸出:1
示例 5:
輸入:arr = [1,2,3,4,5,6,7,8,9,10]
輸出:5
提示:
1 <= arr.length <= 10^5
arr.length 爲偶數
1 <= arr[i] <= 10^5
解法一(順序求解)
思路:最簡單樸素的解法,首先進行元素統計,然後根據統計的結果按最大到小進行計算,不過當前解法C#會在數據大的情況下超時,而Python和GO不會,超時測試用例有6萬多個元素,源碼及超時用例如下:https://www.zhenxiangsimple.com/files/tech/testCase20200202.txt
- 統計元素個數
- 按個數進行最大值累計,相當於冒泡排序
- 時間複雜度:O(n2)
- 空間複雜度:O(n)
public class Solution {
public int MinSetSize(int[] arr) {
Dictionary<int,int> r = new Dictionary<int,int>();
for(int i=0;i<arr.Length;i++)
{//數據統計
if(r.ContainsKey(arr[i]))
{
r[arr[i]] += 1;
}
else
{
r[arr[i]] = 1;
}
}
int c = 0,ct = 0;
while(c * 2<arr.Length)
{//不需要全排序,只要滿足條件即退出
int mx = 0,idx = -1;
foreach(var item in r)
{//取數據最大值
if(item.Value > mx)
{
idx = item.Key;
mx = item.Value;
}
}
r.Remove(idx);//最大值記錄後退出循環
c += mx;
ct ++;
}
return ct;
}
}
解法二(字典排序)
思路:基於解法一,先對結果進行排序,然後使用排序後的結果進行統計,按道理解法一更快,但可能.NET3.5對Dictionary的排序優化使得效率比自己查詢更高。
- 時間複雜度:不算排序部分,O(n)
- 空間複雜度:O(n)
public class Solution {
public int MinSetSize(int[] arr) {
Dictionary<int,int> r = new Dictionary<int,int>();
for(int i=0;i<arr.Length;i++)
{//數據統計
if(r.ContainsKey(arr[i]))
{
r[arr[i]] += 1;
}
else
{
r[arr[i]] = 1;
}
}
//排序
var rOrder = from ro in r orderby ro.Value descending select ro;
int c = 0,ct = 0;
foreach(KeyValuePair<int,int> item in rOrder)
{//累計
c += item.Value;
ct ++;
if(c*2 >= arr.Length)
{
return ct;
}
}
return 0;//僅爲編譯
}
}
解法三(分類統計)
思路:基於分類統計的思想,首先將元素進行統計,使用數組下標表示數組中元素個數,數組元素的值表示不同元素的個數,最後通過統計該數組下標即可。如果該元素爲0,則表示不存在當前個數(數組下標對應的個數)的元素。
- 統計數組中重複元素的個數放入數組r
- 對數組中元素個數進行統計判斷
- 如果已經超出半數,則減去重複的個數(比如,r[2]=3,表示有兩個相同元素的元素有3個,比如1,1,2,2,3,3,…,那麼不需要3個元素全部累計,只需要滿足超出半數即可,因此需要把多餘的元素減去。
- 時間複雜度:O(n)
- 空間複雜度:O(n)
public class Solution {
public int MinSetSize(int[] arr) {
int [] r = new int[arr.Length+1];//存儲元素個數
r[0] = arr.Length;
Dictionary<int,int> d = new Dictionary<int,int>();
foreach(int item in arr)
{
if(d.ContainsKey(item))
{//已有元素時累計
d[item]++;
}
else
{//新元素增加
d.Add(item,1);
}
r[d[item]]++;
r[d[item]-1]--;
}
int c = 0,t = 0,idx = 0;
for(int i=arr.Length;i>0;i--)
{//從最大值到最小值進行統計
if(r[i]>0)
{
c+= i*r[i];
t+=r[i];
idx = i;
}
if(c*2 >= arr.Length)
{//如果超出限制,則減去重複的部分
t -= (c*2 - arr.Length)/(2*i);
break;
}
}
return t;
}
}
解法四(空間換時間)
思路:基於解法三的思路做一個變化,將值和索引做個交換,即使用一個大的空間,索引即爲值本身,數組元素的值爲該值對應的個數。缺點就是如果本身的值很大,就比較耗費資源,可以先便利一邊找到最大值,也可以直接選擇一個相對大的值,本次選擇後者,一個相對大的值110000(開始選擇10萬,提示越界改爲11萬可以了)。
- 統計值的個數
- 對統計數組進行排序
- 將排序結果從大到小進行求解
- 時間複雜度:O(n),不算排序部分
- 空間複雜度:O(1),雖然是1,但其實最大
public class Solution {
public int MinSetSize(int[] arr) {
int[] r = new int[110000];
foreach(int item in arr)
{//元素個數統計
r[item]++;
}
Array.Sort(r);//排序
int c = 0,ct = 0;
for(int i=r.Length-1;i>0;i--)
{//個數計數
c += r[i];
ct ++;
if(c*2 >= arr.Length)
{
return ct;
}
}
return 0;//僅爲編譯
}
}