Leetcode解題之3Sum

寫在前面

身爲一個程序猿,工作了兩年才第一次寫所謂的技術博客,可以說是很不稱職的,不過在這兩年的提升過程中遇見了大大小小的問題,有的簡單卻容易出錯,有的看似複雜卻妙趣橫生。於是,出於治療我懶癌的原因,我選擇堅持寫寫博客,把我遇到的問題與大家分享,當然還有那微不足道的經驗。好了,廢話說的差不多了(畢竟我不是語文專業的,措辭肯定讓人味同嚼蠟),下面就開始我的第一次編程之旅吧。

leetcode代碼題之3Sum

先說說這個題的序號,應該是15,衆所周知,leetcode是按照題目更新順序排列序號的,說明這個題是建站初期就有的題,那時候的人還是真不留情面,若是這個題是現在的面試題,並且要求不能測試並一次性A過的話,我想,除了那些已經把代碼當成交流語言的人,“正常人”都可以直接換行業了,並且到現在爲止也只有不到百分之20的通過率,此題難度可見一斑啊。那麼下面我們就先說說這個題的要求吧。

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.

用中國話的意思就行,給一個包含n個整數的數組S,在S數組中可能存在3個元素a,b,c將滿足a+b+c =0這個條件,找出符合這個條件的三個整數的不同組合情況。

還有一個小提示在下面**The solution set must not contain duplicate triplets.**意思是解集中不存在相同的3元素數組,什麼意思呢就是咱們{1,-1,0}和{-1,1,0}去領禮品,{1,-1,0}領完了,換了個馬甲{-1,1,0}又去領了,服務員急道“小樣你換個馬甲我就不認識你了?”,當然這種情況都不行,那麼兩個{1,-1,0}去領兩次獎品的話,服務員就該動手了。

好了,既然我們知道要求了,就得知道給了我們什麼東西,我們要做成什麼東西,一個數組nums是leet給我們的條件,那麼List<List<Integer>>則是我們的要求了。有了這些東西,OK,我們來動手吧,小編我可不是耍嘴皮子的人。第一步,我們要取3個元素,那麼數組至少要大於等於3個元素纔有可能一種情況

if(nums.length<3) return list

然後,我們知道他是整數數組,這時候腦子中應該反應出這麼幾種方法,排序,二分法,計數統計及set存儲,首先我們看到最簡單的方法(最簡單當然不是效率最高,往往別人給你封裝好的方法包含了一系列的處理方式,導致了空間及時間上都並不是那麼速度)set方法,set存儲獨特子集,相同的元素無法再次存儲,可以解決我們的換馬甲問題,當然存進去之前我們要對我們的數組進行排序,存入順序肯定是由小到大,畢竟set也區分不開{-1,1,0}和{1,-1,0};最後將set轉換成list;有兩種轉換方法addAll()與直接構造生成。我用了這個方法以後,當然只有一個結論time limit,先不說我的運算流程,光是這轉換來轉換去,存之前還要排序,就夠我們喝一壺的。那麼我就要想到能爲我提供便利的方式了,不需要來回跑可以直接解決問題。

bingo,技術統計,對,就是它。我們大部分人都知道計數統計是記錄數字出現的次數及種類,計數數組的容量爲數組最大值-最小值+1;於是第二個問題出現了,求最小值,排序問題,我就不說了(小編爲了省事直接Arrays.sort了有興趣的可以試試for取最小值看看哪個快)在取最小值之後,我們應該想到,我們要3個數相加等於0,那麼就有以下幾種特殊情況最小值等於0,最小值大於0,最大值等於0,最大值小於0,第二,四種情況我們直接進行判斷就可以了,這兩種情況都爲空,所以直接返回定義的空List就可以。而一,三種情況還需要具體判斷,那麼就先統計出來吧。

Arrays.sort(nums);
if(nums[0]>0||nums[nums.length-1]<0) return list;
int[] count = new int[nums[nums.length-1]-nums[0]];
for(n:nums){count[n-nums[0]++]};

存儲好了計數數組,就用到我們剛纔的條件了,此時的計數數組相當於先把原來的數組右移|最小值|的距離,再進行統計,那麼由此可知0就向右平移了|最小值|的距離,最小值剛纔已經判斷了必定小於等於0,那麼|最小值|也就等於
int zero = -nums[0];
知道了0存儲的位置,就回想剛纔一,三的情況於是乎兩個判斷條件
if((nums[0]==0||nums[length-1]==0)&&count[zero]<3) return list;
if(count[zero]>3){List<Integer> l = new ArrayList<Integer>();
l.add(0);
l.add(0);
l.add(0);
list.add(l)};

我的天,添加一種情況這麼費事,擺明了是不想好好過了,那好吧,既然破罐子破摔,誰怕誰。List<Integer>說白了就是一個int數組,就像兩個美女(先不討論臉,如果看臉的話,出門請右拐),int數組苗條性感,放什麼東西也整整齊齊;list有點胖啊,是你會的多,找東西也快,可是我就放個東西就夠了沒打算拿,還是要個輕的吧,公主抱也不至於斷手不是。那麼我們改改好了
list.add(new int[]{0,0,0})
這麼一看舒服多了。特殊情況處理的差不多了,再來看看一般情況,我們知道如果3個數相加等於0,除去3個都是0的特殊情況,至少有一個正數一個負數,所以就需要兩個遊標,需要至少二層循環去控制,判斷條件爲遊標所在位置的內容不爲空也就是count[遊標]!=0;爲了方便我將遊標控制和移動寫在單獨的方法裏
public int goright(int x, int[] count,int zero){
 x++;
while(x<zero&&count[x]==0){
 x++; 
} 
return x; 
} 
public int goleft(int y, int[] count, int zero){ 
y--;
while(y>zero&&count[y]==0){ 
y--; 
} 
return y; 
}
goright方法是遊標向右移動,所以爲加,一開始用自加方法,防止死循環,判斷條件,x向右移動但是必須保證小於0,也就是小於zero,保證自己是負數,並且當count[x]不等於0的時候就將x返回。同理,y就不說了。

最後遊標都設置好了就該想想三個數相加怎麼得到的0,假設一個數爲z,z也在count數組上,也就相當於z也右移了-zero的距離,所以原來的數爲z-zero,同理x代表x-zero,y代表y-zero;根據三數相加爲0,可知(z-zero)+(x-zero)+(y-zero)=0,合併同類項最後得z = 3*zero -x -y;那麼遊標的位置我們就求出來了,不過此時還沒有完結,畢竟不都是{-1,0,1}這種情況,還有{-1,-1,2}這種情況,當兩數相同的時候,還需要判斷count[遊標]是否大於1;另外因爲x與y都是從左右兩端遍歷過來的,所以z小於x或者大於y的情況必定在遍歷過程中已經被存儲,所以,只需要判斷z大於x且z小於y的情況就能保證數組的唯一性。代碼如下

for(int x=0;x<zero;x=goright(x,count,zero){ 
for(int y=count.length-1;y>zero;y=goleft(y,count,zero){ 
int z = 3*zero-x-y; 
if(z==x&&count[z]>1||z==y&&count[z]>1||z>x&&z<y&&count[z]>0) list.add(new int[]{z-zero,x-zero,y-zero});
} 
}

綜上這個題算是竣工了,簡簡單單一個題,想起來卻錯綜複雜,每一處都是驚險萬分,下面附上完整代碼
public class Solution {
	public List<List<Integer>> threeSum(int[] nums) {
	    List list = new ArrayList<>();
		int len = nums.length;
		if (len <= 2)
			return list;
		Arrays.sort(nums);
		if (nums[0] > 0 || nums[len - 1] < 0)
			return list;
		int[] count = new int[nums[len - 1] - nums[0] + 1];
		for (int i = 0; i < nums.length; i++) {
			count[nums[i] - nums[0]]++;
		}
		int zero = -nums[0];
		if (count[zero] >= 3)
			list.add(new int[] { 0, 0, 0 });
		for (int x = 0; x < zero; x = goright(x, count, zero)) {
			for (int y = count.length - 1; y > zero; y = goleft(y, count, zero)) {
				int z = 3 * zero - x - y;
				if(z==x&&count[z]>1||z==y&&count[z]>1||z>x&&z<y&&count[z]>0){
					list.add(new int[]{z-zero,x-zero,y-zero});
				}
			}
		}
		return list;
	}

	public int goright(int x, int[] count, int zero) {
		x++;
		while (count[x] == 0&&x<zero) {
			x++;
		}
		return x;
	}

	public int goleft(int y, int[] count, int zero) {
		y--;
		while (count[y] == 0&&y>zero) {
			y--;
		}
		return y;
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章