C# 有關List<T>的Contains與Equals方法

PaperHammer發表於2022-05-01

【以下內容僅為本人在學習中的所感所想,本人水平有限目前尚處學習階段,如有錯誤及不妥之處還請各位大佬指正,請諒解,謝謝!】                                                                                                  

 

!!!觀前提醒!!!

【本文內容可能較為複雜,雖然我已經以較為清晰的方式展現我的思想,但可能依舊容易引起思維混亂,若感到混亂或不舒服的情況,可直接轉跳至文末的總結處;也可以先看完結論再來閱讀文章】

 

引言

List作為一種動態儲存結構,可以代替陣列,還可以將其當作連結串列使用。本文將介紹C#中List的相關內容,重點介紹其包含的Contains與Equals方法,並針對集合的比較與去重進行分析,提供可行的解決方案。

起因

題目連結:6049. 含最多 K 個可整除元素的子陣列 - 力扣(LeetCode) (leetcode-cn.com)

 

實際上就是在一個陣列的所有非空“子陣列”中,統計其中滿足要求的子陣列的數量。

經過與結果

想法:列舉每一個子陣列,判斷其是否符合要求;若符合則判斷該子陣列是否應經被統計過;未統計則加入,已統計則跳過。

結果:(我真的是匪夷所思啊!!!)

 

【注:將HashSet換為List執行結果相同】

當換用字串為元素後,結果卻沒有問題且AC通過

 

第一部分 關於List

【注:此部分屬於介紹內容,非本文重點,可轉跳至第二部分

在分析上面問題之前,我們先對List有一個認知。List是一個C#中最常見的可伸縮陣列元件,即動態資料儲存結構。我們常常用它來替代陣列,並且因為它的長度是動態的,所以我們在寫的時候不用手動去分配的大小;甚至有時我們也會拿它當連結串列使用。

屬性

(一)Count

定義:獲取 List<T>中包含的元素數。

(二)Capacity

(1)定義:獲取或設定該內部資料結構在不調整大小的情況下能夠容納的元素總數。

(2)可能觸發的異常:

ArgumentOutOfRangeException:Capacity 被設定為一個小於現有長度的值。

OutOfMemoryException:系統上沒有足夠的可用記憶體。

小結

(1)Capacity是可能需要調整大小之前儲存的元素數目,以4為基數,每次遞增4;Count是實際位於其中List<T>元素的數目。

(2)Capacity始終大於等於Count。如果在新增元素時Count超過Capacity,則會在新增元素前自動重新分配Capacity的值;在清空List<T>或刪除元素後Capacity的值不會變化。

(3)可通過TrimExcess()方法刪除多餘的預留空間,即使Capacity的值等於Count的值。

(3)獲取此屬性的值的運算時間複雜度為 O(1)。

(三)Item[index]

(1)定義:獲取或設定指定索引處的元素。

(2)可能觸發的異常:

ArgumentOutOfRangeException:index小於0或大於等於 Count。

常用方法

(一)新增

(1)Add(T)   將物件新增到末尾

(2)AddRange(List<T>)   將集合新增到末尾【注:List<T>非空】

(3)Insert(int index, T)   將元素插入指定索引處

(4)InsertRange(int index, List<T>)   將集合插入指定索引處【注:List<T>非空】

(二)刪除

(1)Remove(T)    移除指定物件的第一個匹配項

(2)RemoveAll(Predicate<T> match)     移除與match相匹配的所有元素

(3)RemoveAt(int num)   移除指定s索引處的元素

(4)RemoveRange(int index, int count)     移除指定範圍內的元素

(三)查詢

(1)Find(Predicate<T> match)   搜尋並返回第一個與match相匹配的元素

(2)FindIndex(Predicate<T> match) 返回第一個與match相匹配的元素的索引值

(3)FindIndex(int startIndex, Predicate<T> match)   從startIndex開始搜尋,返回第一個與match相匹配的元素的索引值

(4)FindIndex(int startIndex, int count, Predicate<T> match) 從startIndex開始搜尋count位,返回第一個與match相匹配的元素的索引值

【注:FindLast、FindLastIndex從後往前搜尋,其餘部分相同】

(5)IndexOf(T)   搜尋指定物件,返回第一個匹配項從零開始的索引

(6)Find(T, int index)  在[index, count)範圍內搜尋指定物件,返回第一個匹配項從零開始的索引

(7)Find(T, int index, int count) 在[index, index + count]範圍內搜尋指定物件,返回第一個匹配項從零開始的索引

(四)排序

(1)Sort()   使用預設比較器(升序)對List進行排序

(2)Sort(IComparer<T>)  使用指定比較器對List進行排序

(3)Sort(int index, int count, IComparer<T>) 在指定範圍內使用指定比較器(不寫,則為預設比較器)對List進行排序

 

第二部分 關於List之間的包含與比較

(一)包含——Contains

注:元素之間的比較較為簡單,在此不做敘述

C#中變數可分為值型別和引用型別。值型別儲存在棧中,引用型別儲存在堆中,引用型別的記憶體地址儲存在棧中。其中:泛型List的Contains在比較值型別時,直接比較值,但在比較引用型別時,比較的是引用地址

雖然明白了Contains的比較原理,但這時候又出現了一個問題:List<T>是引用型別,string也是引用型別,那為什麼在判斷string是否相同時可以達到預期效果而判斷List<T>時不能達到預期效果呢?針對這個問題,我們直接檢視一下這兩個型別所宣告的變數的記憶體地址。

(1)   泛型型別為List<T>此時,內部儲存的元素為集合

 

可以發現:

 a. 兩個字串值相同,則其記憶體地址相同

    b.  兩個List<T>值相同,但其記憶體地址不同

    c. 當泛型型別為集合時,即使兩個集合中儲存的元素值相同且元素記憶體地址相同,但這兩個集合本身的記憶體地址不同,即list1的地址不同於list2。而根據Contains的查詢規律,無法將這兩個集合視為同一個集合。

所以我們所認為的集合相同是內部元素相同,而C#所認為的集合相同是記憶體地址的相同。

2)泛型型別為string此時,內部儲存的元素為字串

內部元素不同:

 

內部元素相同:

 

可以發現:

 a. 兩個字串值相同,則其記憶體地址相同

 b. 當泛型型別為字串時,只有兩個字串的記憶體地址相同,即str1的地址等同於str2,所以可將這兩個元素視為同一個元素。

(3)泛型型別為自定義型別此時,內部儲存的元素為自定義物件

 

通過上面這個例子可以發現:

 a. 兩個自定義物件值相同,但其記憶體地址不同

 b. 物件再傳遞時,傳遞的是記憶體地址

 c. 當泛型型別為自定義物件型別時,只有兩個物件的記憶體地址相同時,才判定這兩個物件是同一個物件。

小結

至此,我們得出List. Contains()方法的執行原理:

 a. 在查詢是否包含時,查詢的是針對定義的泛型T,所儲存的物件,與物件中再儲存的元素無關。

 b. 在查詢是否包含值型別時,直接比較值;查詢引用型別時,先比較記憶體地址,若記憶體地址相同則返回True;否則再比較值。

 

(二)比較——Equals

1)泛型型別為List<T>此時,內部儲存的元素為集合

 

 

2)泛型型別為string此時,內部儲存的元素為字串

內部元素不同:

 

內部元素相同:

 

直接使集合本身相同:

 

(3)泛型型別為自定義型別此時,內部儲存的元素為自定義物件

 

 

 

小結

 a. 通過以上兩個例子,我們可以總結出List. Equals()方法的執行原理:在比較值型別時,直接比較值;在比較引用型別時,只比較記憶體地址。

回到開頭的題目

為什麼第一種方法是錯的,因為執行Contains操作的物件是是兩個集合,雖然兩個集合內部儲存元素相同,但兩個集合本身的記憶體地址是不同的,所以總會被判定為是不同的元素,因而不能用於判重;

第二種方法使用字串,操作物件即為字串,相同的字串值,記憶體地址相同,所以可以用來判重。

總結

 a. 不論是Contains方法還是Equals方法,首先要明確操作的物件,即要查詢或比較的是哪兩個物件。

 b. 確定物件後,判斷其記憶體地址是否相同,相同返回True,不同返回False。

 c. 一般地,引用型別傳遞時,傳遞的是記憶體地址,且相同值的引用型別記憶體地址不同特別地,當字串值相同時,記憶體地址也相同。

【感謝您可以抽出時間閱讀到這裡,第一篇部落格可能會有許多不妥之處;受限於水平,許多地方可能存在錯誤,還請各位大佬留言指正,請見諒,謝謝!】

 

 #附本題程式碼

模擬 + 雜湊去重

public class Solution {
    public int CountDistinct(int[] nums, int k, int p) {
        int res = 0;
        HashSet<string> set = new HashSet<string>();
        for (int i = 0; i < nums.Length; i++) {
            int cnt = 0;
            StringBuilder builder = new StringBuilder();
            for (int j = i; j >= 0; j--) {
                builder.Append(nums[j] + " ");
                if (nums[j] % p == 0) cnt++;
                if (cnt <= k && !set.Contains(builder.ToString())) {
                    set.Add(builder.ToString());
                    res++;
                }
            }
        }
        return res;
    }
}

相關文章