算法 | 一週刷完《劍指Offer》 Day4:第38~49題

寫在前面

上一篇:算法 | 一週刷完《劍指Offer》 Day3:第27~37題
下一篇:算法 | 一週刷完《劍指Offer》 Day5:第50~58題


Day4:第38~49題

有幾道題比較考邏輯和理解。到後面就簡單了,好像更注重思維了。

  • T38. 二叉樹的深度
  • T39. 平衡二叉樹
  • T40. 數組中只出現一次的數字
  • T41. 和爲S的連續正數序列
  • T42. 和爲S的兩個數字
  • T43. 左旋轉字符串
  • T44. 翻轉單詞順序列
  • T45. 二叉樹的深度
  • T46. 孩子們的遊戲(圓圈中最後剩下的數)
  • T47. 求1+2+3+…+n
  • T48. 不用加減乘除做加法
  • T49. 把字符串轉換成整數

T38. 二叉樹的深度

題目描述

輸入一棵二叉樹,求該樹的深度。從根結點到葉結點依次經過的結點(含根、葉結點)形成樹的一條路徑,最長路徑的長度爲樹的深度。

解題思路

分別對左右子樹遞歸計算深度,取深度更大的一個。

	public int TreeDepth(TreeNode root) {
        if(root == null) return 0;
        
        return 1//當前節點深度1
        		+ Math.max(TreeDepth(root.left), TreeDepth(root.right));//左右子樹取深度更大的值
    }

T39. 平衡二叉樹

題目描述

輸入一棵二叉樹,判斷該二叉樹是否是平衡二叉樹。

解題思路

平衡二叉樹:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。

此題即將上一題計算左右子樹深度【取更大】的思想轉化爲了計算左右子樹深度【判斷差值是否不超過1】。

	public boolean IsBalanced_Solution(TreeNode root) {
        if(root == null) return true;
        
        return getDepth(root) != -1;
    }
	
	private int getDepth(TreeNode root) {
		if(root == null) return 0;

		int left = getDepth(root.left);
        if(left == -1) return -1;
        
        int right = getDepth(root.right);
        if(right == -1) return -1;
        
        return Math.abs(left - right) > 1 ? -1 : 1 + Math.max(left, right);
	} 
    }

T40. 數組中只出現一次的數字

題目描述

一個整型數組裏除了兩個數字之外,其他的數字都出現了偶數次。請寫程序找出這兩個只出現一次的數字。

解題思路

重點:注意題意,兩個數只出現一次,其他數都出現偶數次

方法一:HashSet。不包含則加入,包含則移除。最終出現偶數次的數一定都會被移除,僅留只出現了一次的數。

	//num1,num2分別爲長度爲1的數組。傳出參數
	//將num1[0],num2[0]設置爲返回結果
	public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2) {//HashSet
		Set<Integer> set = new HashSet<>();
		
		for(int num: array) {
			if(set.contains(num)) {
				set.remove(num);
			} else {
				set.add(num);
			}
		}
		
		Iterator<Integer> it = set.iterator();
		num1[0] = it.next();
		num2[0] = it.next();
	}

方法二:異或運算。此方法比較巧妙,需仔細理解。

首先理解二進制位運算的幾個概念:
與運算( & ):運算規則:0&0=0; 0&1=0; 1&0=0; 1&1=1;
或運算( | ):0|0=0; 0|1=1; 1|0=1; 1|1=1;
異或運算( ^ ):運算規則:0^0=0; 0^1=1; 1^0=1; 1^1=0;

異或運算有此特性:n^n=0;即任意數與本身異或得0;

所以對array數組所有數進行異或運算,得到的結果爲兩個僅出現一次的數異或運算的結果different(其他數由於出現偶數次,異或運算後都爲0了),這個different是區分這兩個數不同的標誌

然後,或運算有此特性:n&-n取得n的二進制表示中最右邊的1。(-n的表示百度補碼)

對different進行與運算:different = different & (-different)。此時的different就成爲了那個標誌

最後再對array數組所有數進行一次異或運算,不同的是這一次要根據different將兩個數區分開,填入結果。

	//num1,num2分別爲長度爲1的數組。傳出參數
	//將num1[0],num2[0]設置爲返回結果
	public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2) {//位運算
	    int different = 0;
	    for(int num: array) {//最終得到兩個只出現一次的數相異的的結果
	    	different ^= num;
	    }
	    //得到最右邊的1
	    different &= - different;
	    for(int num: array) {
	    	//由於different是那兩個數像異的結果,那麼這個取得的最右邊的1,可將兩個只出現一次的數區分開
	    	//然後再進行一遍異運算即可
	        if((num & different) == 0) {
	        	num1[0] ^= num;
	        } else {
	        	num2[0] ^= num;
	        }
	    }
	}

方法三:暴力解,不多贅述了。

	//num1,num2分別爲長度爲1的數組。傳出參數
	//將num1[0],num2[0]設置爲返回結果
	public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2) {//暴力解法
		ArrayList<Integer> list = new ArrayList<Integer>();
        Arrays.sort(array);
        
        int length = array.length;
        
        for(int i = 0; i < length; i ++){
            if(i == length - 1 && array[i] != array[i - 1]) {
                list.add(array[i]);
            }else if(i == 0 && array[i] != array[i + 1]) {
                list.add(array[i]);
            }else{
                if(i != 0 && i != length - 1 && array[i] != array[i - 1] && array[i] != array[i + 1]) {
                    list.add(array[i]);
                }
            }
        }
        
        num1[0] = list.get(0);
        num2[0] = list.get(1);
    }

T41. 和爲S的連續正數序列

題目描述

小明很喜歡數學,有一天他在做數學作業時,要求計算出9~16的和,他馬上就寫出了正確答案是100。但是他並不滿足於此,他在想究竟有多少種連續的正數序列的和爲100(至少包括兩個數)。沒多久,他就得到另一組連續正數和爲100的序列:18,19,20,21,22。現在把問題交給你,你能不能也很快的找出所有和爲S的連續正數序列? Good Luck!

輸出描述:輸出所有和爲S的連續正數序列。序列內按照從小至大的順序,序列間按照開始數字從小到大的順序

解題思路

夾逼思想,定義正數序列的左邊界small和右邊界big,求small到big的和。和比所求小則big後移,比所求大則small後移。

	public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {//算法,夾逼
		ArrayList<ArrayList<Integer>> result = new ArrayList<>(); 
		if(sum <= 1) return result;
		
		int small = 1;
		int big = 2;
		
		while(small < (sum + 1) / 2) {//要求最少是兩個數字,所以small最大爲(s+1)/2
			int curSum = 0;//求small到big的和
			for(int i = small; i <= big; i ++) {
				curSum += i;
			}
			
			//夾逼思想,小則big後移,大則small後移
			if(curSum < sum) {
				big ++;
			} else if(curSum > sum) {
				small ++;
			} else {
				ArrayList<Integer> list = new ArrayList<>();
				for(int i = small; i <= big; i ++) {
					list.add(i);
				}
				result.add(list);
				small ++;
			}
		}
		
		return result;
    }

公式求解。

	public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {//公式
		ArrayList<ArrayList<Integer>> result = new ArrayList<>(); 
		if(sum < 3) return result;
		
		for(int i = 1; i <= sum / 2; i ++) {
			int value = 1 + 4 * i * i - 4 * i + 8 * sum;
			int valueSqrt = (int) Math.sqrt(value);
			if(value >= 25 && valueSqrt * valueSqrt == value) {
				ArrayList<Integer> list = new ArrayList<Integer>();
				for(int j = i; j <= (valueSqrt - 1) >> 1; j ++) {
					list.add(j);
				}
				result.add(list);
			}
		}
		return result;
    }

T42. 和爲S的兩個數字

題目描述

輸入一個遞增排序的數組和一個數字S,在數組中查找兩個數,使得他們的和正好是S,如果有多對數字的和等於S,輸出兩個數的乘積最小的。

輸出描述:對應每個測試案例,輸出兩個數,小的先輸出。

解題思路

夾逼思想,且和相等時,差越大乘積越小。因此從兩端向中間夾逼時第一個和爲S的即爲所求。

	public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) {
		//夾逼思想,且和相等時,差越大乘積越小
		ArrayList<Integer> result = new ArrayList<>();
		
		int small = 0;//數組下標,從前往後移
		int big = array.length - 1;//數組下標,從後往前移
        
		while(small <= big) {
			int curSum = array[small] + array[big];
			if(curSum < sum) {
				small ++;
			} else if(curSum > sum) {
				big --;
			} else {
				result.add(array[small]);
				result.add(array[big]);
				return result;
			}
		}
		
		return result;
    }

T43. 左旋轉字符串

題目描述

彙編語言中有一種移位指令叫做循環左移(ROL),現在有個簡單的任務,就是用字符串模擬這個指令的運算結果。對於一個給定的字符序列S,請你把其循環左移K位後的序列輸出。例如,字符序列S=”abcXYZdef”,要求輸出循環左移3位後的結果,即“XYZdefabc”。是不是很簡單?OK,搞定它!

解題思路

分三步走。(交換步驟順序也可)

step1:先將左邊3個字符串進行翻轉:[abc]XYZdef --> [cba]XYZdef

step2:再將右邊剩餘字符串進行翻轉:cba[XYZdef] --> cba[fedZYX]

step3:最後將整個字符串進行翻轉: cbafedZYX --> XYZdefabc

	public String LeftRotateString(String str, int n) {
		if(str == null || str.length() == 0) return "";
		
		char[] chars = str.toCharArray();
		//step1:先將左邊3個字符串進行翻轉:[abc]XYZdef --> [cba]XYZdef
		reverse(chars, 0, n - 1);
		//step2:再將右邊剩餘字符串進行翻轉:cba[XYZdef] --> cba[fedZYX]
		reverse(chars, n, chars.length - 1);
		//step3:最後將整個字符串進行翻轉: cbafedZYX --> XYZdefabc
		reverse(chars, 0, chars.length - 1);
		
		return new String(chars);
    }
	
	private void reverse(char[] chars, int start, int end) {
		while(start < end) {
	        char tmp = chars[start]; 
	        chars[start] = chars[end]; 
	        chars[end] = tmp;
	        
	        start ++; 
	        end --;
	    }
	}

T44. 翻轉單詞順序列

題目描述

牛客最近來了一個新員工Fish,每天早晨總是會拿着一本英文雜誌,寫些句子在本子上。同事Cat對Fish寫的內容頗感興趣,有一天他向Fish借來翻看,但卻讀不懂它的意思。例如,“student. a am I”。後來才意識到,這傢伙原來把句子單詞的順序翻轉了,正確的句子應該是“I am a student.”。Cat對一一的翻轉這些單詞順序可不在行,你能幫助他麼?

解題思路

原理同上一題。先翻轉每個單詞的順序,再翻轉整個句子的順序。(交換步驟順序也可)

	public String ReverseSentence(String str) {//原理同T43
		if(str == null || str.length() == 0) return str;
		
		char[] chars = str.toCharArray();
		int length = chars.length;
		int startIndex = 0;//單詞開始標記
		int endIndex = 0;//單詞結束標記
		
		//翻轉每個單詞的字母順序
		while(endIndex <= length) {
			if(endIndex == length || chars[endIndex] == ' ') {//遇到空格或到句末,翻轉單詞
				reversed(chars, startIndex, endIndex - 1);
				startIndex = endIndex + 1;
			}
			endIndex ++;		
		}
		
		//翻轉整個句子的順序
		reversed(chars, 0, length - 1);
        
        return new String(chars);
    }
	
	private void reversed(char[] chars, int start, int end) {
		while(start < end) {
	        char tmp = chars[start]; 
	        chars[start] = chars[end]; 
	        chars[end] = tmp;
	        
	        start ++; 
	        end --;
	    }
	}

T45. 二叉樹的深度

題目描述

LL今天心情特別好,因爲他去買了一副撲克牌,發現裏面居然有2個大王,2個小王(一副牌原本是54張_)…他隨機從中抽出了5張牌,想測測自己的手氣,看看能不能抽到順子,如果抽到的話,他決定去買體育彩票,嘿嘿!!“紅心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是順子…LL不高興了,他想了想,決定大\小 王可以看成任何數字,並且A看作1,J爲11,Q爲12,K爲13。上面的5張牌就可以變成“1,2,3,4,5”(大小王分別看作2和4),“So Lucky!”。LL決定去買體育彩票啦。 現在,要求你使用這幅牌模擬上面的過程,然後告訴我們LL的運氣如何, 如果牌能組成順子就輸出true,否則就輸出false。爲了方便起見,你可以認爲大小王是0。

解題思路

先對數組排序,計算大小王(癩子)數量。然後計算剩下的數兩兩的差值減1即爲需要用癩子替代的張數。題那麼長都是廢話,別想太多正常找就行。

	public boolean isContinuous(int[] numbers) {
		if(numbers == null || numbers.length < 5) return false;
		
		Arrays.sort(numbers);
		int sum0 = 0;//大小王數量
		for(int i = 0; i < numbers.length; i ++) {
			if(numbers[i] == 0) {
				sum0 ++;
			}
		}
		
		for(int i = sum0; i < numbers.length - 1; i ++) {
			if(numbers[i + 1] == numbers[i]) return false;//有相等的牌不可能爲順子
			
			int interval = numbers[i + 1] - numbers[i] - 1;//這兩個數之間差了幾張牌,需要用大小王代替
			if(interval > sum0) return false;//相差太大,大小王不夠
			
			sum0 -= interval;
		}
		
		return true;
    }

T46. 孩子們的遊戲(圓圈中最後剩下的數)

題目描述

每年六一兒童節,牛客都會準備一些小禮物去看望孤兒院的小朋友,今年亦是如此。HF作爲牛客的資深元老,自然也準備了一些小遊戲。其中,有個遊戲是這樣的:首先,讓小朋友們圍成一個大圈。然後,他隨機指定一個數m,讓編號爲0的小朋友開始報數。每次喊到m-1的那個小朋友要出列唱首歌,然後可以在禮品箱中任意的挑選禮物,並且不再回到圈中,從他的下一個小朋友開始,繼續0…m-1報數…這樣下去…直到剩下最後一個小朋友,可以不用表演,並且拿到牛客名貴的“名偵探柯南”典藏版(名額有限哦!!_)。請你試着想下,哪個小朋友會得到這份禮品呢?(注:小朋友的編號是從0到n-1)

解題思路

又是一堆廢話,丟手絹遊戲。。。
約瑟夫環,公式:
n = 1: f(n, m) = 0
n > 1: f(n, m) = [f(n - 1, m) + m] % n

	public int LastRemaining_Solution(int n, int m) {//約瑟夫環
		//公式:f(n, m) = 0                   (n = 1) 
		//f(n, m) = [f(n - 1, m) + m] % n     (n > 1)
		if(n == 0) return -1;
        if(n == 1) return 0;
	
        return (LastRemaining_Solution(n - 1, m) + m) % n;
	}

T47. 求1+2+3+…+n

題目描述

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。

解題思路

遞歸相加。此題關鍵在於如何跳出遞歸,基本方向是採用邏輯與或的方式來計算,與的時候通過n>0來短路,這樣在n=0的時候不需要計算遞歸的值,或的時候通過n==0來短路,在n=0的時候可以短路邏輯或運算。

	public int Sum_Solution(int n) {
        int sum = n;
        //boolean b = (n > 0) && (sum += Sum_Solution(n - 1)) > 0;
        boolean b = (n == 0) || (sum += Sum_Solution(n - 1)) > 0;
        
        return sum;
    }

T48. 不用加減乘除做加法

題目描述

寫一個函數,求兩個整數之和,要求在函數體內不得使用+、-、*、/四則運算符號。

解題思路

位運算。a ^ b 表示沒有考慮進位的情況下兩數的和,(a & b) << 1 就是進位。

	public int Add(int num1, int num2) {
		while(num2 != 0) {
	        int tmp = num1 ^ num2;
	        num2 = (num1 & num2) << 1;
	        num1 = tmp;
	    }
		
	    return num1;
    }

T49. 把字符串轉換成整數

題目描述

將一個字符串轉換成一個整數(實現Integer.valueOf(string)的功能,但是string不符合數字要求時返回0),要求不能使用字符串轉換整數的庫函數。 數值爲0或者字符串不是一個合法的數值則返回0。

輸入描述:
輸入一個字符串,包括數字字母符號,可以爲空
輸出描述:
如果是合法的數值表達則返回該數字,否則返回0

解題思路

正常轉換即可。

注意:正負號的存在,字符型代表的整形在轉換過程中的計算。

	public int StrToInt(String str) {
	    if(str.length() == 0) return 0;
		
	    char[] chars = str.toCharArray();
	    boolean isNegative = chars[0] == '-';//判斷是否有負號
	    
	    int result = 0;
	    
	    for(int i = 0; i < chars.length; i ++) {
	        if(i == 0 && (chars[i] == '+' || chars[i] == '-')) continue;//跳過正負號
	        
	        if(chars[i] < '0' || chars[i] > '9') return 0;
	        
	        result = result * 10 + (chars[i] - '0');
	    }
	    
	    return isNegative ? -result : result;//三元
    }

項目地址https://github.com/JohnnyJYWu/offer-Java

上一篇算法 | 一週刷完《劍指Offer》 Day3:第27~37題
下一篇:算法 | 一週刷完《劍指Offer》 Day5:第50~58題

希望這篇文章對你有幫助~

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