題目-求1+2+3+...+n java
求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。
思路node
因爲須要用到的就是一個遞歸,當n>0時,將後面的數字進行一個遞歸的相加。當輸入0時,即n==0,輸出0,直接不進行遞歸。git
這個題的關鍵要求在於不能使用xxxxxx一系列語句。正則表達式
那麼看起來惟一能用到的只有位運算、加減。算法
用到邏輯與的短路特徵:即如($b=false)&&($a=true),當&&前面的語句不成立(false)時,&&後面的語句不會執行;以此來實現遞歸終止。數組
(ps)邏輯或的短路特徵:如($b=true) || ($a=false),當||前面的語句成立(true)時,||後面的語句不會執行。安全
即當n>0時,執行sum+=Sum_Solution(n-1)進行遞歸的累加;當n==0時,執行&&前面語句的判斷(n>0),爲false,而後不對sum作什麼修改處理,直接就是返回第一句的sum=n=0。網絡
解法多線程
public class Solution { public int Sum_Solution(int n) { int sum = n; boolean ans = (n>0)&&((sum+=Sum_Solution(n-1))>0); return sum; } }
題目-不用加減乘除作加法 app
寫一個函數,求兩個整數之和,要求在函數體內不得使用+、-、*、/四則運算符號。
思路
那麼這道題的要求也是使用位運算,咱們來回顧一下經常使用的位運算。
1.移位運算:
左移<<:向左移動,右邊低位補0.左移1位至關於乘以2
e.g. 3<<2:00000011 ---> 00001100(12)
無符號右移>>>:向右移動,右邊捨棄,左邊補0
有符號右移>>:向右移動,右邊捨棄,左邊補的值取決於原來的最高位。原來是1就補1,原來是0就補0.右移1位至關於除以2.
e.g. 3>>1:00000011 ---> 00000001(1)
2.邏輯運算:
與 & :兩個位都爲1,才爲1
或 | :只有一位爲1,就爲1
取反 ~ :1變成0,0變成1
按位異或 ^ :相異爲真,相同爲假
3.應用場景:
1)判斷奇偶:(1&i)==1,此時i的最低位爲1,偶數;(1&j)==1,此時j的最低位爲1,奇數。
解法
public class Solution { public int Add(int num1,int num2) { int sum, carry; do{ //各位相加,不計進位。二進制每位相加至關於各位作異或操做 sum = num1^num2; //記下進位。兩個數各位作與運算,再左移一位。 carry = (num1 & num2)<<1; num1 = sum; num2 = carry; }while(num2!=0);//直到進位值爲0 return num1; } }
題目-把字符串轉換成整數
將一個字符串轉換成一個整數,要求不能使用字符串轉換整數的庫函數。 數值爲0或者字符串不是一個合法的數值則返回0
思路
1.這個題須要注意到的是數據的邊界,可能會溢出。int範圍爲[-2147483648,2147483647],能夠用Integer.MAX_VALUE和Integer.MIN_VALUE來表示。
2.邊界條件:數據溢出;空字符串;正負號狀況;非法字符
3.庫函數:int atoi(const char *str );//將字符串轉換爲整型
atol();//將字符串轉換爲長整型值。
char *itoa( int value, char *string,int radix);//整數轉換爲字符串。value:欲轉換的數據;string:目標字符串的地址;radix:轉換後的進制數,能夠是10進制、16進制等。
解法
public class Solution { public int StrToInt(String str) { if(str==null || str.length()==0) return 0; //判斷是否爲負數 boolean isMinus = str.charAt(0)=='-'; //設置爲long類型,避免溢出 long ret = 0; for(int i=0; i<str.length(); i++){ char c = str.charAt(i); //符號斷定 if(i==0 && (c=='+' || c=='-')) continue; //若是不是數字,非法輸入 if(c<'0' || c>'9') return 0; //將字符拼接成數字 ret = ret*10+(c-'0'); } ret=isMinus ? -ret : ret; //溢出就返回0,用long類型的res來比較, //若是定義爲int res,那再比較就沒有意義了,int範圍爲[-2147483648,2147483647] if(ret>Integer.MAX_VALUE|| ret<Integer.MIN_VALUE) return 0; return (int)ret; } }
題目-數組中重複的數字
在一個長度爲n的數組裏的全部數字都在0到n-1的範圍內。 數組中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每一個數字重複幾回。請找出數組中任意一個重複的數字。 例如,若是輸入長度爲7的數組{2,3,1,0,2,5,3},那麼對應的輸出是第一個重複的數字2。
思路
要求時間複雜度爲O(N),空間複雜度爲O(1)。所以不能使用排序的方法,也不能使用額外的標記數組。
那麼對於這種數組元素在固定範圍的問題。考慮用將值爲i的元素調整到第i個位置來進行求解。
這樣的話,若是當將第i個元素調整到第i位時,咱們發現某個位置i已經有這個元素i,那麼就說明重複。
而且只是要求返回任意重複的一個,賦值duplication[0]
解法
public class Solution { // Parameters: // numbers: an array of integers // length: the length of array numbers // duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation; // Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++ // 這裏要特別注意~返回任意重複的一個,賦值duplication[0] // Return value: true if the input is valid, and there are some duplications in the array number // otherwise false public boolean duplicate(int numbers[],int length,int [] duplication) { if(numbers==null || length==0) return false; //將值爲1的元素調整到第i個位置上求解 for(int i=0; i<length; i++){ while(numbers[i]!=i){ //好比在第二個位置,已經有一個2的值了,那麼就記錄重複 if(numbers[i]==numbers[numbers[i]]){ duplication[0]=numbers[i]; return true; } swap(numbers, i, numbers[i]); } } return false; } private void swap(int numbers[], int i, int j){ int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; } }
題目-構建乘積數組
給定一個數組A[0,1,...,n-1],請構建一個數組B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。(注意:規定B[0] = A[1] * A[2] * ... * A[n-1],B[n-1] = A[0] * A[1] * ... * A[n-2];)
思路
要求不能使用除法,最簡單的思路固然就是連乘,可是時間複雜度爲O(N^2),效率低。
那咱們將每一個B[i] 的計算式列出來,考慮B[i] 之間的聯繫。
列出上表,能夠發現B[i] 其實不一樣之處就是在於第i位是爲1,那麼能夠將B[i]的計算以哪一位爲1爲分割線。
B[i]的左半部分與B[i-1]有關,即B[i]的左半部分=B[i-1]的左半部分 * A[i-1]
B[i]的右半部分與B[i+1]有關,即B[i]的右半部分=B[i+1]的右半部分 * A[i+1]
而後分別計算左右半部分,再乘起來。
解法
import java.util.ArrayList; public class Solution { public int[] multiply(int[] A) { int n = A.length; int[] B = new int[n]; B[0] = 1; //B[i]的左半部分和B[i-1]有關 for(int i=1; i<n; i++){ B[i] = B[i-1]*A[i-1]; } //temp表示右半部分的乘積 int temp=1; //B[i]的右半部分和B[i+1]有關 for(int i=n-2; i>=0; i--){ temp *= A[i+1]; B[i] *= temp; } return B; } }
題目-正則表達式匹配
請實現一個函數用來匹配包括'.'和'*'的正則表達式。模式中的字符'.'表示任意一個字符,而'*'表示它前面的字符能夠出現任意次(包含0次)。 在本題中,匹配是指字符串的全部字符匹配整個模式。例如,字符串"aaa"與模式"a.a"和"ab*ac*a"匹配,可是與"aa.a"和"ab*a"均不匹配
思路
比較值得注意的就是*的處理。由於它可能表示前面的字符出現任意次(包含0次)
出現的狀況可能有:
1.同時匹配到末尾,就匹配成功;
2.模式串到了末尾,可是字符串尚未到末尾。那說明字符串後面的那一部分沒辦法被匹配,匹配失敗;
3.模式串中的下一個字符是*:
1)若是當前字符不匹配,將模式串後移兩位,繼續匹配。
如模式串爲a*b,字符串爲bbb.
後移兩位是由於,若是當前不匹配,那麼只能是說明*表示的是當前字符出現0次,至關於a*被忽略。那麼模式串就應該跳過這兩個字符,從*後面的b開始匹配字符串。
2)若是當前字符匹配 或者 模式串的當前字符爲 ‘.’(因爲模式中的字符'.'表示任意一個字符),那麼根據*的匹配方式,那麼可能出現:
如模式串爲a*b,字符串爲aaa.
若*表示前面字符出現0次,那麼模式串後移兩位,字符串不動;
若*表示前面字符出現1次,那麼模式串後移兩位,字符串後移一位。至關於a*當作一個總體,與字符串的第一個a,來匹配。匹配完後,就都日後移,匹配下一位。
若*表示前面字符出現屢次,那麼模式串不動,字符串後移一位。至關於*也就是a,與字符串的第二個a去匹配。由於*不能表示直接來匹配,只能依據它前面依附的字符來匹配。
將這三種可能的匹配方式,來進行或處理。由於*確定是出如今這三種狀況裏。
4.模式串中下一個字符不是*:
1)當前字符匹配 或者 模式串的當前字符爲 ‘.’ ,那麼後移匹配下一字符;
2)當前字符不匹配,則匹配失敗。
同時還須要在java裏,還要時刻注意檢查數組是否越界。(好比須要去找,字符串和模式串是否匹配到末尾。由於在匹配過程當中會有須要字串後移的操做)
解法
public class Solution { public boolean match(char[] str, char[] pattern) { //空串則返回false if(str==null || pattern==null){ return false; } //遞歸地匹配 return matchCore(str, 0, pattern, 0); } //設置兩個指針,分別指向字符串和模式串的第一個字符 private boolean matchCore(char[] str, int strIndex, char[] pattern, int patternIndex){ int m=str.length, n=pattern.length; //若是同時匹配到末尾,那麼則匹配成功 if(strIndex==m && patternIndex==n){ return true; } //若是模式串先掃描到末尾,則匹配失敗 if(strIndex!=m && patternIndex==n){ return false; } //若是下一個字符是"*" if(patternIndex+1<n && pattern[patternIndex+1]=='*'){ //字符串和模式串當前字符匹配 if(strIndex!=m && (str[strIndex]==pattern[patternIndex] || pattern[patternIndex] == '.')){ return matchCore(str, strIndex, pattern, patternIndex+2)|| //‘*’前的字符出現0次,那麼pattern就要多走*和*前面的字符 matchCore(str, strIndex+1, pattern, patternIndex+1)|| //‘*’前的字符只出現1次,那麼pattern和str都進入下一步的匹配 matchCore(str, strIndex+1, pattern, patternIndex); //‘*’前的字符出現屢次 } else{ //若是當前字符串不匹配,則認爲‘*’前的字符出現0次 //由於只有模式串當前字符不出現時,纔可能字符串當前字符和模式串下一字符匹配 return matchCore(str, strIndex, pattern, patternIndex+2); } } //若是下一個字符不是'*' //當前字符匹配 if(strIndex!=m && (str[strIndex]==pattern[patternIndex] || pattern[patternIndex]=='.')){ return matchCore(str, strIndex+1, pattern, patternIndex+1); } else{//當前字符不匹配 return false; } } }
題目-表示數值的字符串
請實現一個函數用來判斷字符串是否表示數值(包括整數和小數)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示數值。 可是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
思路
這裏是使用正則匹配。給出一些經常使用的正則表示:
[xyz]:字符集,匹配括號中包含的任一字符。
[^xyz]:反向字符集,匹配括號中未包含的任一字符。
?:零次或一次匹配前面的字符或子表達式。
+:一次或屢次匹配前面的字符或子表達式。
*:零次或屢次匹配前面的字符或子表達式。
^:匹配輸入字符串開始的位置。
$:匹配輸入字符串結尾的位置。
\:將下一字符標記爲特殊字符、文本、反向引用或八進制轉義符。
\d:數字字符匹配,[0-9]。(可是java中\是轉移字符前導符,所以在字符串中書寫\必須寫成\\)
\D:非數字匹配,[^0-9]。
\w:匹配任何字類字符,[A-Za-z0-9]。
經常使用的函數:
public boolean matches();//嘗試將整個區域與模式匹配;
public boolean find();//從當前位置開始匹配;
public boolean lookingAt();//嘗試將從區域開頭開始的輸入序列 與 該模式匹配。從第一個字符開始匹配,可是不要求整句都匹配。
解法
public class Solution { public boolean isNumeric(char[] str) { if(str==null || str.length==0) return false; return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?"); } }
題目-字符流中第一個不重複的字符
請實現一個函數用來找出字符流中第一個只出現一次的字符。例如,當從字符流中只讀出前兩個字符"go"時,第一個只出現一次的字符是"g"。當從該字符流中讀出前六個字符「google"時,第一個只出現一次的字符是"l"。
思路
時間複雜度O(1),空間複雜度O(n)
1.構建一個大小爲128的數組,統計每一個字符ch出現的次數;
2.構建一個隊列,記錄全部第一次出現的ch字符。若是當前字符出現2次,那麼彈出;
3.此時隊列中全都是隻出現一次的字符。若是是空,那麼就輸出「#」;若是不是空,那麼彈出隊首。
解法
import java.util.Queue; import java.util.LinkedList; public class Solution { private int[] cnts = new int[256]; private Queue<Character> queue = new LinkedList<>(); //Insert one char from stringstream public void Insert(char ch) { //記錄各元素的出現次數 cnts[ch]++; //在隊列中記錄全部出現過的元素 queue.add(ch); //若是這個元素的次數大於2,那麼彈出 while(!queue.isEmpty() && cnts[queue.peek()]>1){ //返回第一個元素,並在隊列中刪除 queue.poll(); } } //return the first appearence once char in current stringstream public char FirstAppearingOnce() { //若當前字符流沒有存在出現一次的字符,返回# return queue.isEmpty() ? '#' : queue.peek(); } }
題目-鏈表中環的入口結點
給一個鏈表,若其中包含環,請找出該鏈表的環的入口結點,不然,輸出null。
思路
設置快慢指針,從表頭同時出發。快指針每次走兩步,慢指針每次走一步。
1.若是有環,快慢指針必定會相遇在環中某點。(因爲有環,fast先進入環,那麼low進入環,能夠將二者當作是一個追趕的過程)
2.兩個指針分別從鏈表頭和相遇點繼續出發,每次走一步,最終必定會相遇在環入口。
設置AC=a,CB=b,BC=c。
相遇時,快指針的路程=a+(b+c)*k+b;慢指針的路程=a+b.
而快指針的速度=2*慢指針的速度。所以2*(a+b)=a+(b+c)*k+b,化簡獲得a=(k-1)(b+c)+c,即 鏈表頭到環入口的距離=相遇點到環入口的距離+(k-1)圈環長度。
得證。
解法
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } */ public class Solution { public ListNode EntryNodeOfLoop(ListNode pHead) { if(pHead==null || pHead.next==null){ return null; } ListNode slow=pHead, fast=pHead; //由於存在環,兩個指針一定相遇在環中的某個節點 do{ fast=fast.next.next; slow=slow.next; }while(slow!=fast); //讓fast從頭開始移動,速度變爲一次移動一個節點 fast=pHead; while(slow!=fast){ slow=slow.next; fast=fast.next; } return slow; } }
題目-刪除鏈表中重複的結點
在一個排序的鏈表中,存在重複的結點,請刪除該鏈表中重複的結點,重複的結點不保留,返回鏈表頭指針。 例如,鏈表1->2->3->3->4->4->5 處理後爲 1->2->5
思路
使用遞歸
1)若是當前結點是重複結點,那麼刪除這個重複結點,從第一個與當前結點不一樣的結點開始遞歸。
2)若是當前結點不重複,那麼直接從下一結點開始遞歸。
解法
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } */ public class Solution { public ListNode deleteDuplication(ListNode pHead) { //若是鏈表爲空 if(pHead==null || pHead.next==null) return pHead; ListNode next = pHead.next; //若是出現重複結點,刪除重複結點且不保留 if(next.val == pHead.val){ while(next!=null && next.val==pHead.val){ next = next.next; } return deleteDuplication(next); } //若是沒有重複結點,那麼就判斷下一個結點 else{ pHead.next = deleteDuplication(pHead.next); return pHead; } } }
題目-二叉樹的下一個結點
給定一個二叉樹和其中的一個結點,請找出中序遍歷順序的下一個結點而且返回。注意,樹中的結點不只包含左右子結點,同時包含指向父結點的指針。
思路
根據中序遍歷的規則:左根右。獲得遍歷的規律
1.若是有右子樹,那麼下一個結點就是右子樹最左結點;
2.若是沒有右子樹,那麼下一結點是:a)父結點,當前結點爲父結點的左孩子時;b)向上找到第一個左子樹指向的樹,好比J,其下一節點是父結點的父結點的父結點,直到當前結點是這個父結點的左子樹部分。由於左根右,當前結點是右結點的時候,說明已經遍歷到這棵樹的最後一個結點,應該開始遍歷另外半棵樹。
解法
/* public class TreeLinkNode { int val; TreeLinkNode left = null; TreeLinkNode right = null; TreeLinkNode next = null; //next爲指向父結點的指針 TreeLinkNode(int val) { this.val = val; } } */ public class Solution { public TreeLinkNode GetNext(TreeLinkNode pNode) { //中序遍歷:左根右 //若是一個結點的右子樹不爲空,那麼該結點的下一個結點是右子樹的最左結點 if(pNode.right != null){ TreeLinkNode node = pNode.right; while(node.left != null){ node = node.left; } return node; } //不然,向上找第一個左連接指向的樹 包含 該結點的祖先結點 else{ while(pNode.next != null){ TreeLinkNode parent = pNode.next; if(parent.left == pNode){ return parent; } pNode = pNode.next; } } return null; } }
題目-對稱的二叉樹
請實現一個函數,用來判斷一顆二叉樹是否是對稱的。注意,若是一個二叉樹同此二叉樹的鏡像是一樣的,定義其爲對稱的。
思路
使用遞歸,只是須要不斷判斷結點的left和right是否對稱;
即左右結點的值要相等&&對稱子樹left.left與right.right、left.right與right.left也要對稱。
解法
/* public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ public class Solution { boolean isSymmetrical(TreeNode pRoot) { if(pRoot == null){ return true; } return isSymmetrical(pRoot.left, pRoot.right); } boolean isSymmetrical(TreeNode t1, TreeNode t2){ if(t1 == null && t2 == null) return true; if(t1 == null || t2 == null) return false; if(t1.val != t2.val) return false; return isSymmetrical(t1.left, t2.right) && isSymmetrical(t1.right, t2.left); } }
題目-按之字形順序打印二叉樹
請實現一個函數按照之字形打印二叉樹,即第一行按照從左到右的順序打印,第二層按照從右至左的順序打印,第三行按照從左到右的順序打印,其餘行以此類推。
思路
1.將每層數據存入ArrayList,當偶數層時經過Collections.reverse(list);翻轉list。可是其實咱們給出來的這種方法使用reverse,處理海量數據時,效率過低。
2.能夠利用LinkedList實現的是雙向鏈表的特色,作成雙向遍歷。
使用queue.iterator();//從前日後遍歷
queue.descendingIterator();//從後往前遍歷
而且須要在queue中添加null作層分隔符
解法
import java.util.ArrayList; import java.util.LinkedList; import java.util.Queue; import java.util.Collections; /* public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ public class Solution { public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) { ArrayList<ArrayList<Integer>> ret = new ArrayList<>(); Queue<TreeNode> queue = new LinkedList<>(); queue.add(pRoot); //用變量記錄奇數層仍是偶數層,是否反向 boolean reverse = false; while(!queue.isEmpty()){ ArrayList<Integer> list = new ArrayList<>(); int cnt = queue.size(); //若是隊列不爲空,就循環取值 while(cnt-- > 0){ TreeNode node = queue.poll(); if(node == null) continue; list.add(node.val); queue.add(node.left); queue.add(node.right); } //若是是從右到左,就反轉list if(reverse == true) Collections.reverse(list); //反向 reverse = !reverse; if(list.size() != 0){ ret.add(list); } } return ret; } }
題目-把二叉樹打印成多行
從上到下按層打印二叉樹,同一層結點從左至右輸出。每一層輸出一行。
思路
這個題與上一題「按照之字形打印二叉樹」相似。
只是去掉了須要翻轉的部分。
構建一個隊列來存儲二叉樹,而後將一個結點從隊列中取出並存入list的同時,將結點的左右結點添加到隊列。不斷作輸出。
解法
import java.util.ArrayList; import java.util.Queue; import java.util.LinkedList; /* public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ public class Solution { ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) { ArrayList<ArrayList<Integer>> ret = new ArrayList<>(); //構造隊列來存儲二叉樹 Queue<TreeNode> queue = new LinkedList<>(); queue.add(pRoot); while(!queue.isEmpty()){ ArrayList<Integer> list = new ArrayList<>(); int cnt = queue.size(); while(cnt-- > 0){ TreeNode node = queue.poll(); if(node == null) continue; //將當前結點存儲到list中,待輸出 list.add(node.val); //加入當前結點的左右子樹 queue.add(node.left); queue.add(node.right); } if(list.size() != 0) ret.add(list); } return ret; } }
題目-序列化二叉樹
請實現兩個函數,分別用來序列化和反序列化二叉樹 二叉樹的序列化是指:把一棵二叉樹按照某種遍歷方式的結果以某種格式保存爲字符串,從而使得內存中創建起來的二叉樹能夠持久保存。序列化能夠基於先序、中序、後序、層序的二叉樹遍歷方式來進行修改,序列化的結果是一個字符串,序列化時經過 某種符號表示空節點(#),以 ! 表示一個結點值的結束(value!)。 二叉樹的反序列化是指:根據某種遍歷順序獲得的序列化字符串結果str,重構二叉樹。
思路
1.序列化:就是使用一些遍歷規則來將二叉樹遍歷爲字符串,好比前序遍歷「根左右」。就按照這個順序讀結點的值、遞歸左結點、遞歸右結點。
2.反序列化:按照遍歷順序,重構二叉樹。
根據字符串序列,因爲序列化時,不一樣結點之間有分隔符,經過分隔符,獲取每一個結點。
將該結點的字符截取出來,而後截斷原字符。根據結點的值構建二叉樹、遞歸重構左結點、遞歸重構右結點。
3.public String substring(int beginIndex);//返回一個新字符串,它是此字符串的一個子字符串。該子字符串始於指定索引處的字符,一直到此字符串末尾。
public String substring(int beginIndex, int endIndex);//返回一個新字符串,它是此字符串的一個子字符串。該子字符串從指定的 beginIndex 處開始, endIndex:到指定的 endIndex-1處結束。
解法
/* public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */
public class Solution { String Serialize(TreeNode root) { //序列化時經過 某種符號表示空節點(#) if(root == null) return "#"; return root.val + " " + Serialize(root.left) + " " + Serialize(root.right); } private String deserializeStr;
TreeNode Deserialize(String str) { deserializeStr = str; return Deserialize(); } TreeNode Deserialize(){ if(deserializeStr.length() == 0) return null; //獲取一個結點 int index = deserializeStr.indexOf(" "); //該子字符串從指定的 beginIndex 處開始, endIndex:到指定的 endIndex-1處結束。 String node = index==-1 ? deserializeStr : deserializeStr.substring(0, index); //將字符串截掉已經存入node的那一部分。substring該子字符串始於指定索引處的字符,一直到此字符串末尾。 deserializeStr = index==-1 ? deserializeStr : deserializeStr.substring(index+1); //"#"爲空結點 if(node.equals("#")){ return null; } int val = Integer.valueOf(node); TreeNode t = new TreeNode(val); t.left = Deserialize(); t.right = Deserialize(); return t; } }
題目-二叉搜索樹的第k個結點
給定一棵二叉搜索樹,請找出其中的第k小的結點。例如,(5,3,7,2,4,6,8)中,按結點數值大小順序第三小結點的值爲4。
思路
二叉搜索樹(二叉查找樹、二叉排序樹):根節點的值大於其左子樹中任意一個節點的值,小於其右節點中任意一節點的值,這一規則適用於二叉查找樹中的每個節點。
根據二叉搜索樹的特色,咱們能夠發現其中序遍歷(左根右)是有序的。即按照中序遍歷的順序,找到的第k個結點就是結果。
解法
/* public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ public class Solution { //二叉搜索樹的特性:根結點的值大於其左子樹中任意一個結點的值,小於其右子樹中任意一結點的值。 //所以中序遍歷(左根右)是有序的 private TreeNode ret; private int cnt = 0; TreeNode KthNode(TreeNode pRoot, int k) { inOrder(pRoot, k); return ret; } private void inOrder(TreeNode pRoot, int k){ if(pRoot == null || cnt >= k){ return; }
//左 inOrder(pRoot.left, k); //根,標記第k個結點 cnt++; if(cnt == k){ ret = pRoot; }
//右 inOrder(pRoot.right, k); } }
題目-數據流中的中位數
如何獲得一個數據流中的中位數?若是從數據流中讀出奇數個數值,那麼中位數就是全部數值排序以後位於中間的數值。若是從數據流中讀出偶數個數值,那麼中位數就是全部數值排序以後中間兩個數的平均值。咱們使用Insert()方法讀取數據流,使用GetMedian()方法獲取當前讀取數據的中位數。
傳入的數據爲:[5,2,3,4,1,6,7,0,8],那麼按照要求,輸出是"5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00 "。
思路
1.也就是不斷根據獲取的數據流,得到當前數據的中位數。注意中位數是須要排序的。
2.那麼咱們能夠經過優先隊列PriorityQueue來設置大頂堆和小頂堆。
其中大頂堆用來存較小的數,從大到小排序;小頂堆用來村較大的數,從小到大排序。
這樣的話中位數也就是大頂堆的根結點(較小數中的最大) 和 小頂堆的根結點(較大數中的最小)的平均數。也就是中位數依據的 全部數值排序後中間的兩個數
3.構造兩個堆的時候,須要保證小頂堆的元素都大於等於大頂堆的元素&&須要保證兩個堆的數據處於平衡狀態(元素個數相差不超過1)。
每一個堆加一個的順序。
當數目爲偶數時,將這個值插入大頂堆中,再從大頂堆中poll出根結點(最大值),插入到小頂堆;
當數目爲奇數時,將這個值插入小頂堆中,再從小頂堆中poll出根結點(最小值),插入到大頂堆;
所以取中位數時,若當前個數爲偶數,那麼就是取大頂堆和小頂堆的根結點的平均值;若當前個數爲奇數時,就是取小頂堆的根結點。
4.隊列遵循先進先出原則,可是有時須要在隊列中基於優先級處理對象。PriorityQueue是基於優先堆的一個無界隊列,這個優先隊列中的元素能夠默認天然排序(從小到大)或者經過提供的Comparator(比較器)在隊列實例化的時排序。優先隊列不容許空值,且隊列的大小不受限制,在建立時能夠指定初始大小(以下面定義maxHeap時給出的11)。當咱們向優先隊列增長元素的時候,隊列大小會自動增長。
PriorityQueue是非線程安全的,因此Java提供了PriorityBlockingQueue(實現BlockingQueue接口)用於Java多線程環境。
解法
import java.util.Comparator; import java.util.PriorityQueue; public class Solution { //當前數據流讀入的元素個數 public int count = 0; //用兩個堆來保存數據 //小頂堆,存儲右半邊元素,右半邊元素都大於左半邊 public PriorityQueue<Integer> minHeap = new PriorityQueue<>(); //大頂堆,存儲左半邊元素。 public PriorityQueue<Integer> maxHeap = new PriorityQueue<>(11, new Comparator<Integer>(){ public int compare(Integer o1, Integer o2){ return o2 - o1; } }); public void Insert(Integer num) { count++; //插入過程,須要保證兩個堆的數據處於平衡狀態(元素個數相差不超過1) if(count % 2 ==0){//偶數 //由於右半邊元素都要大於左半邊,可是新插入的元素不必定比左半邊元素大 //所以須要先將元素插入左半邊,而後利用左半邊爲大頂堆的特色(堆頂元素即爲最大值),插入右半邊) //即偶數狀況 都插入到右半邊 if(!maxHeap.isEmpty() && num < maxHeap.peek()){ maxHeap.add(num); num = maxHeap.poll(); } minHeap.add(num); } else{//奇數 //將新加入的元素加入小頂堆 if(!minHeap.isEmpty() && num > minHeap.peek()){ minHeap.add(num); num = minHeap.poll(); } maxHeap.add(num); } } public Double GetMedian() { if(maxHeap.size() == minHeap.size()) return (minHeap.peek() + maxHeap.peek()) / 2.0; else if(maxHeap.size() > minHeap.size()) return maxHeap.peek()*1.0; else return minHeap.peek()*1.0; /*if(count % 2 == 0){//總量是偶數 return (minHeap.poll() + maxHeap.poll()) / 2.0; } else{//總量是奇數 return (double)minHeap.poll(); }*/ } }
題目-滑動窗口的最大值
給定一個數組和滑動窗口的大小,找出全部滑動窗口裏數值的最大值。例如,若是輸入數組{2,3,4,2,6,2,5,1}及滑動窗口的大小3,那麼一共存在6個滑動窗口,他們的最大值分別爲{4,4,6,6,6,5}; 針對數組{2,3,4,2,6,2,5,1}的滑動窗口有如下6個。
{[2,3,4],2,6,2,5,1},
{2,[3,4,2],6,2,5,1},
{2,3,[4,2,6],2,5,1},
{2,3,4,[2,6,2],5,1},
{2,3,4,2,[6,2,5],1},
{2,3,4,2,6,[2,5,1]}
思路
1.因爲題目要求輸出滑動窗口中數值最大值。一樣使用java的PriorityQueue,使用大頂堆。經過滑動窗口裏的值來維護這個大頂堆。
滑動窗口滑動時,爲了維護滑動窗口值組成的大頂堆,將當前元素加入到大頂堆,堆頂元素就是最大值。將滑動窗口最左邊的值去掉。
最後把該元素添加到Arraylist中待輸出。
2.Queue的用法:
offer()&add():add()在失敗時會拋出異常。
poll()&remove():返回第一個元素,並在隊列中刪除。remove()在失敗時會拋出異常。
peek()&element():返回第一個元素.element()在隊列爲空時,會拋出異常。
解法
import java.util.*; public class Solution { public ArrayList<Integer> maxInWindows(int [] num, int size) { ArrayList<Integer> ret = new ArrayList<>(); if(size > num.length || size < 1){ return ret; } //構建大頂堆. PriorityQueue<Integer> maxHeap = new PriorityQueue<>(11, new Comparator<Integer>(){ public int compare(Integer o1, Integer o2){ return o2 - o1; } }); //最初添加,滑動窗口滑動時,將當前元素加入大頂堆中,堆頂元素便是最大值。把頭三個元素加入到maxHeap中,而且輸出最大元素到Arraylist for(int i = 0; i < size ; i++){ maxHeap.add(num[i]); } //同時還要判斷當前元素是否存在於當前窗口中,不存在的話彈出,最後將該元素添加到Arraylist ret.add(maxHeap.peek()); //窗口開始滑動。維護一個大小爲size的大頂堆 for(int i=0, j=i+size; j < num.length; i++, j++){ //按照窗口,添加元素。maxHeap中始終爲窗口內的元素 maxHeap.remove(num[i]); maxHeap.add(num[j]); ret.add(maxHeap.peek()); } return ret; } }
題目-矩陣中的路徑
請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串全部字符的路徑。路徑能夠從矩陣中的任意一個格子開始,每一步能夠在矩陣中向左,向右,向上,向下移動一個格子。若是一條路徑通過了矩陣中的某一個格子,則該路徑不能再進入該格子。 例如 矩陣中包含一條字符串"bcced"的路徑,可是矩陣中不包含"abcb"路徑,由於字符串的第一個字符b佔據了矩陣中的第一行第二個格子以後,路徑不能再次進入該格子。
思路
1.這是一個典型的回溯法應用場景。
2.首先須要建立一個輔助數組 來 標記 每一個位置是不是字符串中的字符;再建立一個變量,用來存儲當前路徑的長度。當其長度與字符串同樣時,說明已經完成。
3.先遍歷矩陣,找到和字符串第一個字符相同的字符,做爲路徑的開始;
4.函數中首先要進行合法性判斷&當前路徑的字符和字符串上對應位置的字符是否相等。符合條件才能進行下面的遞歸遍歷;
5.矩陣路徑此時+1,而且標記輔助數組中的該位置爲已訪問;
6.因爲「每一步能夠在矩陣中向左,向右,向上,向下移動一個格子」。接下來對該位置的上下左右四個位置進行遞歸遍歷,在遞歸中,對於各位置找尋下一個位置。若是找到了,就可以一條順序下去找到完整的路徑,返回true;
7.若是沒有找到,須要將路徑-1,且標記該位置從新爲未訪問(清除以前的標記狀態)。由於要從新尋找。
解法
public class Solution { private final static int[][] next = {{0,-1},{0,1},{-1,0},{1,0}}; private int rows; private int cols; public boolean hasPath(char[] matrix, int rows, int cols, char[] str) { if(matrix==null || rows<=0 || cols<=0 || str==null){ return false; } if(str.length==0){ return true; } //用來標記路徑中已經訪問過的結點 boolean[] marked=new boolean[matrix.length]; for(int i=0; i<rows; i++){ for(int j=0; j<cols; j++){ //先遍歷矩陣,找到和字符串第一個字符相同的字符,做爲路徑的開始 if(backtracking(matrix, str, marked, 0, i, j, rows, cols)) return true; } } return false; } //嘗試尋找路徑 public boolean backtracking(char[] matrix, char[] str, boolean[] marked, int k, int row, int col, int rows, int cols){ //合法性判斷&當前路徑的字符和字符串上對應位置的字符是否相等 if(row<0 || row>=rows || col<0 || col>=cols || str[k]!=matrix[row*cols+col] || marked[row*cols+col]){ return false; } //直到路徑字符串上全部字符都在矩陣中找到合適的位置 if(k==str.length-1){ return true; } //要搜索這個結點,首先須要將其標記爲已經使用,防止重複使用 marked[row*cols+col]=true; //對當前位置的上下左右4個相鄰的格子進行遍歷,匹配到下一個字符 if(backtracking(matrix, str, marked, k+1, row+1, col, rows, cols) || backtracking(matrix, str, marked, k+1, row, col+1, rows, cols) || backtracking(matrix, str, marked, k+1, row-1, col, rows, cols) || backtracking(matrix, str, marked, k+1, row, col-1, rows, cols)){ return true; } //若是4個相鄰的格子都沒有匹配字符串中下一個的字符,代表當前路徑字符串中字符在矩陣中的定位不正確,咱們須要回到前一個,而後從新定位。 //這一次搜索結束後,須要將該結點的已經使用狀態清除 marked[row*cols+col]=false; return false; } }
題目-機器人的運動範圍
地上有一個m行和n列的方格。一個機器人從座標0,0的格子開始移動,每一次只能向左,右,上,下四個方向移動一格,可是不能進入行座標和列座標的數位之和大於k的格子。 例如,當k爲18時,機器人可以進入方格(35,37),由於3+5+3+7 = 18。可是,它不能進入方格(35,38),由於3+5+3+8 = 19。請問該機器人可以達到多少個格子?
思路
這道題與上一題思路基本類似,使用回溯法。
一點點小區別在於:
1.本題規定直接從「座標0,0的各自開始移動」,上一題須要咱們去遍歷數組,獲得一個合適的路徑開始點;
2.本題中有一些格子不可以走(行座標和列座標的數位之和大於k的格子)。每一步移動時,還須要添加一步來判斷這個各自是否合法(再合法性判斷時添加便可)。也就是至關於上一題中須要判斷結點是否匹配的效果。
解法
public class Solution { public int movingCount(int threshold, int rows, int cols) { if(rows <= 0 || cols <= 0){ return 0; } //標記已經訪問過的位置 boolean[] marked = new boolean[rows*cols]; for(int i=0; i<marked.length; i++){ marked[i] = false; } //從(0,0)座標開始移動 int count = movingCountCore(threshold, rows, cols, 0, 0, marked); return count; } public int movingCountCore(int threshold, int rows, int cols, int row, int col, boolean[] marked){ int count = 0; if(row>=0 && row<rows && col>=0 && col<cols && getDigitSum(row)+getDigitSum(col)<=threshold && !marked[row*cols+col]){ marked[row*cols+col] = true; count = 1+movingCountCore(threshold, rows, cols, row-1, col, marked)+ movingCountCore(threshold, rows, cols, row+1, col, marked)+ movingCountCore(threshold, rows, cols, row, col-1, marked)+ movingCountCore(threshold, rows, cols, row, col+1, marked); } return count; } public int getDigitSum(int num){ int sum = 0; while(num > 0){ sum += (num%10); num /= 10; } return sum; } }
題目-剪繩子
給你一根長度爲n的繩子,請把繩子剪成整數長的m段(m、n都是整數,n>1而且m>1),每段繩子的長度記爲k[0],k[1],...,k[m]。請問k[0]xk[1]x...xk[m]可能的最大乘積是多少?例如,當繩子的長度是8時,咱們把它剪成長度分別爲二、3、3的三段,此時獲得的最大乘積是18。
思路
1.本題可使用貪婪算法,可是並非全部問題均可以經過 貪婪算法 獲得最優解,依賴於數學證實。貪婪算法的效率也更高。
2.咱們使用動態規劃(每一步都是從新將子問題的最優值相加計算出的)來解決這個問題。雖然不少子問題會重複求解,效率不高。
要求的是乘積最大值,那麼定義一個函數f(n)表示長度爲n的繩子剪成若干段後 各段 乘積 的最大值。
當n=1時,最大乘積只能爲0=1*0;
當n=2時,最大乘積只能爲1=1*1;
當n=3時,最大乘積只能爲2=1*2;
當n=4時,能夠分爲以下幾種狀況:1*1*1*1,1*2*1,1*3,2*2,最大乘積爲4。
剪第一刀時,選擇下第一刀的地方有1~n-1這些地方,有n-1種可能。剪出來的第一段的繩子可能長度爲1,2,3,...,n-1。假設在第i處下刀,繩子將分爲[0,i]和[i,n],長度分別爲i與n-i。那麼找出第一刀最合適的位置,就是找i在哪裏下刀,可使得[0,i]與[i,n]的乘積最大。
即f(n)=max( f(i)*f(n-i) ),0<i<n。
從下往上推,先計算f(1)接着計算f(2)...直到獲得f(n)。大的問題經過按照小問題的最優組合獲得。
3.好比要求長度爲10的繩子,就計算1-9這9種長度的繩子,每種長度的最大乘積是多少。要求長度爲9的繩子,就計算1-8這8種長度的繩子,每種長度的最大乘積是多少。以此類推。
解法
public class Solution { public int cutRope(int target) {
//dp用來存放最大乘積值 int[] dp = new int[target+1]; dp[1] = 1; //記長度爲n的最大乘積爲f(n),易知f(n)=max{ f(i)*f(n-i) },其中0<i<n。 for(int i=2; i<=target; i++){ for(int j=1; j<i; j++){ dp[i] = Math.max(dp[i], Math.max(j*(i-j), dp[j]*(i-j))); } } return dp[target]; } }
public int cutRope(int length) { if (length < 2) { return 0; } if (length == 2) { return 1; } if (length == 3) { return 2; } //存儲長度從 0-len 的最大結果 int[] products = new int[length + 1]; // 將最優解存儲在數組中 // 數組中第i個元素表示把長度爲i的繩子剪成若干段以後的乘積的最大值 products[0] = 0; products[1] = 1; products[2] = 2; products[3] = 3; int max = 0; for (int i = 4; i <= length; i++) { //i表示長度 max = 0; for (int j = 1; j <= i / 2; j++) { //因爲長度i存在(1,i-1)和(i-1,1)的重複,因此只須要考慮前一種 int product = products[j] * products[i - j]; if (product > max) { max = product; } products[i] = max; } } max = products[length] return max; } }
題目-圖的廣度優先遍歷
用隊列記錄下一步的走向。
1.訪問下一個未訪問的鄰接點,這個頂點必須是當前頂點的鄰接點,標記它,加入隊列
2.若是由於已經沒有 未訪問頂點,而不能執行上一條規則,那麼從隊列頭取一個頂點,使其成爲當前頂點
3.直到隊列爲空,完成搜索
題解
public void searchTraversing(GraphNode node){ ArrayList<GraphNode> visited=new ArrayList<GraphNode>(); ArrayList<GraphNode> list=new ArrayList<GraphNode>(); Queue<GraphNode> queue = new LinkedList<GraphNode>(); queue.add(node); while(!queue.isEmpty()){ GraphNode curNode=queue.poll(); if(!visited.contain(curNode)){ visited.add(curNode); list.add(curNode.getLabel()); for(int i=0;i<curNode.edgeList.size();i++){ queue.offer(curNode.edgeList.get(i).getNodeRight()); } } } }
(思路部分的圖均源於網絡,侵刪)