劍指Offer——程式設計題的Java實現(更新完畢……)
二維陣列中的查詢
在一個二維陣列中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函式,輸入這樣的一個二維陣列和一個整數,判斷陣列中是否含有該整數。
/* * 思路 矩陣是有序的,從右上角來看,向左數字遞減,向下數字遞增, * 因此從右上角開始查詢,當要查詢數字比左下角數字大時。下移 * 要查詢數字比左上角數字小時,左移 */ public class Solution { public boolean Find(int[][] array, int target) { int len = array.length - 1; int i = 0; while ((len >= 0) && (i < array[0].length)) { if (array[len][i] > target) { len--; } else if (array[len][i] < target) { i++; } else { return true; } } return false; } }
替換空格
請實現一個函式,將一個字串中的空格替換成“%20”。例如,當字串為We Are Happy.則經過替換之後的字串為We%20Are%20Happy。請實現一個函式,將一個字串中的空格替換成“%20”。例如,當字串為We Are Happy.則經過替換之後的字串為We%20Are%20Happy。
public class Solution { // 從後往前,先計算需要多少空間,然後從後往前移動,則每個字元只為移動一次. public String replaceSpace(StringBuffer str) { if (str == null) { return null; } int blankNum = 0; int length = str.length(); int newLength = 0; for (int i = 0; i < length; i++) { if (str.charAt(i) == ' ') { blankNum++; } } newLength = length + 2 * blankNum; // 替換後的字串長度 char[] newChars = new char[newLength];// 新的字元陣列 int index = newLength - 1; for (int i = length - 1; i >= 0; i--) { if (str.charAt(i) == ' ') { newChars[index--] = '0'; newChars[index--] = '2'; newChars[index--] = '%'; } else { newChars[index--] = str.charAt(i); } } return new String(newChars); } }
public class Solution { //藉助StringBuffer public String replaceSpace(StringBuffer str) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < str.length(); i++) { if (str.charAt(i) == ' ') { sb.append("%20"); } else { sb.append(str.charAt(i)); } } return sb.toString(); } }
從尾到頭列印連結串列
輸入一個連結串列,從尾到頭列印連結串列每個節點的值。
連結串列結點定義:
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
使用遞迴:
import java.util.ArrayList;
public class Solution {
ArrayList<Integer> arrayList = new ArrayList<Integer>();
//使用遞迴實現
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
if (listNode != null) {
printListFromTailToHead(listNode.next);
arrayList.add(listNode.val);
}
return arrayList;
}
}
使用棧的後進先出
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
Stack<Integer> stack = new Stack<>();
while (listNode != null) {
stack.push(listNode.val);
listNode = listNode.next;
}
ArrayList<Integer> list = new ArrayList<>();
while (!stack.isEmpty()) {
list.add(stack.pop());
}
return list;
}
}
重建二叉樹
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Solution {
public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
TreeNode root = reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1);
return root;
}
// 前序遍歷{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6}
private TreeNode reConstructBinaryTree(int[] pre, int startPre, int endPre, int[] in, int startIn, int endIn) {
if (startPre > endPre || startIn > endIn)
return null;
TreeNode root = new TreeNode(pre[startPre]);
for (int i = startIn; i <= endIn; i++)
if (in[i] == pre[startPre]) {
root.left = reConstructBinaryTree(pre, startPre + 1, startPre + i - startIn, in, startIn, i - 1);
root.right = reConstructBinaryTree(pre, i - startIn + startPre + 1, endPre, in, i + 1, endIn);
}
return root;
}
}
用兩個棧實現佇列
用兩個棧來實現一個佇列,完成佇列的Push和Pop操作。 佇列中的元素為int型別。有兩個棧,棧1和棧2.當入棧的時候,我們將它全放進棧1中,當需要出棧的時候,我們將棧1出棧到棧2中,然後再將棧2依次出棧。所以入棧的時候,思路很簡單;當需要出棧的時候,我們用API提供的方法while(stack1.isEmpty())來將所有棧1的元素壓入棧2中,然後將棧2彈出就可以.
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
if (stack1.empty() && stack2.empty()) {
throw new RuntimeException("Queue is empty!");
}
if (stack2.empty()) {
while (!stack1.empty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
用兩個佇列實現一個棧
移步
http://blog.csdn.net/mine_song/article/details/63322097
旋轉陣列的最小數字
把一個數組最開始的若干個元素搬到陣列的末尾,我們稱之為陣列的旋轉。輸入一個非遞減排序的陣列的一個旋轉,輸出旋轉陣列的最小元素。例如陣列{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該陣列的最小值為1。
NOTE:給出的所有元素都大於0,若陣列大小為0,請返回0。
使用二分查詢:
public class Solution {
public int minNumberInRotateArray(int[] array) {
if (array == null || array.length == 0)
return 0;
int low = 0;
int high = array.length - 1;
while (low < high) {
int mid = low + (high - low) / 2;
if (array[mid] > array[high]) {
low = mid + 1;
// high = high - 1;可以避免low,high,mid相等的找不到最小值情況。
// int[] array={1,0,1,1,1};
} else if (array[mid] == array[high]) {
high = high - 1;
} else {
high = mid;
}
}
return array[low];
}
}
首先陣列長度為零時,返回零,因為測試要求這樣。然後有一個特殊情況是沒有旋轉,那麼返回array[0],其次一般情況while一直迴圈,直到後面的數 < 前面的數停止,這個數就是我們要找的。
public class Solution {
public int minNumberInRotateArray(int[] array) {
if (array.length == 0)
return 0;
// 避免i+1越界,i要小於array.length - 1
for (int i = 0; i < array.length - 1; i++) {
if (array[i] > array[i + 1])
return array[i + 1];
}
// 所有元素相等時候或者未旋轉,返回array[0]
return array[0];
}
}
斐波那契數列
大家都知道斐波那契數列,現在要求輸入一個整數n,請你輸出斐波那契數列的第n項。n<=39
public class Solution {
public int Fibonacci(int n) {
// 方法:用遞迴,系統會讓一個超大的n來讓StackOverflow,所以遞迴就不考慮了
// 使用迭代法,用fn1和fn2儲存計算過程中的結果,並複用起來
int fn1 = 1;
int fn2 = 1;// 考慮出錯情況
int res = 0;
if (n <= 0) {
return 0;
} // 第一和第二個數直接返回
if (n == 1 || n == 2) {
return 1;
}
for (int i = 3; i <= n; i++) {
res = fn1 + fn2;
fn2 = fn1;
fn1 = res;
}
return res;
}
}
跳臺階
一隻青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
public class Solution {
public int JumpFloor(int target) {
int fn1 = 1;
int fn2 = 2;
int res = 0;
if (target <= 0) {
return 0;
}
if (target == 1) {
return 1;
}
if (target == 2) {
return 2;
}
for (int i = 3; i <= target; i++) {
res = fn1 + fn2;
fn1 = fn2;
fn2 = res;
}
return res;
}
}
遞迴對於N級臺階,可以從N-1級和N-2級上來,所以JumpFloor(N) = JumpFloor(N-1)+JumpFloor(N-2) N=1時,只有一種 N=2時,有兩種:一次2級;兩次1級
public class Solution {
public int JumpFloor(int target) {
int result = 0;
if (target > 0) {
if (target <= 2)
return target;
else
return result = JumpFloor(target - 1) + JumpFloor(target - 2);
}
return result;
}
變態跳臺階
一隻青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
:
非遞迴:
public class Solution {
public int JumpFloorII(int target) {
int jumpFlo = 1;
while (--target > 0) {
jumpFlo *= 2;
}
return jumpFlo;
}
}
2^(n-1)可以用位移操作進行
public class Solution {
public int JumpFloorII(int target) {
return 1<<--target;
}
}
使用遞迴:
public class Solution {
public int JumpFloorII(int target) {
if (target < 0) {
return 0;
} else if (target == 1) {
return 1;
} else {
return 2 * JumpFloorII(target - 1);
}
}
}
矩形覆蓋
我們可以用2*1的小矩形橫著或者豎著去覆蓋更大的矩形。請問用n個2*1的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?
同上述青蛙跳臺階
二進位制中1的個數
輸入一個整數,輸出該數二進位制表示中1的個數。其中負數用補碼錶示。
如果一個整數不為0,那麼這個整數至少有一位是1。如果我們把這個整數減1,那麼原來處在整數最右邊的1就會變為0,原來在1後面的所有的0都會變成1(如果最右邊的1後面還有0的話)。其餘所有位將不會受到影響。
舉個例子:一個二進位制數1100,從右邊數起第三位是處於最右邊的一個1。減去1後,第三位變成0,它後面的兩位0變成了1,而前面的1保持不變,因此得到的結果是1011.我們發現減1的結果是把最右邊的一個1開始的所有位都取反了。這個時候如果我們再把原來的整數和減去1之後的結果做與運算,從原來整數最右邊一個1那一位開始所有位都會變成0。如1100&1011=1000.也就是說,把一個整數減去1,再和原整數做與運算,會把該整數最右邊一個1變成0.那麼一個整數的二進位制有多少個1,就可以進行多少次這樣的操作。
public class Solution {
public int NumberOf1(int n) {
int num = 0;
while (n != 0) {
n = n & (n - 1);
num++;
}
return num;
}
}
數值的整數次方
給定一個double型別的浮點數base和int型別的整數exponent。求base的exponent次方
public class Solution {
//時間複雜度O(n)
public double Power(double base, int exponent) {
int n = Math.abs(exponent);
if (n == 0)
return 1;
if (n == 1)
return base;
//以上兩個if判斷可省。for迴圈中判斷
double result = 1.0
;
for (int i = 0; i < n; i++) {
result *= base;
}
if (exponent < 0) {
result = 1 / result;
}
return result;
}
}
使用遞迴,時間複雜度O(logn) 當n為偶數,a^n =(a^n/2)*(a^n/2)
當n為奇數,a^n = a^[(n-1)/2] * a^[(n-1)/2] * a
舉例
2^11 = 2^1 * 2^2 * 2^8
2^1011 = 2^0001 * 2^0010 * 2^1000
public class Solution {
// 時間複雜度O(lgn)
public double power(double base, int exponent) {
int n = Math.abs(exponent);
double result = 0.0;
if (n == 0)
return 1.0;
if (n == 1)
return base;
result = power(base, n >> 1);
result *= result;
// 如果指數n為奇數,則要再乘一次底數base
// 最後一位是1,與1相與得1,是奇數
if ((n & 1) == 1)
result *= base;
// 如果指數為負數,則應該求result的倒數
if (exponent < 0)
result = 1 / result;
return result;
}
}
調整陣列順序使奇數位於偶數前面
輸入一個整數陣列,實現一個函式來調整該陣列中數字的順序,使得所有的奇數位於陣列的前半部分,所有的偶數位於位於陣列的後半部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變。
1.使用氣泡排序,前偶後奇就交換
public void reOrderArray(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
for (int j = 0; j < array.length - 1 - i; j++) {
// 前偶後奇數就交換
if ((array[j] & 1) == 0 && (array[j + 1] & 1) == 1) {
array[j] = array[j] ^ array[j + 1];
array[j + 1] = array[j] ^ array[j + 1];
array[j] = array[j] ^ array[j + 1];
}
}
}
}
2.空間換時間,使用額外陣列。
public void reOrderArray(int[] array) {
int[] newArr = new int[array.length];
//newArr的下標計數器
int j = 0;
for (int i = 0; i < array.length; i++)
if ((array[i] & 1) == 1) {
newArr[j] = array[i];
j++;
}
for (int i = 0; i < array.length; i++)
if ((array[i] & 1) == 0) {
newArr[j] = array[i];
j++;
}
for (int i = 0; i < array.length; i++)
array[i] = newArr[i];
}
相對位置發生變化的解法
public class Solution {
public void reOrderArray(int[] array) {
if (array == null)
return;
int begin = 0;
int end = array.length - 1;
while (begin <= end) {
while (begin <= end && ((array[begin] & 1) == 1))
begin++;
while (begin <= end && ((array[end] & 1) == 0))
end--;
if (begin <= end) {
array[begin] = array[begin] ^ array[end];
array[end] = array[begin] ^ array[end];
array[begin] = array[begin] ^ array[end];
}
}
}
}
連結串列中倒數第k個結點
輸入一個連結串列,輸出該連結串列中倒數第k個結點。
快慢指標,讓快指標先走k步,然後慢指標開始走,若快指標走到末尾(為null),就是慢指標指向的就是倒數第k個結點
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
public class Solution {
public ListNode FindKthToTail(ListNode head, int k) {
ListNode front = head;
int i = 0;
for (; front != null && i < k; i++) {
front = front.next;
}
// 如果k大於連結串列的長度或者k小於0,返回null;
if (i != k)
return null;
ListNode behind = head;
while (front != null) {
front = front.next;
behind = behind.next;
}
// 若k等於0,則behind為null
return behind;
}
}
反轉連結串列
輸入一個連結串列,反轉連結串列後,輸出連結串列的所有元素。
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
public class Solution {
public ListNode ReverseList1(ListNode head) {
if (head == null)
return null;
// head為當前節點,如果當前節點為空的話,那就什麼也不做,直接返回null;
ListNode pre = null;
ListNode next = null;
// 當前節點是head,pre為當前節點的前一節點,next為當前節點的下一節點
// 需要pre和next的目的是讓當前節點從pre->head->next1->next2變成pre<-head next1->next2
// 即pre讓節點可以反轉所指方向,但反轉之後如果不用next節點儲存next1節點的話,此單鏈表就此斷開了
// 所以需要用到pre和next兩個節點
// 1->2->3->4->5
// 1<-2<-3 4->5
while (head != null) {
// 做迴圈,如果當前節點不為空的話,始終執行此迴圈,此迴圈的目的就是讓當前節點從指向next到指向pre
// 如此就可以做到反轉連結串列的效果
// 先用next儲存head的下一個節點的資訊,保證單鏈表不會因為失去head節點的原next節點而就此斷裂
next = head.next;
// 儲存完next,就可以讓head從指向next變成指向pre了,程式碼如下
head.next = pre;
// head指向pre後,就繼續依次反轉下一個節點
// 讓pre,head,next依次向後移動一個節點,繼續下一次的指標反轉
pre = head;
head = next;
}
// 如果head為null的時候,pre就為最後一個節點了,但是連結串列已經反轉完畢,pre就是反轉後連結串列的第一個節點
// 直接輸出pre就是我們想要得到的反轉後的連結串列
return pre;
}
}
public class Solution {
public ListNode ReverseList(ListNode head) {
if (head == null)
return null;
ListNode newhead = null;
ListNode pre = null;
ListNode pNext = null;
ListNode pNode = head;
while (pNode != null) {
pNext = pNode.next;
//最後一個頭結點賦值給newHead
if (pNext == null)
newhead = pNode;
pNode.next = pre;
pre = pNode;
pNode = pNext;
}
return newhead;
}
}
合併兩個排序的連結串列
輸入兩個單調遞增的連結串列,輸出兩個連結串列合成後的連結串列,當然我們需要合成後的連結串列滿足單調不減規則。
比較兩個連結串列的首結點,哪個小的的結點則合併到第三個連結串列尾結點,並向前移動一個結點。
步驟一結果會有一個連結串列先遍歷結束,或者沒有
第三個連結串列尾結點指向剩餘未遍歷結束的連結串列
返回第三個連結串列首結點
public ListNode Merge(ListNode list1, ListNode list2) {
if (list1 == null)
return list2;
if (list2 == null)
return list1;
//新建一個頭節點,用來存合併的連結串列。
ListNode newList = new ListNode(-1);
ListNode temp = newList;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
temp.next = list1;
list1 = list1.next;
temp = temp.next;
} else {
temp.next = list2;
list2 = list2.next;
temp = temp.next;
}
}
//把未結束的連結串列連線到合併後的連結串列尾部
if (list1 != null) {
temp.next = list1;
}
if (list2 != null) {
temp.next = list2;
}
return newList.next;
}
遞迴:
public ListNode Merge(ListNode list1, ListNode list2) {
if (list1 == null)
return list2;
if (list2 == null)
return list1;
if (list1.val <= list2.val) {
list1.next = Merge(list1.next, list2);
return list1;
} else {
list2.next = Merge(list1, list2.next);
return list2;
}
}
樹的子結構
輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構)
思路:參考劍指offer
1、首先設定標誌位result = false,因為一旦匹配成功result就設為true,剩下的程式碼不會執行,如果匹配不成功,預設返回false
2、遞迴思想,如果根節點相同則遞迴呼叫DoesTree1HaveTree2(),如果根節點不相同,則判斷tree1的左子樹和tree2是否相同,再判斷右子樹和tree2是否相同
3、注意null的條件,HasSubTree中,如果兩棵樹都不為空才進行判斷,DoesTree1HasTree2中,如果Tree2為空,則說明第二棵樹遍歷完了,即匹配成功,tree1為空有兩種情況:
(1)如果tree1為空&&tree2不為空說明不匹配,
(2) 如果tree1為空,tree2為空,說明匹配。
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
public class Solution {
public boolean HasSubtree(TreeNode root1, TreeNode root2) {
boolean result = false;
// 當Tree1和Tree2都不為零的時候,才進行比較。否則直接返回false
if (root2 != null && root1 != null) {
// 如果找到了對應Tree2的根節點的點
if (root1.val == root2.val) {
// 以這個根節點為為起點判斷是否包含Tree2
result = doesTree1HaveTree2(root1, root2);
}
// 如果找不到,那麼就再去root的左兒子當作起點,去判斷時候包含Tree2
if (!result) {
result = HasSubtree(root1.left, root2);
}
// 如果還找不到,那麼就再去root的右兒子當作起點,去判斷時候包含Tree2
if (!result) {
result = HasSubtree(root1.right, root2);
}
}
// 返回結果
return result;
}
public boolean doesTree1HaveTree2(TreeNode root1, TreeNode root2) {
// 如果Tree2已經遍歷完了都能對應的上,返回true
if (root2 == null) {
return true;
}
// 如果Tree2還沒有遍歷完,Tree1卻遍歷完了。返回false
if (root1 == null) {
return false;
}
// 如果其中有一個點沒有對應上,返回false
if (root1.val != root2.val) {
return false;
}
// 如果根節點對應的上,那麼就分別去子節點裡面匹配
return doesTree1HaveTree2(root1.left, root2.left) &&
doesTree1HaveTree2(root1.right, root2.right);
}
}
順時針列印矩陣
輸入一個矩陣,按照從外向裡以順時針的順序依次打印出每一個數字,例如,如果輸入如下矩陣: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 則依次打印出數字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
第一種方法:
public ArrayList<Integer> printMatrix(int[][] array) {
ArrayList<Integer> result = new ArrayList<Integer>();
if (array.length == 0)
return result;
int n = array.length, m = array[0].length;
if (m == 0)
return result;
// 此種方法關鍵點--求圈數
int layers = (Math.min(n, m) - 1) / 2 + 1;
// 要列印的圈數
for (int i = 0; i < layers; i++) {
// 列印每圈
for (int k = i; k < m - i; k++)
result.add(array[i][k]);// 左至右
for (int j = i + 1; j < n - i; j++)
result.add(array[j][m - i - 1]);// 右上至右下
// 注意k,j開始的下標
for (int k = m - i - 2; (k >= i) && (n - i - 1 != i); k--)
result.add(array[n - i - 1][k]);// 右至左
for (int j = n - i - 2; (j > i) && (m - i - 1 != i); j--)
result.add(array[j][i]);// 左下至左上
}
return result;
}
第二種方法:劍指offer
public ArrayList<Integer> printMatrix(int[][] matrix) {
// 得到矩陣的長度和寬度
int rows = matrix.length;
int columns = matrix[0].length;
ArrayList<Integer> list = new ArrayList<Integer>();
if (rows == 0 && columns == 0) {
return list;
}
// start標誌著每一次遍歷一圈的起始下標
int start = 0;
while (rows > start * 2 && columns > start * 2) {
printNumber(list, matrix, rows, columns, start);
start++;
}
return list;
}
public ArrayList<Integer> printNumber(ArrayList<Integer> list, int[][] matrix, int rows, int columns,
int start) {
// 先列印行,從左到右
for (int i = start; i <= columns - start - 1; i++) {
list.add(matrix[start][i]);
}
// 列印列,從上到下
for (int j = start + 1; j <= rows - start - 1; j++) {
list.add(matrix[j][columns - start - 1]);
}
// 列印行,從左到右列印
for (int m = columns - start - 2; m >= start && rows - start - 1 > start; m--) {
list.add(matrix[rows - start - 1][m]);
}
// 列印列,從下到上,columns-start-1>start 避免當只有一列時重新列印
for (int k = rows - start - 2; k >= start + 1 && columns - start - 1 > start; k--) {
list.add(matrix[k][start]);
}
return list;
}
}
二叉樹的映象
操作給定的二叉樹,將其變換為源二叉樹的映象。
二叉樹的映象定義:源二叉樹 8 / \ 6 10 / \ / \ 5 7 9 11 映象二叉樹 8 / \ 10 6 / \ / \ 11 9 7 5
public class Solution {
public void Mirror(TreeNode root) {
if (root == null) {
return;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
//左右子樹不空交換
if (node.left != null || node.right != null) {
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
}
if (node.left != null)
stack.push(node.left);
if (node.right != null)
stack.push(node.right);
}
}
}
遞迴,和棧相似,用棧操作也可以用遞迴操作
public class Solution {
// 二叉樹的映象如果二叉樹為空,直接返回。
// 如果二叉樹左右子數都為空,也直接返回。
// 把根節點的左右子樹交換。
// 如果根節點左子樹不為空,遞迴呼叫映象函式
// 如果根節點右子樹不為空,遞迴呼叫映象函式
public void Mirror(TreeNode root) {
if (root == null)
return;
if (root.left == null && root.right == null)
return;
TreeNode pTemp = root.left;
root.left = root.right;
root.right = pTemp;
if (root.left != null)
Mirror(root.left);
if (root.right != null)
Mirror(root.right);
}
}
包含min函式的棧
定義棧的資料結構,請在該型別中實現一個能夠得到棧最小元素的min函式。
/*
* 思路:用一個棧stack儲存資料,用另外一個棧min儲存依次入棧最小的數
* 比如,stack中依次入棧,5, 4, 3, 8, 10,11,12,1
* 則min依次入棧,5, 4, 3, 3, 3, 3, 3, 1
* 每次入棧的時候,如果入棧的元素比min中的棧頂元素小或等於則入棧,否則入stack的棧頂元素。
* 保持stack中和min中保持相同個數的元素
*/
Stack<Integer> stack = new Stack<>();
Stack<Integer> minStack = new Stack<>();
public void push(int node) {
stack.push(node);
// 如果min為空或者node比min棧中的元素小,則入min棧
if (minStack.size() == 0 || minStack.peek() > node) {
minStack.push(node);
}
// 否則把min棧中的頂部元素入棧
else minStack.push(minStack.peek());
}
public void pop() {
if (!stack.isEmpty()) {
stack.pop();
minStack.pop();
}
}
public int top() {
return stack.peek();
}
public int min() {
return minStack.peek();
}
棧的壓入、彈出序列
輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否為該棧的彈出順序。假設壓入棧的所有數字均不相等。例如序列1,2,3,4,5是某棧的壓入順序,序列4,5,3,2,1是該壓棧序列對應的一個彈出序列,但4,3,5,1,2就不可能是該壓棧序列的彈出序列。(注意:這兩個序列的長度是相等的)
借用一個輔助的棧,遍歷壓棧順序,先講第一個放入棧中,這裡是1,然後判斷棧頂元素是不是出棧順序的第一個元素,這裡是4,很顯然1≠4,所以我們繼續壓棧,直到相等以後開始出棧,出棧一個元素,則將出棧順序向後移動一位,直到不相等,這樣迴圈等壓棧順序遍歷完成,如果輔助棧還不為空,說明彈出序列不是該棧的彈出順序。
舉例:
入棧1,2,3,4,5
出棧4,5,3,2,1
首先1入輔助棧,此時棧頂1≠4,繼續入棧2
此時棧頂2≠4,繼續入棧3
此時棧頂3≠4,繼續入棧4
此時棧頂4=4,出棧4,彈出序列向後一位,此時為5,,輔助棧裡面是1,2,3
此時棧頂3≠5,繼續入棧5
此時棧頂5=5,出棧5,彈出序列向後一位,此時為3,,輔助棧裡面是1,2,3
….
依次執行,最後輔助棧為空。如果不為空說明彈出序列不是該棧的彈出順序。
import java.util.Stack;
public class Solution {
public boolean IsPopOrder(int[] pushA, int[] popA) {
if (pushA == null || popA == null || pushA.length == 0 || popA.length == 0)
return false;
// 用於標識彈出序列的位置
int index = 0;
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < pushA.length; i++) {
stack.push(pushA[i]);
// 如果棧不為空,且棧頂元素等於彈出序列
while (!stack.isEmpty()) {
if (stack.peek() != popA[index])
break;
stack.pop();
index++;
}
}
return stack.isEmpty();
}
}
二叉搜尋樹的後序遍歷序列
輸入一個整數陣列,判斷該陣列是不是某二叉搜尋樹的後序遍歷的結果。如果是則輸出Yes,否則輸出No。假設輸入的陣列的任意兩個數字都互不相同。
1、確定root;
2、遍歷序列(除去root結點),找到第一個大於root的位置,則該位置左邊為左子樹,右邊為右子樹;
3、遍歷右子樹,若發現有小於root的值,則直接返回false;
4、分別判斷左子樹和右子樹是否仍是二叉搜尋樹(即遞迴步驟1、2、3)。
public boolean VerifySquenceOfBST(int[] sequence) {
if (sequence == null || sequence.length == 0)
return false;
// 呼叫函式,java沒有指標,要用下標模擬指標,新建函式判斷
return IsTreeBST(sequence, 0, sequence.length - 1);
}
private boolean IsTreeBST(int[] sequence, int start, int end) {
// index是指示找到第一個大於左子樹的結點
int index = start;
for (; index < end; index++)
if (sequence[index] > sequence[end])
break;
// 若右子樹有小於跟結點的值,返回false
for (int i = index; i < end; i++)
if (sequence[i] < sequence[end])
return false;
return IsTreeBST(sequence, start, index - 1) && IsTreeBST(sequence, index, end - 1);
}
非遞迴:
// 非遞迴
// 非遞迴也是一個基於遞迴的思想:
// 左子樹一定比右子樹小,因此去掉根後,數字分為left,right兩部分,right部分的
// 最後一個數字是右子樹的根他也比左子樹所有值大,因此我們可以每次只看有子樹是否符合條件即可,
// 即使到達了左子樹左子樹也可以看出由左右子樹組成的樹還想右子樹那樣處理
// 對於左子樹回到了原問題,對於右子樹,左子樹的所有值都比右子樹的根小可以暫時把他看出右子樹的左子樹
// 只需看看右子樹的右子樹是否符合要求即可
public boolean VerifySquenceOfBST(int[] sequence) {
if (sequence == null || sequence.length == 0)
return false;
int len = sequence.length;
while (--len > 0) {
int i = 0;
while (sequence[i] < sequence[len])
i++;
while (sequence[i] > sequence[len])
i++;
if (i != len)
return false;
}
return true;
}
二叉樹中和為某一值的路徑
輸入一顆二叉樹和一個整數,打印出二叉樹中結點值的和為輸入整數的所有路徑。路徑定義為從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。
import java.util.ArrayList;
public class Solution {
private ArrayList<ArrayList<Integer>> listAll = new ArrayList<ArrayList<Integer>>();
private ArrayList<Integer> list = new ArrayList<Integer>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
if (root == null)
return listAll;
list.add(root.val);
target -= root.val;
if (target == 0 && root.left == null && root.right == null)
listAll.add(new ArrayList<Integer>(list));
/*
* if (root.left != null) { FindPath(root.left, target);
* list.remove(list.size() - 1); } if (root.right != null) {
* FindPath(root.right, target); list.remove(list.size() - 1); }
*/
// 繼續遍歷左右結點
FindPath(root.left, target);
FindPath(root.right, target);
// 在返回父節點之前,在路徑上刪除該結點
list.remove(list.size() - 1);
return listAll;
}
}
複雜連結串列的複製
輸入一個複雜連結串列(每個節點中有節點值,以及兩個指標,一個指向下一個節點,另一個特殊指標指向任意一個節點),返回結果為複製後複雜連結串列的head。(注意,輸出結果中請不要返回引數中的節點引用,否則判題程式會直接返回空)
/**解題思路:
* *1、遍歷連結串列,複製每個結點,如複製結點A得到A1,將結點A1插到結點A後面;
* *2、重新遍歷連結串列,複製老結點的隨機指標給新結點,
* 如A1.random = A.random.next;
* 3、拆分連結串列,將連結串列拆分為原連結串列和複製後的連結串列
* */
public RandomListNode Clone(RandomListNode pHead) {
if (pHead == null)
return null;
RandomListNode pCur = pHead;
// 複製next 如原來是A->B->C 變成A->A'->B->B'->C->C'
while (pCur != null) {
RandomListNode node = new RandomListNode(pCur.label);
node.next = pCur.next;
pCur.next = node;
pCur = node.next;
}
pCur = pHead;
// 複製random pCur是原來連結串列的結點 pCur.next是複製pCur的結點
while (pCur != null) {
if (pCur.random != null)
pCur.next.random = pCur.random.next;
pCur = pCur.next.next;
}
RandomListNode pCloneHead = null;
RandomListNode pCloneNode = null;
pCur = pHead;
// 初始化,讓pcur指向pCloneNode的下一個結點,避免空指標
if (pCur != null) {
pCloneHead = pCloneNode = pCur.next;
pCur.next = pCloneNode.next;
pCur = pCur.next;
// pCur = pCloneNode.next;
}
while (pCur != null) {
pCloneNode.next = pCur.next;
pCloneNode = pCloneNode.next;
pCur.next = pCloneNode.next;
pCur = pCur.next;
}
return pCloneHead;
}
連續子陣列的最大和
HZ偶爾會拿些專業問題來忽悠那些非計算機專業的同學。今天測試組開完會後,他又發話了:在古老的一維模式識別中,常常需要計算連續子向量的最大和,當向量全為正數的時候,問題很好解決。但是,如果向量中包含負數,是否應該包含某個負數,並期望旁邊的正數會彌補它呢?例如:{6,-3,-2,7,-15,1,2,2},連續子向量的最大和為8(從第0個開始,到第3個為止)。你會不會被他忽悠住?(子向量的長度至少是1)
/*
演算法時間複雜度O(n)
用total記錄累計值,maxSum記錄和最大
基於思想:對於一個數A,若是A的左邊累計數非負,那麼加上A能使得值不小於A,認為累計值對
整體和是有貢獻的。如果前幾項累計值負數,則認為有害於總和,total記錄當前值。
此時 若和大於maxSum 則用maxSum記錄下來
*/
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
if (array.length == 0)
return 0;
else {
int total = array[0], maxSum = array[0];
for (int i = 1; i < array.length; i++) {
if (total >= 0)
total += array[i];
else
total = array[i];
if (total > maxSum)
maxSum = total;
}
return maxSum;
}
}
}
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
if (array.length == 0)
return 0;
int res = Integer.MIN_VALUE; // 記錄當前所有子陣列的和的最大值
int tempMax = 0; // 包含array[i]的連續陣列最大值
for (int i = 0; i < array.length; i++) {
tempMax = Math.max(tempMax + array[i], array[i]);
res = Math.max(tempMax, res);
}
return res;
}
}
字串的排列
輸入一個字串,按字典序打印出該字串中字元的所有排列。例如輸入字串abc, 則打印出由字元a,b,c所能排列出來的所有字串abc,acb,bac,bca,cab和cba。輸入一個字串,長度不超過9(可能有字元重複),字元只包括大小寫字母。
/**
* 對於無重複值的情況** 固定第一個字元,遞迴取得首位後面的各種字串組合;*
* 再把第一個字元與後面每一個字元交換,並同樣遞迴獲得首位後面的字串組合;
* *遞迴的出口,就是隻剩一個字元的時候,遞迴的迴圈過程,就是從每個子串的第二個字元開始依次與第一個字元交換,然後繼續處理子串。* *
* 假如有重複值呢?*
* *由於全排列就是從第一個數字起,每個數分別與它後面的數字交換,我們先嚐試加個這樣的判斷——如果一個數與後面的數字相同那麼這兩個數就不交換了。 *
* 例如abb,第一個數與後面兩個數交換得bab,bba。然後abb中第二個數和第三個數相同,就不用交換了。* 但是對bab,第二個數和第三個數不
* 同,則需要交換,得到bba。* 由於這裡的bba和開始第一個數與第三個數交換的結果相同了,因此這個方法不行。**
* 換種思維,對abb,第一個數a與第二個數b交換得到bab,然後考慮第一個數與第三個數交換,此時由於第三個數等於第二個數,*
* 所以第一個數就不再用與第三個數交換了。再考慮bab,它的第二個數與第三個數交換可以解決bba。此時全排列生成完畢!** * @param
*/
public ArrayList<String> Permutation(String str) {
ArrayList<String> list = new ArrayList<String>();
if (str != null && str.length() > 0) {
PermutationHelper(str.toCharArray(), 0, list);
// 按照字典序輸出
Collections.sort(list);
}
return list;
}
private void PermutationHelper(char[] chars, int i, ArrayList<String> list) {
if (i == chars.length) {
list.add(String.valueOf(chars));
return;
}
// 用於結果去重,可以用list
Set<Character> charSet = new HashSet<Character>();
for (int j = i; j < chars.length; ++j) {
if (!charSet.contains(chars[j])) {
charSet.add(chars[j]);
swap(chars, i, j);
PermutationHelper(chars, i + 1, list);
swap(chars, i, j);
}
}
}
private void swap(char[] cs, int i, int j) {
char temp = cs[i];
cs[i] = cs[j];
cs[j] = temp;
}
二叉樹與雙向連結串列
輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列。要求不能建立任何新的結點,只能調整樹中結點指標的指向。
// 1.核心是中序遍歷的非遞迴演算法。
//2.修改當前遍歷節點與前一遍歷節點的指標指向。
public TreeNode ConvertBSTToBiList(TreeNode root)
{
if (root == null)
return null;
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode p = root;
// 用於儲存中序遍歷序列的上一節點
TreeNode pre = null;
boolean isFirst = true;
while (p != null || !stack.isEmpty()) {
while (p != null) {
stack.push(p);
p = p.left;
}
p = stack.pop();
if (isFirst) {
// 將中序遍歷序列中的第一個節點記為root
root = p;
pre = root;
isFirst = false;
} else {
pre.right = p;
p.left = pre;
pre = p;
}
p = p.right;
}
return root;
}
// 直接用中序遍歷
TreeNode head = null;
TreeNode realHead = null;
public TreeNode Convert(TreeNode pRootOfTree) {
ConvertSub(pRootOfTree);
return realHead;
}
private void ConvertSub(TreeNode pRootOfTree) {
if (pRootOfTree == null)
return;
ConvertSub(pRootOfTree.left);
if (head == null) {
head = pRootOfTree;
realHead = pRootOfTree;
} else {
head.right = pRootOfTree;
pRootOfTree.left = head;
head = pRootOfTree;
}
ConvertSub(pRootOfTree.right);
}
字串的組合
給一個字串,比如ABC, 把所有的組合,即:A, B, C, AB, AC, BC, ABC, 都找出來。
假設我們想在長度為n的字串中求m個字元的組合。我們先從頭掃描字串的第一個字元。針對第一個字元,我們有兩種選擇:一是把這個字元放到組合中去,接下來我們需要在剩下的n-1個字元中選取m-1個字元;二是不把這個字元放到組合中去,接下來我們需要在剩下的n-1個字元中選擇m個字元。這兩種選擇都很容易用遞迴實現。
import java.util.*;
public class Solution {
static List<String> retList = new ArrayList<>();
public static void combiantion(char chs[]) {
if (chs.length == 0)
return;
Stack<Character> stack = new Stack<Character>();
for (int i = 1; i <= chs.length; i++) {
combine(chs, 0, i, stack);
}
}
private static void combine(char[] chs, int begin, int number, Stack<Character> stack) {
if (number == 0 && !retList.contains(stack.toString())) {
retList.add(stack.toString());
return;
}
if (begin == chs.length) {
return;
}
stack.push(chs[begin]);
combine(chs, begin + 1, number - 1, stack);
stack.pop();
combine(chs, begin + 1, number, stack);
}
}
陣列中出現次數超過一半的數字
陣列中有一個數字出現的次數超過陣列長度的一半,請找出這個數字。例如輸入一個長度為9的陣列{1,2,3,2,2,2,5,4,2}。由於數字2在陣列中出現了5次,超過陣列長度的一半,因此輸出2。如果不存在則輸出0。
/*
* 採用陣地攻守的思想:第一個數字作為第一個士兵,守陣地;times = 1;
* 遇到相同元素,times++;遇到不相同元素,即為敵人,同歸於盡,times--;
* 當遇到times為0的情況,又以新的i值作為守陣地的士兵,繼續下去, 到最後還留在陣地上的士兵,有可能是主元素。
* 再加一次迴圈,記錄這個士兵的個數看是否大於陣列一般即可。
*/
public int MoreThanHalfNum_Solution(int[] array) {
if (array.length <= 0)
return 0;
int res = array[0];
int times = 1;
for (int i = 1; i < array.length; i++) {
if (times == 0) {
res = array[i];
times = 1;
} else if (array[i] == res)
times++;
else
times--;
}
times = 0;
for (int i = 0; i < array.length; i++)
if (res == array[i])
times++;
if (times * 2 > array.length)
return res;
else
return 0;
}
// 陣列排序後,如果符合條件的數存在,則一定是陣列中間那個數
// 要注意此數的出現的次數大於陣列長度一半才可以
// 涉及到快排sort,其時間複雜度為O(NlogN)
public int MoreThanHalfNum_Solution(int[] array) {
Arrays.sort(array);
int count = 0;
int len = array.length;
// 統計array[len / 2]出現的次數
for (int i = 0; i < len; i++)
if (array[i] == array[len / 2])
count++;
return count > len / 2 ? array[len / 2] : 0;
}
//基於快速排序的思想
public int MoreThanHalfNum_Solution(int[] array) {
if (array.length <= 0)
return 0;
int mid = array.length >> 1;
int start = 0;
int end = array.length - 1;
int index = Partition(array, start, end);
while (index != mid) {
if (index > mid)
index = Partition(array, start, index - 1);
else
index = Partition(array, index + 1, end);
}
int res = array[mid];
int times = 0;
for (int i = 0; i < array.length; i++)
if (res == array[i])
times++;
if (times * 2 > array.length)
return res;
else
return 0;
}
private int Partition(int[] arr, int start, int end) {
// arr[start]為挖的第一個坑
int key = arr[start];
while (start < end) {
while (arr[end] >= key && end > start)
end--;
arr[start] = arr[end];
while (arr[start] <= key && end > start)
start++;
arr[end] = arr[start];
}
arr[start] = key;
return start;
}
把陣列排成最小的數
輸入一個正整數陣列,把數組裡所有數字拼接起來排成一個數,列印能拼接出的所有數字中最小的一個。例如輸入陣列{3,32,321},則打印出這三個數字能排成的最小數字為321323。
解題思路: 先將整型陣列轉換成String陣列,然後將String陣列排序,最後將排好序的字串陣列拼接出來。關鍵就是制定排序規則。*排序規則如下:* 若ab > ba 則 a > b,* 若ab < ba 則 a < b,* 若ab = ba 則 a = b;
* 解釋說明:* 比如 "3" < "31"但是 "331" > "313",所以要將二者拼接起來進行比較
import java.util.Arrays;
import java.util.Comparator;
public class Solution {
// 使用String.valueOf(int)來變換整型到字串
// 使用StringBuilder來拼接字串
public String PrintMinNumber(int[] numbers) {
if (numbers == null || numbers.length == 0)
return "";
int len = numbers.length;
String[] str = new String[len];
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) {
str[i] = String.valueOf(numbers[i]);
}
// comparator 外部比較器
Arrays.sort(str, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
String c1 = s1 + s2;
String c2 = s2 + s1;
return c1.compareTo(c2);
}
});
for (int i = 0; i < len; i++) {
sb.append(str[i]);
}
return sb.toString();
}
}
附錄String.compareTo原始碼 基於JDK 1.7
// jdk1.7 實現value為String封裝陣列
private final char value[];
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
// 獲取到兩個字串的較短的長度
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
// 如果兩個字元的ASC不相同,則直接返回
if (c1 != c2) {
return c1 - c2;
}
k++;
}
// 如果都一樣,返回兩個字串的長度查
return len1 - len2;
}
第一個只出現一次的字元
在一個字串(1<=字串長度<=10000,全部由字母組成)中找到第一個只出現一次的字元,並返回它的位置
import java.util.LinkedHashMap;
public class Solution {
// 使用map儲存,key為字元,value為出現的次數
//掃描map,取第一個value為1的key,返回下標
public int FirstNotRepeatingChar(String str) {
// 使用 linkedhashmap保持有序
LinkedHashMap<Character, Integer> map = new LinkedHashMap<>();
for (int i = 0; i < str.length(); i++)
if (!map.containsKey(str.charAt(i)))
map.put(str.charAt(i), 1);
else
map.put(str.charAt(i), map.get(str.charAt(i)) + 1);
for (int index = 0; index < str.length(); index++)
if (map.get(str.charAt(index)) == 1)
return index;
return -1;
}
陣列中的逆序對
在陣列中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個陣列中的逆序對的總數P。並將P對1000000007取模的結果輸出。
即輸出P%1000000007
輸入描述:
題目保證輸入的陣列中沒有的相同的數字資料範圍:
對於%50的資料,size<=10^4
對於%75的資料,size<=10^5
對於%100的資料,size<=2*10^5
輸入例子:
1,2,3,4,5,6,7,0
輸出例子:
7
使用歸併排序
public class Solution {
public int InversePairs(int[] array) {
if (array == null || array.length == 0) {
return 0;
}
int[] copy = new int[array.length];
// for複製???可省
for (int i = 0; i < array.length; i++) {
copy[i] = array[i];
}
// 數值過大求餘
int count = InversePairsCore(array, copy, 0, array.length - 1);
return count;
}
private int InversePairsCore(int[] array, int[] copy, int low, int high) {
if (low == high) {
return 0;
}
// mid屬於前半部分最後一個數字
int mid = (low + high) >> 1;
int leftCount = InversePairsCore(array, copy, low, mid) % 1000000007;
int rightCount = InversePairsCore(array, copy, mid + 1, high) % 1000000007;
int count = 0;
// i初始前半部分最後一個數字
int i = mid;
// j初始後半部分最後一個數字
int j = high;
// indexCopy記錄copy陣列的下標
int locCopy = high;
while (i >= low && j > mid) {
if (array[i] > array[j]) {
// j-mid~j的數都小於等於j的(排序),j的數字小於i
count += j - mid;
copy[locCopy--] = array[i--];
if (count >= 1000000007)// 數值過大求餘
count %= 1000000007;
} else {
copy[locCopy--] = array[j--];
}
}
for (; i >= low; i--)
{
copy[locCopy--] = array[i];
}
// 剩餘部分依次放入臨時陣列
for (; j > mid; j--) {
copy[locCopy--] = array[j];
}
for (int s = low; s <= high; s++) {
array[s] = copy[s];
}
return (leftCount + rightCount + count) % 1000000007;
}
}
兩個連結串列的第一個公共結點
輸入兩個連結串列,找出它們的第一個公共結點。
找出2個連結串列的長度,然後讓長的先走兩個連結串列的長度差,然後再一起走(因為2個連結串列用公共的尾部)
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
int len1 = findListLenth(pHead1);
int len2 = findListLenth(pHead2);
ListNode current1 = pHead1;
ListNode current2 = pHead2;
// 先在長連結串列上走上幾步,在同時在兩個連結串列上遍歷
if (len1 - len2 > 0)
current1 = walkStep(current1, len1 - len2);
else
current2 = walkStep(current2, len2 - len1);
while (current1 != null && current2 != null) {
if (current1 == current2)
return current1;
current1 = current1.next;
current2 = current2.next;
}
return null;
}
private ListNode walkStep(ListNode cur, int step) {
// 從step~1
while (step-- > 0)
cur = cur.next;
return cur;
}
// 計算連結串列長度
private int findListLenth(ListNode head) {
if (head == null)
return 0;
ListNode cur = head;
int length = 0;
while (cur != null) {
length++;
cur = cur.next;
}
return length;
}
}
數字在排序陣列中出現的次數
統計一個數字在排序陣列中出現的次數。(二分查詢找到第一個和最後一個,排序,首先想到二分查詢)
public class Solution {
public int GetNumberOfK(int[] array, int k) {
int first, last;
first = getFirstKIndex(array, 0, array.length - 1, k);
last = getLastIndex(array, 0, array.length - 1, k);
if (first > -1 && last > -1)
return last - first + 1;
return 0;
}
private int getLastIndex(int[] array, int start, int end, int k) {
int mid;
// 一定要等有=!!!!!!
while (start <= end) {
mid = (start + end) / 2;
if (k == array[mid]) {
// k在中間或者結尾,找到
if (mid == end || array[mid + 1] != k) {
return mid;
} else {
start = mid + 1;
}
} else if (k < array[mid]) {
end = mid - 1;
} else {
start = mid + 1;
}
}
return -1;
}
private int getFirstKIndex(int[] array, int start, int end, int k) {
int mid;
// 一定要等有=!!!!!!
while (start <= end) {
mid = (start + end) / 2;
if (k == array[mid]) {
if (mid == start || array[mid - 1] != k) {
return mid;
} else {
end = mid - 1;
}
} else if (k < array[mid]) {
end = mid - 1;
} else {
start = mid + 1;
}
}
return -1;
}
}
二叉樹的深度及判斷是否為平衡二叉樹
輸入一棵二叉樹,1,判斷該二叉樹是否是平衡二叉樹,2,計算深度
public class Solution {
// 注意使用全域性變數
boolean isBalance = true;
public boolean IsBalanced_Solution(TreeNode root) {
lengthOfTree(root);
return isBalance;
}
private int lengthOfTree(TreeNode root) {
if (root == null)
return 0;
int left = lengthOfTree(root.left);
int right = lengthOfTree(root.right);
if (Math.abs(left - right) > 1)
isBalance = false;
return Math.max(left, right) + 1;
}
// 每個結點被遍歷多次的解法
public boolean IsBalancedTree(TreeNode root) {
// 空樹為true
if (root == null)
return true;
int leftDepth = TreeDepth(root.left);
int rightDepth = TreeDepth(root.right);
if (Math.abs(leftDepth - rightDepth) > 1)
return false;
return IsBalancedTree(root.left)
&& IsBalancedTree(root.right);
}
// 計算樹的深度,注意加1
public int TreeDepth(TreeNode root) {
if (root == null)
return 0;
// 注意最後加1,因為左右子樹的深度大的+根節點的深度1
return Math.max(TreeDepth(root.left),
TreeDepth(root.right)) + 1;
}
}
陣列中只出現一次的數字
一個整型數組裡除了兩個數字之外,其他的數字都出現了兩次。請寫程式找出這兩個只出現一次的數字。
使用map
// num1,num2分別為長度為1的陣列。傳出引數
// 將num1[0],num2[0]設定為返回結果
// 因為題目要求最多出現兩次,可以用list,如果存在就刪除,最後list剩下的就是兩個數字。
// 但是要判斷list的長度必須大於1,才符合要求、
public void FindNumsAppearOnce1(int[] array, int num1[], int num2[]) {
java.util.HashMap<Integer, Integer> map = new java.util.HashMap<>();
for (int num : array) {
if (!map.containsKey(num))
map.put(num, 1);
else
map.put(num, map.get(num) + 1);
}
int i = 0;
for (int num : array) {
if (map.get(num) == 1) {
if (i == 1) {
num2[0] = num;
break;
} else {
num1[0] = num;
i++;
}
}
}
}
位運算中異或的性質:兩個相同數字異或=0,一個數和0異或還是它本身。當只有一個數出現一次時,我們把陣列中所有的數,依次異或運算,最後剩下的就是落單的數,因為成對兒出現的都抵消了。
依照這個思路,我們來看兩個數(我們假設是AB)出現一次的陣列。我們首先還是先異或,剩下的數字肯定是A、B異或的結果,這個結果的二進位制中的1,表現的是A和B的不同的位。我們就取第一個1所在的位數,假設是第3位,接著把原陣列分成兩組,分組標準是第3位是否為1。如此,相同的數肯定在一個組,因為相同數字所有位都相同,而不同的數,肯定不在一組。然後把這兩個組按照最開始的思路,依次異或,剩餘的兩個結果就是這兩個只出現一次的數字。
使用異或
public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) {
if (array == null || array.length < 2)
return;
int bitResult = 0;
for (int i = 0; i < array.length; i++)
bitResult ^= array[i];
int index = findFirstBitIs1(bitResult);
for (int i = 0; i < array.length; i++)
if (isBit1(array[i], index))
num1[0] ^= array[i];
else
num2[0] ^= array[i];
}
// 判斷target的右側index位是否為1
private boolean isBit1(int target, int index) {
return ((target >> index) & 1) == 1;
}
// 查詢num右側第一個1的下標
private int findFirstBitIs1(int num) {
int indexBit = 0;
// 注意判斷位數合法性
while ((num & 1) == 0 && indexBit < 32) {
indexBit++;
num >>= 1;
}
return indexBit;
}
和為S的連續正數序列
小明很喜歡數學,有一天他在做數學作業時,要求計算出9~16的和,他馬上就寫出了正確答案是100。但是他並不滿足於此,他在想究竟有多少種連續的正數序列的和為100(至少包括兩個數)。沒多久,他就得到另一組連續正數和為100的序列:18,19,20,21,22。現在把問題交給你,你能不能也很快的找出所有和為S的連續正數序列? Good Luck!輸出描述:
輸出所有和為S的連續正數序列。序列內按照從小至大的順序,序列間按照開始數字從小到大的順序
import java.util.ArrayList;
/*
*初始化small=1,big=2;
*small到big序列和小於sum,big++;大於sum,small++;
*當small增加到(1+sum)/2是停止
*/
public class Solution {
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
// 不重複,最少有2個數字
if (sum < 3)
return ret;
// 初始化small=1,big=2;
int small = 1;
int big = 2;
while (small != (sum + 1) / 2) {
int curSum = sumOfList(small, big);
if (curSum == sum) {
// 注意此時list不能使用全域性,並clear,因為是引用!只有最後一個結果!!!
ArrayList<Integer> list = new ArrayList<>();
for (int i = small; i <= big; i++)
list.add(i);
ret.add(list);
big++;
} else if (curSum < sum)
big++;
else
small++;
}
return ret;
}
// 計算list內數字之和
private int sumOfList(int small, int big) {
int sum = 0;
for (int i = small; i <= big; i++)
sum += i;
return sum;
}
}
和為S的兩個數字
輸入一個遞增排序的陣列和一個數字S,在陣列中查詢兩個數,是的他們的和正好是S,如果有多對數字的和等於S,輸出兩個數的乘積最小的。
輸出描述:
對應每個測試案例,輸出兩個