1.旋轉數組最小數字
把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉。 輸入一個非減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1。 NOTE:給出的所有元素都大於0,若數組大小爲0,請返回0。
- 思路:
暴力破解:
循環遍歷,找到臨界點
1)即left >= right,則right爲返回值
2)從頭到尾遞增,即從前到後都沒找到臨界點的值,說明該數組爲標準順序遞增數組,則最小元素array[0]
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
int min = 0;
if(array.length == 0){
return 0;
}
if(array.length == 1){
return array[0];
}
for(int i = 0; i < array.length-1;i++){
//此處不能取 >=,遞增的過程中也可能存在相等的情景
if(array[i]> array[i+1]){
return array[i+1];
} else {
//遞增數組
if(i==array.length-2){
return array[0];
}
}
}
return min ;
}
}
二分查找:
high初始值:數組最後一個元素,low初始值:數組第一個元素,先取mid值,mid = low + (hight - low)/2,從low --> high循環判斷
mid > high,說明,臨界點一定在mid的右邊
mid = hign,無法確定是在左邊還是右邊,只能全部遍歷 high –
mid < hign, 說明mid到high之間爲遞增數組,將high置換爲mid
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
//二分查找
int low = 0 ,high = array.length -1 ;
while(low < high){
int mid = low + (high - low)/2;
if(array[mid] > array[high]){
//臨界點一定在mid的右邊
low = mid +1;
}else if(array[mid] == array[high]){
//無法確定是在左邊還是在右邊,依次遍歷
high -- ;
}else{
//array[mid] < array [high]
//mid < hign, 說明mid到high之間爲遞增數組,將high置換爲mid
high = mid;
}
}
return array[low];
}
}
2.斐波那契數列
大家都知道斐波那契數列,現在要求輸入一個整數n,請你輸出斐波那契數列的第n項(從0開始,第0項爲0)。
n<=39
- 思路:
-
f(n) = f(n-1)+f(n-2);
public class Solution {
public int Fibonacci(int n) {
//f(n) = f(n-1)+f(n-2);
if(n<=0){
return 0;
}else if(n<3){
return 1;
}
int numFn = 0 , numFn1 = 1 ,numFn2 = 1;
for(int i = 2 ;i<n ; i++){
numFn = numFn1 + numFn2;
numFn1 = numFn2;
numFn2 = numFn;
}
return numFn;
}
}
3.青蛙跳臺階
一隻青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法(先後次序不同算不同的結果)。
- 思路:
-
跳臺階的本質問題是:斐波那契數列,假如有6個臺階,跳到第6個臺階有2種可能,從第5個臺階跳一步到達,或從第4個臺階跳2步到達,故跳到第6個臺階的可能性,其實是跳到第5個臺階和跳到第4個臺階的可能性之和,以此類推,F(n) = F(n-1) + F(n-2)
public class Solution {
public int JumpFloor(int n) {
//跳臺階問題的本質是斐波那契數列,假如有6個臺階,最後一步到第6個臺階,只有2種可能,
//1)從第5個臺階跳一步到第6個臺階,
//2)從第4個臺階跳2步到第6個臺階
if(n<=0){
return 0;
}else if(n ==1){
return 1;
}else if(n == 2){
return 2;
}
int numFn = 0,numFn1 = 1,numFn2=2;
for(int i =2 ;i<n;i++){
numFn = numFn1 + numFn2;
numFn1 = numFn2;
numFn2 = numFn;
}
return numFn;
}
}
4.變態青蛙跳臺階
一隻青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
- 思路:
找規律:
最後一跳到第n階的可能是,跳1、2、4、5…n
即F(n) = F(n-1)+F(n-2)+…+F(1)1
F(n-1) = F(n-2) + F(n-3) + …+F(1)1
則 F(n) - F(n-1) = F(n-1);
故 F(n) = 2*F(n-1)
數學之美:
2)除了最後一個臺階,每一個臺階都有跳與跳2種可能,最後一個臺階必須跳,故2*(n-1)
public class Solution {
public int JumpFloorII(int n) {
//最後一跳到第n階的可能是,跳1、2、4、5...n
//即F(n) = F(n-1)+F(n-2)+....+F1
//F(n-1) = F(n-2) + F(n-3) + ....+F1
//則 F(n) - F(n-1) = F(n-1);
//故 F(n) = 2*F(n-1)
if(n ==0){
return 0;
}else if(n == 1){
return 1;
}
return 2*JumpFloorII(n-1);
}
}
5.矩形覆蓋
我們可以用21的小矩形橫着或者豎着去覆蓋更大的矩形。請問用n個21的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?
- 思路:
歸納法:當n=1、2、3、4分別對應的結果爲1、2、3、5、8,其本質上是斐波那契數列
public class Solution {
public int RectCover(int n) {
//按照歸納法,其本質上還是斐波那契數列
if(n<=0){
return 0;
}else if(n ==1){
return 1;
}else if(n == 2){
return 2;
}
return RectCover(n-1)+RectCover(n-2);
}
}
6.鏈表中倒數第k個節點
輸入一個鏈表,輸出該鏈表中倒數第k個結點。
- 思路:
2個指針,一個用於計數,一個用於遍歷
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
if(k <=0 || null == head){
return null ;
}
ListNode list = head;
int num = 1;
while(list.next!=null){
num ++ ;
list = list.next;
}
if(k > num){
return null;
}
ListNode node = head;
for(int i =0;i<num-k;i++){
node = node.next;
}
return node ;
}
}
時間複雜度O(n),一次遍歷即可
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
ListNode pre=null,p=null;
//兩個指針都指向頭結點
p=head;
pre=head;
//記錄k值
int a=k;
//記錄節點的個數
int count=0;
//p指針先跑,並且記錄節點數,當p指針跑了k-1個節點後,pre指針開始跑,
//當p指針跑到最後時,pre所指指針就是倒數第k個節點
while(p!=null){
p=p.next;
count++;
if(k<1){
pre=pre.next;
}
k--;
}
//如果節點個數小於所求的倒數第k個節點,則返回空
if(count<a) return null;
return pre;
}
}
7.反轉鏈表
輸入一個鏈表,反轉鏈表後,輸出新鏈表的表頭。
- 思路:
遍歷鏈表找到頭節點,確定當前節點、前一個節點、後一個節點之間的關係
當前節點指遍歷的當前節點,將當前節點的下一個節點作爲前一個節點,將前一個節點作爲當前節點的下一個節點
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
if(null == head){
return null;
}
//當前節點前一個元素
ListNode pre = null;
//當前節點
ListNode pnode = head;
//反轉的表頭
ListNode newHead = null;
while(null != pnode){
ListNode next = pnode.next;
if(null == next){
//設置表頭
newHead = pnode;
}
//反轉
pnode.next = pre;
pre = pnode;
pnode = next;
}
return newHead;
}
}
8.合併兩個排序鏈表
輸入兩個單調遞增的鏈表,輸出兩個鏈表合成後的鏈表,當然我們需要合成後的鏈表滿足單調不減規則。
- 思路:
指定頭節點,存儲合併的元素,
遍歷比較兩個鏈表的節點值,根據比較結果指定新的頭節點,及頭節點的next節點,遞歸執行
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(null == list1){
return list2;
}else if(null == list2){
return list1;
}
ListNode pHead = null;
if(list1.val <= list2.val){
pHead = list1;
pHead.next = Merge(list1.next,list2);
}else{
pHead = list2;
pHead.next = Merge(list1,list2.next);
}
return pHead;
}
}
9.樹的子結構
輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構)
- 思路:
先找到根節點,然後依次遍歷左右樹,對比值是否相等
/**
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) {
if(null == root1 || null == root2){
return false;
}
//先找到根節點
boolean flag = false;
if(root1.val == root2.val){
flag = isNode1HasNode2(root1,root2);
}
//依次遍歷根節點的左樹和右樹,對比是否一致
if(!flag){
flag = HasSubtree(root1.left,root2);
}
if(!flag){
flag = HasSubtree(root1.right,root2);
}
return flag;
}
public boolean isNode1HasNode2(TreeNode node1,TreeNode node2){
//已找到根節點
//樹1遍歷完了而樹2卻沒遍歷完
//注意判斷先後順序,先判斷node2是否爲null再判斷node1
if(null == node2){
return true;
}
if(null == node1){
return false;
}
if(node1.val != node2.val){
return false;
}
return isNode1HasNode2(node1.left,node2.left) && isNode1HasNode2(node1.right,node2.right);
}
}
10.二叉樹鏡像
操作給定的二叉樹,將其變換爲源二叉樹的鏡像
- 思路:
遞歸:依次交換左右樹的節點
源二叉樹
8
/ \
6 10
/ \ / \
5 7 9 11
鏡像二叉樹
8
/ \
10 6
/ \ / \
11 9 7 5
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public void Mirror(TreeNode root) {
if(null == root){
return;
}
//先交換左右樹
swap(root);
//交換左樹
if(null!=root.left){
Mirror(root.left);
}
//交換右樹
if(null!=root.right){
Mirror(root.right);
}
}
public void swap(TreeNode node){
if(null == node){
return;
}
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
}
}
11.包含min函數的棧
定義棧的數據結構,請在該類型中實現一個能夠得到棧中所含最小元素的min函數(時間複雜度應爲O(1))
- 思路:
定義輔助棧,棧中存放的元素是存入的值從棧頂到棧底是依次遞減的(輔助棧中的元素個數<=主棧中的元素)
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
//創建輔助棧,其中棧頂的元素一定是最小的
public void push(int node) {
stack1.push(node);
if(stack2.empty()){
stack2.push(node);
}else if(node <= stack2.peek()){
stack2.push(node);
}
}
public void pop() {
int value = stack1.pop();
if(value == stack2.peek()){
stack2.pop();
}
}
public int top() {
return stack1.peek();
}
public int min() {
return stack2.peek();
}
}
12.棧的壓入、彈出序列
棧的彈出順序。假設壓入棧的所有數字均不相等。例如序列1,2,3,4,5是某棧的壓入順序,序列4,5,3,2,1是該壓棧序列對應的一個彈出序列,但4,3,5,1,2就不可能是該壓棧序列的彈出序列。(注意:這兩個序列的長度是相等的)
- 思路:
定義輔助棧,遍歷將壓入棧的元素壓入
定義彈出序列的下標,用於遍歷彈出序列時,計算數組下標(從0開始,匹配到之後,執行++操作)
壓入棧的數組,持續將節點壓入輔助棧中,每次輔助棧的節點值和彈出序列的的彈出序下標元素對比,相同的彈出
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
if(null == pushA || null == popA){
return false;
}
//定義輔助棧,用於對比push棧
Stack<Integer> stack = new Stack<>();
//定義彈出下標
int popIndex = 0;
for(int i =0 ;i< pushA.length;i++){
stack.push(pushA[i]);
while(!stack.empty() && stack.peek()== popA[popIndex]){
stack.pop();
popIndex ++;
}
}
return stack.empty();
}
}
13.從上往下打印二叉樹
從上往下打印出二叉樹的每個節點,同層節點從左至右打印
- 思路:
利用queue先進先出,將自上而下的節點放進去,依次遍歷,其中list.remove(0)可以模仿queue的先進先出的特性
import java.util.ArrayList;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> list = new ArrayList<>();
if(null == root){
return list;
}
//queue隊列先進先出
ArrayList<TreeNode> queue = new ArrayList<>();
queue.add(root);
while(queue.size()>0){
TreeNode temp = queue.remove(0);
list.add(temp.val);
if(null != temp.left){
queue.add(temp.left);
}
if(null != temp.right){
queue.add(temp.right);
}
}
return list;
}}
14.二叉搜索樹後序遍歷序列
輸入一個整數數組,判斷該數組是不是某二叉搜索樹的後序遍歷的結果。如果是則輸出Yes,否則輸出No。假設輸入的數組的任意兩個數字都互不相同
- 思路:
二叉樹後序遍歷:左->右->根,
最後一個節點爲根節點,剩下的數組由左子樹和右子樹組成
找到第一個比根節點大的數字,則該數字前的是左子樹,該數字及其之後的數組爲右子樹
左子樹的所有元素比根節點小,右子樹的所有元素比根節點大
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
if(null == sequence || sequence.length ==0){
return false;
}
return judge(sequence,0,sequence.length-1);
}
public boolean judge(int[] seq,int start,int end){
//已經遍歷完成了
if(end <= start){
return true;
}
int i = start;
//尋找臨界點
for(;i< end ;i++){
if(seq[i]> seq[end]){
break;
}
}
for(int j=i;j<end;j++){
if(seq[j]<seq[end]){
return false;
}
}
return judge(seq,start,i-1) && judge(seq,i,end-1);
}
}
15.二叉樹中和爲某一值的路徑
輸入一顆二叉樹的跟節點和一個整數,打印出二叉樹中結點值的和爲輸入整數的所有路徑。路徑定義爲從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。(注意: 在返回值的list中,數組長度大的數組靠前)
- 思路:
遞歸先序遍歷樹, 把結點加入路徑
若該結點是葉子結點則比較當前路徑和是否等於期待和
彈出結點,每一輪遞歸返回到父結點時,當前路徑也應該回退一個結點
import java.util.ArrayList;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
private ArrayList<ArrayList<Integer>> listAll = new ArrayList<>();
private ArrayList<Integer> list = new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
if(null == root || target<0){
return listAll;
}
dfsFindPath(root,target);
return listAll;
}
public void dfsFindPath(TreeNode root,int target){
if(null == root){
return;
}
list.add(root.val);
//滿足路徑之後爲指定值,且到達葉子結點
if(target == root.val && null == root.left && null == root.right){
listAll.add(new ArrayList<Integer>(list));
}
if(null != root.left){
dfsFindPath(root.left,target - root.val);
}
if(null != root.right){
dfsFindPath(root.right,target - root.val );
}
// dfs深度遍歷,當每一次走過一個路徑計算過之後,都需要逐級回退,有點類似於棧的先進後出,
//如一條路徑分別是A->B->C其中,走過的dfsFindPath順序應該是dfsFindPathA、dfsFindPathB、dfsFindPathC,
//如果需要回到根節點,則每一個dfs方法都需要將當前節點進行回退,
list.remove(list.size()-1);
}
}