1.二維數組中的查找
題目描述
在一個二維數組中(每個一維數組的長度相同),每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。
public class Solution {
public boolean Find(int target, int [][] array) {
if(array==null||array.length==0||array[0].length==0){
return false;
}
int i=0;
int j=array[0].length-1;
while(i<array.length&&j>=0){
if(target==array[i][j]){
return true;
}else if(target<array[i][j]){
j--;
}else{
i++;
}
}
return false;
}
}
2.替換空格
題目描述
請實現一個函數,將一個字符串中的每個空格替換成“%20”。例如,當字符串為We Are Happy.則經過替換之后的字符串為We%20Are%20Happy。
思路1:從頭到尾掃描字符串,遇到空格,將后面的字符串后移2位,然后做替換
缺點:后面的字符串將被移動多次(多次賦值)。如果有O(n)個空格,字符串將移動O(n)次,每次需要移動O(n)個字符,所以時間復雜度是O(n^2)
思路2:從后往前開始字符串的替換和移動。首先遍歷一次字符串,找到所有空格的數目。替換后字符串長度為 原來的字符串長度+空格個數*2。我們新建一個數組,從尾部開始向前遍歷原數組,如果是空格,則新數組的位置替換成“%20”,新數組的下標逐個遞減3格;如果不是空格,直接復制原數組的內容到新數組對應位置,下標都減1格。
注意:
1.空字符串
2.null輸入
3.沒有空格的情況
4.空格在頭,中,尾的情況
知識點:
1.StringBuffer線程安全。內部char[]數組,方法前加synchronized關鍵字
2.StringBuffer中的capacity和length是不同的,capacity是容量,而length是實際字符串長度。當我們new StringBuffer(16)的時候,我們得到的是一個capacity=16,length=0的StringBuffer。
3.返回值是String,StringBuffer需要toString轉化
4.從char[]到String,不能通過toString方式(沒有重寫這個方法,實際使用的是Object的toString,即返回了地址),而通過new String(char[] array)方式
5.StringBuffer的get和set:str.charAt(i)、str.setCharAt(int index,char newChar)
6.String的get和set:str.charAt(i)、String不可變,可以將String轉化成StringBuilder/StringBuffer
public class Solution {
public static String replaceSpace(StringBuffer str) {
if(str==null||str.length()<=0){
return str.toString();
}
int count=0;
for(int i=0;i<str.length();i++){
if(str.charAt(i)==' '){
count++;
}
}
char[] newStr = new char[str.length()+count*2];
int i=str.length()-1;
int j=newStr.length-1;
while(i>=0){
if(str.charAt(i)==' '){
newStr[j--]='0';
newStr[j--]='2';
newStr[j--]='%';
}else{
newStr[j--]=str.charAt(i);
}
i--;
}
return new String(newStr);
}
}
3.從尾到頭打印鏈表
題目描述
輸入一個鏈表,按鏈表值從尾到頭的順序返回一個ArrayList。
思路一:用棧。先進后出。
要點:
1.注意判斷null值
2.stack的pop和peek方法可能會拋出EmptyStackException(),所以需要先調用empty()判斷是否為空,然后再進行操作
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> result = new ArrayList<Integer>();
if(listNode==null){
return result;
}
Stack<Integer> stack = new Stack<Integer>();
ListNode currentNode = listNode;
while(currentNode!=null){
stack.push(currentNode.val);
currentNode = currentNode.next;
}
while(!stack.empty()){
result.add(stack.pop());
}
return result;
}
}
思路二:用遞歸的方式。遞歸的終止條件是下一個節點為空指針。
需要注意,如果是遞歸,需要把返回結果的list放在遞歸函數外,以免每次遞歸時被歸0
public class Solution {
ArrayList<Integer> result = new ArrayList<Integer>();
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
if(listNode==null){
return result;
}
printListFromTailToHead(listNode.next);
result.add(listNode.val);
return result;
}
}
4.重建二叉樹
題目描述
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重復的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹并返回。
思路:
前序遍歷的首個節點是根節點,接下來是所有左子樹節點,最后是所有右子樹節點。利用中序遍歷得到左子樹節點個數和右子樹節點個數;再對左右子樹分別重復上述步驟,進行迭代。
1.前序遍歷和層次遍歷是不同的。前序遍歷是根左右,深度優先,會遍歷完左子樹再遍歷右子樹;層次遍歷是廣度優先。同層從左到右遍歷。
2.前序遍歷的序列,首個節點是根節點。再之后是全部的左子樹節點;再然后是全部的右子樹節點。所以給出前序遍歷序列之后,我們需要找到左子樹截止的位置;
3.這里可以借助中序遍歷的特點,根節點左邊的是全部的左子樹序列,根節點右邊的是全部的右子樹序列。
4.如果是后序遍歷,則按照左右根的順序,最后一個節點是根節點,根節點前面是右子樹的全部節點,再往前是左子樹的全部節點。也需要找到一個左右子樹的分界點。
如果是前序+中序/后序+中序,可以唯一確定一棵二叉樹。
如果是前序+后序,無法唯一確定一棵二叉樹。
相關問題:
1.二叉樹遍歷(已知前序和后序遍歷,求中序遍歷的可能的序列數)
https://blog.csdn.net/qq_37437983/article/details/79613947
2.已知前序和中序,求后序遍歷
遞歸。后序遍歷 右子節點緊挨根節點(如果有的話);左子節點位置在根節點前,右子樹節點前的位置。
如果,i是后序遍歷數組每次迭代的根節點位置,j是右子樹長度;則i-j-1是左子樹根節點的位置,i-1是右子樹根節點的位置。
https://blog.csdn.net/qq_29375837/article/details/81160362
/**
* 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 node=construct(pre,in,0,0,in.length-1);
return node;
}
public TreeNode construct(int [] pre,int [] in,int preStart,int inStart,int inEnd){
TreeNode node=null;
if(pre==null||in==null||preStart<0||preStart>pre.length||inStart<0||
inEnd>in.length-1||inStart>inEnd){
return null;
}
for(int i=inStart;i<=inEnd;i++){
if(in[i]==pre[preStart]){
node = new TreeNode(pre[preStart]);
node.left = construct(pre,in,preStart+1,inStart,i-1);
node.right = construct(pre,in,preStart+(i-inStart)+1,i+1,inEnd);
break;
}
}
return node;
}
}
5.用兩個棧實現一個隊列
題目描述
用兩個棧來實現一個隊列,完成隊列的Push和Pop操作。 隊列中的元素為int類型。
思路:push不變
pop時先判斷stack2是否為空,不為空直接pop stack2;為空則把stack1的元素逐一出棧再入棧stack2,最后pop stack2
遵循先進先出
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(stack2.empty()){
while(!stack1.empty()){
int node = stack1.pop();
stack2.push(node);
}
}
return stack2.pop();
}
}
附加:如果是用兩個隊列實現一個棧呢
思路1:queue1做進出隊列,queue2做中轉隊列。push直接push到queue1,pop的時候,先把queue1的全部元素pop到queue2,然后queue2.pop,再把queue2中的所有元素pop回queue1(效率不高)
思路2: queue1和queue2都做進出隊列。
push的時候,選擇非空的隊列進行push,如果都空,選擇queue1
push。
pop的時候,把非空隊列的元素全部pop到空隊列,然后pop。
6.旋轉數組的最小數字
題目描述
把一個數組最開始的若干個元素搬到數組的末尾,我們稱之為數組的旋轉。 輸入一個非減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該數組的最小值為1。 NOTE:給出的所有元素都大于0,若數組大小為0,請返回0。
關鍵點:
1.首先,“若干個元素搬到數組的末尾”中的若干個,可以為0個;即{1,2,3,4,5}也是{1,2,3,4,5}的一個旋轉。
2.可以有相同元素。
10111是01111的一個旋轉
11101也是01111的一個旋轉
思路:
思路1.遍歷得最小:O(n)復雜度
思路2.優化一點。發現后面的元素小于前面元素,則break。O(n)復雜度
思路3:利用二分法。O(logn)復雜度
利用二分法需要考慮縮小的條件。
考慮對于一個非遞減數組有下面的情況:
(1)旋轉0個元素。即旋轉后的數組也非遞減。返回數組第一個元素即可。這時只要判斷出第一個元素小于最后一個元素,必定是這種情況
(2)如果旋轉了,則必定是兩個非遞減數組的拼接。我們用low在第一個非遞減數組中,high在第二個非遞減數組中,不斷縮小邊界。mid是low和high的中間位置。
最小元素位于兩個非遞減數組的分界處。
如果low指向的值小于等于mid指向的值,說明low和mid都位于第一個非遞減數組,low可以后移至mid的位置,依舊還是位于第一個非遞減數組。
如果high指向的值大于等于mid指向的值,說明mid和high都位于第二個非遞減數組,high可以前移至mid的位置,依舊還是位于第二個非遞減數組。
最終,low將指向第一個非遞減數組的最后一個元素,high將指向第二個非遞減數組的第一個元素(即分界處)。這時,low和high將相鄰,這就是循環結束的條件。high指向的位置就是最小元素的位置。
(3)在(2)中還包含著一個特殊情況。比如10111。它的low,high,mid指向的值相同,但是顯然,low不能縮小到mid的邊界。這說明,當low,high,mid指向值相同的時候,我們無法判斷該如何移動指針。這個時候,應該改用遍歷的方式。
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
if(array==null||array.length==0)
return 0;
int low = 0;
int high = array.length-1;
int mid = low;
while(array[low]>=array[high]){
if(high-low==1){
mid = high;
break;
}
mid = (low+high)/2;
if(array[low]==array[mid]&&array[mid]==array[high]){
return inOrder(array,low,high);
}else if(array[low]<=array[mid]){
low = mid;
}else if(array[high]>=array[mid]){
high = mid;
}
}
return array[mid];
}
public int inOrder(int[] array,int low,int high){
for(int i=low;i<high;i++){
if(array[i]>array[i+1]){
return array[i+1];
}
}
return array[high];
}
}
7.斐波那契數列
用循環,不要用遞歸
注意取值,是否超過int范圍(一般10位)
39是63245986,沒超。
保險起見,可以使用long
public class Solution {
public int Fibonacci(int n) {
if(n==0)return 0;
if(n==1)return 1;
int[] fib = {0,1};
int result =0;
for(int i=2;i<=n;i++){
result = fib[0]+fib[1];
fib[0]=fib[1];
fib[1]=result;
}
return result;
}
}
變種1:跳臺階
一只青蛙一次可以跳上1級臺階,也可以跳上2級,求該青蛙跳上一個n級臺階總共有多少種跳法(先后次序不同算不同的結果)
解法:如果只有一級臺階,有1種跳法;如果有2級,有2種跳法。(初始)
如果有n級臺階(n>2),那么如果第一步跳1,有f(n-1)種跳法;如果第一步跳2,有f(n-2)種跳法。所以,f(n)=f(n-1)+f(n-2)
如果先后次序不同算相同的結果的話:n/2 +1種結果
變種2:變態跳臺階
一只青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
思路1:
如果只有一級臺階,有1種跳法;如果有2級,有2種跳法。(初始)
如果有n級臺階,那么第一步可以跳1,2,3,……,n。所以剩下的分別有f(n-1),f(n-2),f(n-3)……,f(1),1種跳法。
用一個長度為n的數組存儲之前計算出來的值。
f(n)=f(n-1)+f(n-2)+……+f(1)+1
public class Solution {
public int JumpFloorII(int target) {
if(target==0)return 0;
if(target==1)return 1;
if(target==2)return 2;
int[] pre = new int[target];
pre[0]=1;
pre[1]=2;
int result=pre[0]+pre[1];
for(int i=2;i<target;i++){
pre[i]=result+1;
result+=pre[i];
}
return pre[target-1];
}
}
然而,這種方式仍然不是最簡單的
由于
f(n)=f(n-1)+f(n-2)+……+f(1)+1
f(n-1)=f(n-2)+……+f(1)+1
所以f(n) = 2f(n-1)=22f(n-2)=22……f(2) = 2(n-2)*f(2)=2(n-1)
可以根據這個遞推式去做。
pow(2,n-1)或者1<<n-1
變種3:矩形覆蓋
題目描述
我們可以用21的小矩形橫著或者豎著去覆蓋更大的矩形。請問用n個21的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?
可以轉化成變種1的思考方式
public class Solution {
public int RectCover(int target) {
if(target==0)return 0;
if(target==1)return 1;
if(target==2)return 2;
int pre1= 1;
int pre2= 2;
int result =0;
for(int i=2;i<target;i++){
result=pre1+pre2;
pre1=pre2;
pre2 = result;
}
return result;
}
}
8.二進制中1的個數
位運算是屬于二進制數的運算
基本知識
- 代碼中在數字前加前綴
0x十六進制
0b二進制
0八進制 - 操作
&
^
|
m<<n:最左邊的n位將被丟棄,同時在右邊補上n個0
m=m>>n:最右邊的n位將被丟棄。如果是有符號數,用數字的符號位填補
左邊的n位
m>>>n:無符號右移,補0 - 原碼、反碼和補碼
https://blog.csdn.net/hittata/article/details/9108323
計算機中的負數表示是補碼
以負數-5為例:
1.先將-5的絕對值轉換成二進制,即為0000 0101;
2.然后求該二進制的反碼,即為 1111 1010;
3.最后將反碼加1,即為:1111 1011
所以Java中Integer.toBinaryString(-5)結果為11111111111111111111111111111011. Integer是32位(bit)的.
題目描述
輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼表示。
思路1:整數右移,直到為0(負數會陷入死循環)。但是java提供無符號右移方式,此時是可行的
public class Solution {
public int NumberOf1(int n) {
int count=0;
while(n!=0){
if((n&1)!=0){
count++;
}
n=n>>>1;
}
return count;
}
}
思路2:設置一個無符號整型的flag,然后每次flag左移,n&flag!=0時count數+1,直到flag為0(java語言中沒有無符號整型這樣的變量)
public class Solution {
public int NumberOf1(int n) {
int count=0;
int flag=1;
while(flag!=0){
if((n&flag)!=0){
count++;
}
flag = flag<<1;
}
return count;
}
}
思路3:n&(n-1)會將n的最右邊一位1變成0。令n=n&(n-1),直到n為0
public class Solution {
public int NumberOf1(int n) {
int count=0;
while(n!=0){
n=n&(n-1);
count++;
}
return count;
}
}