背景 前段時(shí)間,在知識(shí)星球立了一個(gè)Flag,這是總結(jié)Leetcode刷題的第四篇圖文。
理論部分 集合技術(shù)在解題中主要用于處理有數(shù)據(jù)重復(fù)出現(xiàn)的問(wèn)題 。
HashSet
C# 語(yǔ)言中 HashSet<T>
是包含不重復(fù)項(xiàng)的無(wú)序列表,稱(chēng)為“集合(set
)”。由于set
是一個(gè)保留字,所以用HashSet
來(lái)表示。
源碼:
https://referencesource.microsoft.com/#System.Core/System/Collections/Generic/HashSet.cs,2d265edc718b158b
HashSet的成員方法
public HashSet();
-> 構(gòu)造函數(shù)
public HashSet(IEnumerablecollection);
-> 構(gòu)造函數(shù)
public int Count { get; }
-> 獲取集合中包含的元素?cái)?shù)。
public bool Add(T item);
-> 將指定的元素添加到集合中。
public bool Remove(T item);
-> 從集合中移除指定元素。
public void Clear();
-> 從集合中移除所有元素。
public bool Contains(T item);
-> 確定集合中是否包含指定的元素。
public void UnionWith(IEnumerableother);
-> 并集
public void IntersectWith(IEnumerableother);
-> 交集
public void ExceptWith(IEnumerableother);
-> 差集
public bool IsSubsetOf(IEnumerableother);
-> 確定當(dāng)前集合是否為指定集合的子集。
public bool IsProperSubsetOf(IEnumerableother);
-> 確定當(dāng)前集合是否為指定集合的真子集。
public bool IsSupersetOf(IEnumerableother);
-> 確定當(dāng)前集合是否為指定集合的超集。
public bool IsProperSupersetOf(IEnumerableother);
-> 確定當(dāng)前集合是否為指定集合的真超集。
public bool Overlaps(IEnumerableother);
-> 確定是否當(dāng)前集合和指定的集合共享通用元素。
public bool SetEquals(IEnumerableother);
-> 確定是否當(dāng)前集合和指定集合包含相同的元素。
set
Python 中set
與dict
類(lèi)似,也是一組key
的集合,但不存儲(chǔ)value
。由于key
不能重復(fù),所以,在set
中,沒(méi)有重復(fù)的key
。
注意,key
為不可變類(lèi)型,即可哈希的值。
num = {} print(type(num)) # <class 'dict'> num = {1 , 2 , 3 , 4 } print(type(num)) # <class 'set'>
集合的創(chuàng)建
先創(chuàng)建對(duì)象再加入元素。
在創(chuàng)建空集合的時(shí)候只能使用s = set()
,因?yàn)?code style="font-size: inherit;line-height: inherit;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">s = {}創(chuàng)建的是空字典。
basket = set() basket.add('apple' ) basket.add('banana' ) print(basket) # {'banana', 'apple'}
直接把一堆元素用花括號(hào)括起來(lái){元素1, 元素2, …, 元素n}
。
重復(fù)元素在set
中會(huì)被自動(dòng)被過(guò)濾。
basket = {'apple' , 'orange' , 'apple' , 'pear' , 'orange' , 'banana' } print(basket) # {'banana', 'apple', 'pear', 'orange'}
a = set('abracadabra' ) print(a) # {'r', 'b', 'd', 'c', 'a'} b = set(("Google" , "Lsgogroup" , "Taobao" , "Taobao" )) print(b) # {'Taobao', 'Lsgogroup', 'Google'} c = set(["Google" , "Lsgogroup" , "Taobao" , "Google" ]) print(c) # {'Taobao', 'Lsgogroup', 'Google'}
lst = [0 , 1 , 2 , 3 , 4 , 5 , 5 , 3 , 1 ] temp = []for item in lst: if item not in temp: temp.append(item) print(temp) # [0, 1, 2, 3, 4, 5] a = set(lst) print(list(a)) # [0, 1, 2, 3, 4, 5]
從結(jié)果發(fā)現(xiàn)集合的兩個(gè)特點(diǎn):無(wú)序 (unordered) 和唯一 (unique)。
由于 set
存儲(chǔ)的是無(wú)序集合,所以我們不可以為集合創(chuàng)建索引或執(zhí)行切片(slice)操作,也沒(méi)有鍵(keys)可用來(lái)獲取集合中元素的值,但是可以判斷一個(gè)元素是否在集合中。
訪問(wèn)集合中的值
thisset = set(['Google' , 'Baidu' , 'Taobao' ]) print(len(thisset)) # 3
thisset = set(['Google' , 'Baidu' , 'Taobao' ])for item in thisset: print(item)# Baidu # Google # Taobao
thisset = set(['Google' , 'Baidu' , 'Taobao' ]) print('Taobao' in thisset) # True print('Facebook' not in thisset) # True
集合的內(nèi)置方法
set.add(elmnt)
-> 用于給集合添加元素,如果添加的元素在集合中已存在,則不執(zhí)行任何操作。
set.update(set)
-> 用于修改當(dāng)前集合,可以添加新的元素或集合到當(dāng)前集合中,如果添加的元素在集合中已存在,則該元素只會(huì)出現(xiàn)一次,重復(fù)的會(huì)忽略。
set.remove(item)
-> 用于移除集合中的指定元素。如果元素不存在,則會(huì)發(fā)生錯(cuò)誤。
set.discard(value)
-> 用于移除指定的集合元素。remove()
方法在移除一個(gè)不存在的元素時(shí)會(huì)發(fā)生錯(cuò)誤,而 discard()
方法不會(huì)。
set.pop()
-> 用于隨機(jī)移除一個(gè)元素。
set.intersection(set1, set2 …)
-> 返回兩個(gè)集合的交集。
set1 & set2
返回兩個(gè)集合的交集。
set.intersection_update(set1, set2 …)
-> 交集,在原始的集合上移除不重疊的元素。
set.union(set1, set2…)
-> 返回兩個(gè)集合的并集。
set1 | set2
-> 返回兩個(gè)集合的并集。
set.difference(set)
-> 返回集合的差集。
set1 - set2
-> 返回集合的差集。
set.difference_update(set)
-> 集合的差集,直接在原來(lái)的集合中移除元素,沒(méi)有返回值。
set.symmetric_difference(set)
-> 返回集合的異或。
set1 ^ set2
-> 返回集合的異或。
set.symmetric_difference_update(set)
-> 移除當(dāng)前集合中在另外一個(gè)指定集合相同的元素,并將另外一個(gè)指定集合中不同的元素插入到當(dāng)前集合中。
set.issubset(set)
-> 判斷集合是不是被其他集合包含,如果是則返回 True,否則返回 False。
set1 <= set2
-> 判斷集合是不是被其他集合包含,如果是則返回 True,否則返回 False。
set.issuperset(set)
-> 用于判斷集合是不是包含其他集合,如果是則返回 True,否則返回 False。
set1 >= set2
-> 判斷集合是不是包含其他集合,如果是則返回 True,否則返回 False。
set.isdisjoint(set)
-> 用于判斷兩個(gè)集合是不是不相交,如果是返回 True,否則返回 False。
frozenset
Python 提供了不能改變?cè)氐募系膶?shí)現(xiàn)版本,即不能增加或刪除元素,類(lèi)型名叫frozenset
。需要注意的是frozenset
仍然可以進(jìn)行集合操作,只是不能用帶有update
的方法。
應(yīng)用部分 題目1:存在重復(fù)元素
給定一個(gè)整數(shù)數(shù)組,判斷是否存在重復(fù)元素。
如果任何值在數(shù)組中出現(xiàn)至少兩次,函數(shù)返回 true。如果數(shù)組中每個(gè)元素都不相同,則返回 false。
示例 1 :
輸入: [1 ,2 ,3 ,1 ] 輸出: true
示例 2 :
輸入: [1 ,2 ,3 ,4 ] 輸出: false
示例 3 :
輸入: [1 ,1 ,1 ,3 ,3 ,4 ,3 ,2 ,4 ,2 ] 輸出: true
思路:通過(guò)集合的方法
C# 語(yǔ)言
狀態(tài):通過(guò)
18 / 18 個(gè)通過(guò)測(cè)試用例
執(zhí)行用時(shí): 156 ms, 在所有 C# 提交中擊敗了 93.33% 的用戶(hù)
內(nèi)存消耗: 30.3 MB, 在所有 C# 提交中擊敗了 5.31% 的用戶(hù)
public class Solution { public bool ContainsDuplicate (int [] nums) { if (nums.Length < 2 ) return false ; HashSet<int > h = new HashSet<int >(); foreach (int num in nums) { if (h.Contains(num)) return true ; h.Add(num); } return false ; } }
Python 語(yǔ)言
執(zhí)行結(jié)果:通過(guò)
執(zhí)行用時(shí):48 ms, 在所有 Python3 提交中擊敗了 78.11% 的用戶(hù)
內(nèi)存消耗:18.9 MB, 在所有 Python3 提交中擊敗了 24.00% 的用戶(hù)
class Solution : def containsDuplicate (self, nums: List[int]) -> bool: if len(nums) < 2 : return False h = set() for num in nums: if num in h: return True h.add(num) return False
題目2:相交鏈表
編寫(xiě)一個(gè)程序,找到兩個(gè)單鏈表相交的起始節(jié)點(diǎn)。
如下面的兩個(gè)鏈表:
在節(jié)點(diǎn) c1 開(kāi)始相交。
示例 1 :
輸入:intersectVal = 8 , listA = [4 ,1 ,8 ,4 ,5 ], listB = [5 ,0 ,1 ,8 ,4 ,5 ], skipA = 2 , skipB = 3 輸出:Reference of the node with value = 8 輸入解釋?zhuān)合嘟还?jié)點(diǎn)的值為 8 (注意,如果兩個(gè)列表相交則不能為 0 )。 從各自的表頭開(kāi)始算起,鏈表 A 為 [4 ,1 ,8 ,4 ,5 ],鏈表 B 為 [5 ,0 ,1 ,8 ,4 ,5 ]。 在 A 中,相交節(jié)點(diǎn)前有 2 個(gè)節(jié)點(diǎn);在 B 中,相交節(jié)點(diǎn)前有 3 個(gè)節(jié)點(diǎn)。
示例 2 :
輸入:intersectVal = 2 , listA = [0 ,9 ,1 ,2 ,4 ], listB = [3 ,2 ,4 ], skipA = 3 , skipB = 1 輸出:Reference of the node with value = 2 輸入解釋?zhuān)合嘟还?jié)點(diǎn)的值為 2 (注意,如果兩個(gè)列表相交則不能為 0 )。 從各自的表頭開(kāi)始算起,鏈表 A 為 [0 ,9 ,1 ,2 ,4 ],鏈表 B 為 [3 ,2 ,4 ]。 在 A 中,相交節(jié)點(diǎn)前有 3 個(gè)節(jié)點(diǎn);在 B 中,相交節(jié)點(diǎn)前有 1 個(gè)節(jié)點(diǎn)。
示例 3 :
輸入:intersectVal = 0 , listA = [2 ,6 ,4 ], listB = [1 ,5 ], skipA = 3 , skipB = 2 輸出:null 輸入解釋?zhuān)簭母髯缘谋眍^開(kāi)始算起,鏈表 A 為 [2 ,6 ,4 ],鏈表 B 為 [1 ,5 ]。 由于這兩個(gè)鏈表不相交,所以 intersectVal 必須為 0 ,而 skipA 和 skipB 可以是任意值。 解釋?zhuān)哼@兩個(gè)鏈表不相交,因此返回 null。
注意 :
如果兩個(gè)鏈表沒(méi)有交點(diǎn),返回 null.
在返回結(jié)果后,兩個(gè)鏈表仍須保持原有的結(jié)構(gòu)。
可假定整個(gè)鏈表結(jié)構(gòu)中沒(méi)有循環(huán)。
程序盡量滿足 O(n) 時(shí)間復(fù)雜度,且僅用 O(1) 內(nèi)存。
思路:通過(guò)集合的方法
C# 語(yǔ)言
狀態(tài):通過(guò)
45 / 45 個(gè)通過(guò)測(cè)試用例
執(zhí)行用時(shí): 172 ms, 在所有 C# 提交中擊敗了 100.00% 的用戶(hù)
內(nèi)存消耗: 37.6 MB, 在所有 C# 提交中擊敗了 5.88% 的用戶(hù)
/** * Definition for singly-linked list. * public class ListNode { * public int val; * public ListNode next; * public ListNode(int x) { val = x; } * } */ public class Solution { public ListNode GetIntersectionNode (ListNode headA, ListNode headB) { HashSet<ListNode> hash = new HashSet<ListNode>(); ListNode temp = headA; while (temp != null) { hash.Add(temp); temp = temp.next; } temp = headB; while (temp != null) { if (hash.Contains(temp)) return temp; temp = temp.next; } return null; } }
Python 語(yǔ)言
執(zhí)行結(jié)果:通過(guò)
執(zhí)行用時(shí):200 ms, 在所有 Python3 提交中擊敗了 40.19% 的用戶(hù)
內(nèi)存消耗:29.4 MB, 在所有 Python3 提交中擊敗了 5.00% 的用戶(hù)
# Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution : def getIntersectionNode (self, headA: ListNode, headB: ListNode) -> None : h = set() temp = headA while temp is not None : h.add(temp) temp = temp.next temp = headB while temp is not None : if temp in h: return temp temp = temp.next return None
題目3:環(huán)形鏈表
給定一個(gè)鏈表,判斷鏈表中是否有環(huán)。
為了表示給定鏈表中的環(huán),我們使用整數(shù)pos
來(lái)表示鏈表尾連接到鏈表中的位置(索引從 0 開(kāi)始)。如果pos
是 -1,則在該鏈表中沒(méi)有環(huán)。
示例 1 :
輸入:head = [3 ,2 ,0 ,-4 ], pos = 1 輸出:true 解釋?zhuān)烘湵碇杏幸粋€(gè)環(huán),其尾部連接到第二個(gè)節(jié)點(diǎn)。
示例 2 :
輸入:head = [1 ,2 ], pos = 0 輸出:true 解釋?zhuān)烘湵碇杏幸粋€(gè)環(huán),其尾部連接到第一個(gè)節(jié)點(diǎn)。
示例 3 :
輸入:head = [1 ], pos = -1 輸出:false 解釋?zhuān)烘湵碇袥](méi)有環(huán)。
進(jìn)階 :
你能用 O(1)(即,常量)內(nèi)存解決此問(wèn)題嗎?
思路:通過(guò)集合的方法
通過(guò)檢查一個(gè)結(jié)點(diǎn)此前是否被訪問(wèn)過(guò)來(lái)判斷鏈表是否為環(huán)形鏈表。
C# 語(yǔ)言
狀態(tài):通過(guò)
執(zhí)行用時(shí):112 ms, 在所有 C# 提交中擊敗了 84.04% 的用戶(hù)
內(nèi)存消耗:26.5 MB, 在所有 C# 提交中擊敗了 100.00% 的用戶(hù)
/** * Definition for singly-linked list. * public class ListNode { * public int val; * public ListNode next; * public ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public bool HasCycle (ListNode head) { HashSet<ListNode> h = new HashSet<ListNode>(); ListNode temp = head; while (temp != null) { if (h.Contains(temp)) return true ; h.Add(temp); temp = temp.next; } return false ; } }
Python 語(yǔ)言
執(zhí)行結(jié)果:通過(guò)
執(zhí)行用時(shí):60 ms, 在所有 Python3 提交中擊敗了 64.49% 的用戶(hù)
內(nèi)存消耗:17.3 MB, 在所有 Python3 提交中擊敗了 9.52% 的用戶(hù)
# Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution : def hasCycle (self, head: ListNode) -> bool: h = set() temp = head while temp is not None : if temp in h: return True h.add(temp) temp = temp.next return False
題目4:環(huán)形鏈表 II
給定一個(gè)鏈表,返回鏈表開(kāi)始入環(huán)的第一個(gè)節(jié)點(diǎn)。如果鏈表無(wú)環(huán),則返回 null。
為了表示給定鏈表中的環(huán),我們使用整數(shù) pos 來(lái)表示鏈表尾連接到鏈表中的位置(索引從 0 開(kāi)始)。如果 pos 是 -1,則在該鏈表中沒(méi)有環(huán)。
說(shuō)明 :不允許修改給定的鏈表。
示例 1:
輸入:head = [3 ,2 ,0 ,-4 ], pos = 1 輸出:tail connects to node index 1 解釋?zhuān)烘湵碇杏幸粋€(gè)環(huán),其尾部連接到第二個(gè)節(jié)點(diǎn)。
示例 2 :
輸入:head = [1 ,2 ], pos = 0 輸出:tail connects to node index 0 解釋?zhuān)烘湵碇杏幸粋€(gè)環(huán),其尾部連接到第一個(gè)節(jié)點(diǎn)。
示例 3 :
輸入:head = [1 ], pos = -1 輸出:no cycle 解釋?zhuān)烘湵碇袥](méi)有環(huán)。
進(jìn)階:
你是否可以不用額外空間解決此題?
思路:通過(guò)集合的方法
C# 語(yǔ)言
狀態(tài):通過(guò)
16 / 16 個(gè)通過(guò)測(cè)試用例
執(zhí)行用時(shí): 140 ms, 在所有 C# 提交中擊敗了 82.93% 的用戶(hù)
內(nèi)存消耗: 26 MB, 在所有 C# 提交中擊敗了 5.00% 的用戶(hù)
/** * Definition for singly-linked list. * public class ListNode { * public int val; * public ListNode next; * public ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public ListNode DetectCycle (ListNode head) { HashSet<ListNode> h = new HashSet<ListNode>(); ListNode temp = head; while (temp != null) { if (h.Contains(temp)) return temp; h.Add(temp); temp = temp.next; } return null; } }
Python 語(yǔ)言
執(zhí)行結(jié)果:通過(guò)
執(zhí)行用時(shí):72 ms, 在所有 Python3 提交中擊敗了 36.52% 的用戶(hù)
內(nèi)存消耗:17.2 MB, 在所有 Python3 提交中擊敗了 7.69% 的用戶(hù)
# Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution : def detectCycle (self, head: ListNode) -> ListNode: h = set() temp = head while temp is not None : if temp in h: return temp h.add(temp) temp = temp.next return None
題目5:快樂(lè)數(shù)
編寫(xiě)一個(gè)算法來(lái)判斷一個(gè)數(shù)是不是“快樂(lè)數(shù)”。
一個(gè)“快樂(lè)數(shù)”定義為:對(duì)于一個(gè)正整數(shù),每一次將該數(shù)替換為它每個(gè)位置上的數(shù)字的平方和,然后重復(fù)這個(gè)過(guò)程直到這個(gè)數(shù)變?yōu)?1,也可能是無(wú)限循環(huán)但始終變不到 1。如果可以變?yōu)?1,那么這個(gè)數(shù)就是快樂(lè)數(shù)。
示例:
輸入: 19 輸出: true 解釋: 1 ^2 + 9 ^2 = 82 8 ^2 + 2 ^2 = 68 6 ^2 + 8 ^2 = 100 1 ^2 + 0 ^2 + 0 ^2 = 1 輸入:7 輸出:true 輸入: 20 輸出: false 解釋?zhuān)?br>20 => 4 + 0 4 => 16 16 => 1 + 36 37 => 9 + 49 58 => 25 + 64 89 => 64 + 81 145 => 1 + 16 + 25 42 => 16 + 4 20 可以看到, 20 再次重復(fù)出現(xiàn)了, 所以永遠(yuǎn)不可能等于1
思路:通過(guò)集合的方法
C# 語(yǔ)言
執(zhí)行結(jié)果:通過(guò)
執(zhí)行用時(shí):48 ms, 在所有 C# 提交中擊敗了 80.74% 的用戶(hù)
內(nèi)存消耗:17 MB, 在所有 C# 提交中擊敗了 100.00% 的用戶(hù)
public class Solution { public bool IsHappy (int n) { HashSet<int > h = new HashSet<int >(); int m = 0 ; while (true ) { while (n != 0 ) { m += (int )Math.Pow(n % 10 ,2 ); n /= 10 ; } if (m == 1 ) { return true ; } if (h.Contains(m)) { return false ; } h.Add(m); n = m; m = 0 ; } } }
Python 語(yǔ)言
執(zhí)行結(jié)果:通過(guò)
執(zhí)行用時(shí):40 ms, 在所有 Python3 提交中擊敗了 79.79% 的用戶(hù)
內(nèi)存消耗:13.8 MB, 在所有 Python3 提交中擊敗了 9.09% 的用戶(hù)
class Solution : def isHappy (self, n: int) -> bool: h = set() m = 0 while True : while n != 0 : m += (n % 10 ) ** 2 n //= 10 if m == 1 : return True if m in h: return False h.add(m) n = m m = 0
題目6:只出現(xiàn)一次的數(shù)字
給定一個(gè)非空整數(shù)數(shù)組,除了某個(gè)元素只出現(xiàn)一次以外,其余每個(gè)元素均出現(xiàn)兩次。找出那個(gè)只出現(xiàn)了一次的元素。
說(shuō)明 :
你的算法應(yīng)該具有線性時(shí)間復(fù)雜度。你可以不使用額外空間來(lái)實(shí)現(xiàn)嗎?
示例 1 :
輸入: [2 ,2 ,1 ] 輸出: 1
示例 2 :
輸入: [4 ,1 ,2 ,1 ,2 ] 輸出: 4
思路:通過(guò)集合的方法
C# 語(yǔ)言
狀態(tài):通過(guò)
16 / 16 個(gè)通過(guò)測(cè)試用例
執(zhí)行用時(shí): 136 ms, 在所有 C# 提交中擊敗了 98.86% 的用戶(hù)
內(nèi)存消耗: 26.4 MB, 在所有 C# 提交中擊敗了 5.34% 的用戶(hù)
public class Solution { public int SingleNumber (int [] nums) { HashSet<int > h = new HashSet<int >(); for (int i = 0 ; i < nums.Length; i++) { if (h.Contains(nums[i])) { h.Remove(nums[i]); } else { h.Add(nums[i]); } } return h.ElementAt(0 ); } }
Python 語(yǔ)言
執(zhí)行結(jié)果:通過(guò)
執(zhí)行用時(shí):60 ms, 在所有 Python3 提交中擊敗了 55.88% 的用戶(hù)
內(nèi)存消耗:15.6 MB, 在所有 Python3 提交中擊敗了 5.26% 的用戶(hù)
class Solution : def singleNumber (self, nums: List[int]) -> int: h = set() for num in nums: if num in h: h.remove(num) else : h.add(num) return list(h)[0 ]
總結(jié) 本篇圖文首先介紹了 C# 與 Python 中的集合。C# 的集合類(lèi)型是 HashSet
,Python 的集合類(lèi)型是 set
,另外還提供了不可變集合類(lèi)型frozenset
。其次通過(guò)六道Leetcode題目介紹了集合這種類(lèi)型的應(yīng)用。
通過(guò)對(duì)這塊知識(shí)的總結(jié),不得不說(shuō) Python 提供了豐富的操作來(lái)簡(jiǎn)化開(kāi)發(fā)人員的工作,怪不得大家都說(shuō)“人生苦短,我用Python!”今天就到這里吧。Flag完成率80%,See You!
當(dāng)前活動(dòng)
我是 終身學(xué)習(xí)者“老馬 ”,一個(gè)長(zhǎng)期踐行“結(jié)伴式學(xué)習(xí)”理念的 中年大叔 。
我崇尚分享,渴望成長(zhǎng),于2010年創(chuàng)立了“LSGO軟件技術(shù)團(tuán)隊(duì) ”,并加入了國(guó)內(nèi)著名的開(kāi)源組織“Datawhale ”,也是“Dre@mtech ”、“智能機(jī)器人研究中心 ”和“大數(shù)據(jù)與哲學(xué)社會(huì)科學(xué)實(shí)驗(yàn)室 ”的一員。
愿我們一起學(xué)習(xí),一起進(jìn)步,相互陪伴,共同成長(zhǎng)。