Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.
Note:
For example, given array S = {-1 0 1 2 -1 -4}, A solution set is: (-1, 0, 1) (-1, -1, 2)
16 /// <summary> 17 /// Find Triplet that S[a] + S[b] + S[c] = 0 18 /// </summary> 19 /// <param name="S">input array</param> 20 /// <returns>HasSet of Triplet which ensure no duplicate</returns> 21 public static HashSet<List<int>> threeSum(int[] S) 22 { 23 HashSet<Triplet> ret = new HashSet<Triplet>(); 24 if(S.Length < 3) //if array length less than 3, return a empty HastSet<Triplet> 25 return ret; 26 27 Array.Sort(S); 28 29 for (int a = 0; a <= S.Length - 3; a++) 30 { 31 int b = a + 1; 32 int c = S.Length - 1; 33 while (b < c) 34 { 35 if (S[a] + S[b] + S[c] == 0) 36 { 37 List<int> triplet = new List<int> { S[a], S[b], S[c] }; 38 ret.Add(triplet); 39 b++; 40 c--; 41 } 42 else if (S[a] + S[b] + S[c] > 0) 43 c--; 44 else 45 b++; 46 } 47 } 48 49 return ret; 50 }
代码分析:
代码的算法应该算比较简单,我总结这种题目为指针题(不知道这种说法正不正确,请各位指正)。
我设了3个指针(当然你这里不是真正意义上的指针,只是3个在数组上游走的index): a, b 和 c。a 从 0 到S.Length - 3 循环, b和c分别从a+1, S.Length -1 往中间走,碰到 S[a] + S[b] + S[c] == 0 插入到HashSet中, 如果 S[a] + S[b] + S[c] > 0, c--,S[a] + S[b] + S[c] < 0, b++, 知道b >= c 的时候跳出内循环。
因为题目要求triplet不能有重复,所有我一开始想到用HashSet这个.NET提供的集合(Collectino)来帮助消除重复的元素。因为HashSet在每次做Add()插入的时候都会去检查一下集合里面有没有相同的元素,如果有,则不执行插入,如果没有,则执行插入。
问题来了,就算我使用了HashSet,我的ret里面还是有重复的triplet组合。经过一番研究,知道原来HashSet在比较里面是否存在元素的时候,会调用元素的GetHashCode(), 而且因为每次插入我们都创建一个新的List<int>实例,所以就算List<int>里面的组合是相同,GetHashCode()方法返回的值都是不同的(因为是不同的实例)。所以,我们需要自己建一个类,继承List<int>这个集合,然后overrideGetHashCode()方法,以及Equal(object obj)方法,因为当GetHashCode()返回值相同以后,HashSet会再去调用Equal(object obj)方法来确认这两个元素确实相同(Hash Function不能避免有collision吧)。而最原始的System.Object 类的Equal()方法实现是看这两个实例是否指向同一个地址,所以我们也必须重写他。(两个不同的实例永远不会指向同一地址吧,我也不知道微软有没有重写List<T>的Equal()方法,希望大牛能指点一下)
所以一下代码我创建了一个继承List<int>的Triplet类,然后重写了以上两个方法。
1 public class Triplet : List<int> 2 { 3 public override int GetHashCode() 4 { 5 return (this[0].ToString() + this[1].ToString() + this[2].ToString()).GetHashCode(); 6 } 7 public override bool Equals(object obj) 8 { 9 var other = obj as Triplet; 10 if(other == null) 11 return false; 12 return this[0] == other[0] && this[1] == other[1] && this[2] == other[2]; 13 } 14 } 15 16 /// <summary> 17 /// Find Triplet that S[a] + S[b] + S[c] = 0 18 /// </summary> 19 /// <param name="S">input array</param> 20 /// <returns>HasSet of Triplet which ensure no duplicate</returns> 21 public static HashSet<Triplet> threeSum(int[] S) 22 { 23 HashSet<Triplet> ret = new HashSet<Triplet>(); 24 if(S.Length < 3) //if array length less than 3, return a empty HastSet<Triplet> 25 return ret; 26 27 Array.Sort(S); //Sort the array 28 29 for (int a = 0; a <= S.Length - 3; a++) 30 { 31 int b = a + 1; 32 int c = S.Length - 1; 33 while (b < c) 34 { 35 if (S[a] + S[b] + S[c] == 0) 36 { 37 Triplet triplet = new Triplet { S[a], S[b], S[c] }; 38 ret.Add(triplet); 39 b++; 40 c--; 41 } 42 else if (S[a] + S[b] + S[c] > 0) 43 c--; 44 else 45 b++; 46 } 47 } 48 49 return ret; 50 }
这样就实现没有duplicates这个要求了。
当然了,我们可以不用HashSet这个集合也可以做到没有重复。
1 public static List<List<int>> threeSumNoSet(int[] S) 2 { 3 List<List<int>> ret = new List<List<int>>(); 4 if(S.Length < 3) 5 return ret; 6 Array.Sort(S); 7 string str; 8 Dictionary<string, bool> map = new Dictionary<string, bool>(); 9 10 for (int a = 0; a <= S.Length - 3; a++) 11 { 12 int b = a + 1; 13 int c = S.Length - 1; 14 while (b < c) 15 { 16 if (S[a] + S[b] + S[c] == 0) 17 { 18 str = S[a].ToString() + S[b].ToString() + S[c].ToString(); 19 if (map.ContainsKey(str)) 20 { 21 b++; 22 c--; 23 } 24 else 25 { 26 List<int> triplet = new List<int> { S[a], S[b], S[c] }; 27 map.Add(str, true); 28 ret.Add(triplet); 29 b++; 30 c--; 31 } 32 } 33 else if (S[a] + S[b] + S[c] > 0) 34 c--; 35 else 36 b++; 37 } 38 } 39 40 return ret; 41 }
代码分析:
在上面的代码,我们使用的Dictionary<key, value>这个泛型集合帮组我们检查重复,而且建了一个string = S[a].ToString() + S[b].ToString() + S[c].ToString() 作为Dictionary的key。因为我们的数组 S 已经是排列过的(Sorted),所以不肯定不会出现{-1,0,1}, {-1,1,0}这种应视为重复的情况。
这题就做到这里了,在做题过程中,学习很多自己以前没有留意到的东西,比如如果泛型HashSet<>里面是个reference type, 它会如何比较元素是否相等。