劍指Offer-1
阿新 • • 發佈:2020-12-05
做了又忘,忘了又做,怎麼刷都是學不會啊啊啊
1
從每行每列都是遞增的二維陣列中找是否存在某數
public class Solution { public boolean Find(int target, int[][] array) { int rows = array.length; int cols = array[0].length; int i = rows - 1; int j = 0; // 從左下角,或右上角掃描 while(i >= 0 && j < cols){ if(target < array[i][j]){ i--; }else if(target > array[i][j]){ j++; }else{ return true; } } return false; } }
2
字串替換
// 內部函式
public class Solution {
public String replaceSpace(StringBuffer str) {
return str.toString().replaceAll(" ", "%20");
}
}
// 效率也沒差 public class Solution { public String replaceSpace(StringBuffer str) { StringBuffer sb = new StringBuffer(str.length()); // 避免多次擴容 System.arraycopy 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 Solution { public String replaceSpace(StringBuffer str) { int spaceNum = 0; // 算出空格總數 for(int i = 0; i < str.length(); i++){ if(str.charAt(i) == ' '){ spaceNum++; } } if(spaceNum == 0) return str.toString(); // 沒有空格直接返回 int oldLength = str.length(); int newLength = str.length() + spaceNum * 2; // 設定新長度 str.setLength(newLength); newLength--; // Java中沒有 "\0"結尾字元,所以實際大小減一 for(int i = oldLength-1; i >= 0; i--){ if(str.charAt(i) == ' '){ str.setCharAt(newLength--, '0'); str.setCharAt(newLength--, '2'); str.setCharAt(newLength--, '%'); }else{ str.setCharAt(newLength--, str.charAt(i)); } } return str.toString(); } }
3
從尾到頭遍歷連結串列
// 遞迴
public class Solution {
ArrayList list = new ArrayList();
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
if(listNode != null){
printListFromTailToHead(listNode.next);
list.add(listNode.val);
}
return list;
}
}
// 非遞迴
// 模擬棧,ArrayList(index,val)也可以模擬棧,remove(index),LinkedList??
// ArrayList的頭插法
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList list = new ArrayList();
while(listNode != null){
list.add(0,listNode.val);
listNode = listNode.next;
}
return list;
}
}
4
重建二叉樹(前序,中序)
// 一般樹都是遞迴操作,用索引
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
return reConBTree(pre,0,pre.length-1,in,0,in.length-1); // 傳入實際長度
}
public TreeNode reConBTree(int[] pre,int pleft,int pright,int[] in,int inleft,int inright){
if(pleft > pright || inleft > inright) return null; // 遞迴出口
TreeNode root = new TreeNode(pre[pleft]); // 父節點可以確定的
for(int i = inleft; i <= inright; i++){ // 遍歷尋找根節點
if(pre[pleft] == in[i]){ // in的要排除根節點+-1、
root.left = reConBTree(pre,pleft+1,pleft+(i-inleft),in,inleft,i-1);
root.right = reConBTree(pre,pleft+1+(i-inleft),pright,in,i+1,inright);
break;
}
}
return root; // 返回父節點
}
}
// Arrays.copyOfRange(arr,from,to),注意包括上標,不包括下標
// Arrays.copyOf(arr,newLength),新長度大於就長度就填充預設值
import java.util.*;
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if(pre.length == 0 || in.length == 0) return null; // 條件判斷
TreeNode root = new TreeNode(pre[0]); // 建立根節點
for(int i = 0; i < in.length; i++){
if(pre[0] == in[i]){
root.left = reConstructBinaryTree
(Arrays.copyOfRange(pre, 1, i+1),
Arrays.copyOfRange(in, 0, i));
root.right = reConstructBinaryTree
(Arrays.copyOfRange(pre, i+1, pre.length),
Arrays.copyOfRange(in, i+1,in.length));
}
}
return root;
}
}
5
兩個棧模擬佇列,先清空棧2才進入元素,先清空棧1才彈出元素
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
while(!stack2.isEmpty()){
stack1.push(stack2.pop());
}
stack1.push(node);
}
public int pop() {
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
return stack2.pop();
}
}
6
旋轉陣列的最小數字
// 非遞減,即遞增有重複
// 用二分法:最後low指向分界點前一個,high指向分界點後一個,二者相鄰,返回low++
// 第二個指標將指向最小元素
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
if(array.length == 0) return 0;
int low = 0;
int high = array.length - 1;
int mid = 0;
while(low < high){
mid = low + (high-low)/2;
if(array[low] < array[mid]){
low = mid;
}else if(array[mid] < array[high]){
high = mid;
}else { // 重複元素無法判斷是分界前後,只能順序找
low++; // 且最後也使指標指向分界的後一個
}
}
return array[low];
}
}
7
斐波那契數列
// 迭代法
public class Solution {
public int Fibonacci(int n) {
if(n == 0 || n == 1) return n;
int a = 0;
int b = 1;
int c = 0;
while(n > 1){ // 第一項1,上面已經給出了
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
}
// 遞迴
public class Solution {
public int Fibonacci(int n) {
if(n == 0 || n == 1) return n;
return Fibonacci(n-1) + Fibonacci(n-2);
}
}
8
跳臺階:注重思想,就是斐波那契數列
迭代沒什麼好說的
遞迴:只剩最後一步時,要麼是跳1階,要麼是跳2階
// 設n個臺階有f(n)種走法
// 只剩最後一步時,要麼是跳1階,要麼是跳2階
// 最後一步跳1階,即之前有n-1個臺階,據前面的假設,即n-1個臺階有f(n-1)種走法
// 最後一步跳2階,即之前有n-2個臺階,據前面的假設,即n-2個臺階有f(n-2)種走法
// 總結規律:n個臺階的走法等於前兩種情況的走法之和即 f(n) = f(n-1)+f(n-2)
// 變相斐波那契數列
// 注意和上面那題起點不一樣
public class Solution {
public int JumpFloor(int target) {
if(target == 1 || target == 2) return target;
return JumpFloor(target - 1) + JumpFloor(target - 2);
}
}
9
跳臺階II
// n級臺階,第一步有n種跳法:跳1級、跳2級、……、到跳n級
// 跳1級,剩下n-1級,則剩下跳法是f(n-1)
// 跳2級,剩下n-2級,則剩下跳法是f(n-2)
// 所以f(n) = f(n-1)+f(n-2)+...+f(1)
// 因為f(n-1) = f(n-2)+f(n-3)+...+f(1)
// 所以f(n) = 2*f(n-1)
public class Solution {
public int JumpFloorII(int target) {
int temp = 1;
while(target > 1){ // 第一個已給出{
temp *= 2;
target--;
}
return temp;
}
}
10
矩形覆蓋
// 逆序思想:最後一步有兩種情況
// OO OO
// XX OO
// XX XX
// .. ..
// XX XX
// 第一種情況:陰影部分的n-1塊矩形有多少種覆蓋方法,為f(n-1);
// 第二種情況:陰影部分的n-2塊矩形有多少種覆蓋方法,為f(n-2);
// 故f(n) = f(n-1) + f(n-2),還是一個斐波那契數列
// 斐波那契數列就是注意起點問題
public class Solution {
public int RectCover(int target) {
if(target == 0) return 0;
if(target == 1) return 1;
int a = 1,b = 1,c = 0;
while(target > 1){ // 第一項1,上面已經給出了
c = a + b;
a = b;
b = c;
target--;
}
return c;
}
}
11
求整數的二進位制中1的個數
// 逐位比較
public class Solution {
public int NumberOf1(int n) {
int cnt = 0;
while(n != 0){ // 不是大於0,因為有負數
cnt += n & 1;
n >>>= 1;
}
return cnt;
}
}
// 最優解
// 一個整數減去1,再和原整數做與運算,會把該整數最右邊一個1變成0
// 那麼一個整數的二進位制有多少個1,就可以進行多少次這樣的操作
public class Solution {
public int NumberOf1(int n) {
int cnt = 0;
while(n != 0){
n = n & (n-1);
cnt++;
}
return cnt;
}
}
// 作弊解
// 計算該數的二進位制,然後返回二進位制中`1`的個數
public class Solution {
public int NumberOf1(int n) {
return Integer.bitCount(n);
}
}
12
數值的整數次方
// 連乘思路:O(n)
public class Solution {
public double Power(double base, int exponent) {
double rs = 1;
for(int i = 0; i < Math.abs(exponent); i++){
rs *= base; // 連乘
}
if(exponent < 0){
rs = 1 / rs; // 負數次冪,直接倒數
}
return rs;
}
}
// 快速冪:log(n)
// 11可轉化為二進位制次冪:11 = 1011 = 2³×1 + 2²×0 + 2¹×1 + 2º×1 = 2³×1 + 2¹×1 + 2º×1
// base *= base 保持累乘的作用:base-->base2-->base4-->base8-->base16
// 那麼化簡後:a¹¹ = a^(2º+2¹+2³) = a^(1+2+8)
// 那麼 a¹¹ = a¹ * a² * a^8 = base * base^2 * base^8
public class Solution {
public double Power(double base, int exponent) {
double rs = 1; // 儲存結果
int power = Math.abs(exponent); // 冪的絕對值
// 快速冪核心
while(power != 0){
if( (power & 1) == 1 ){ // 冪的二進位制當前位為1即有效,結果相乘
rs *= base;
}
power >>>= 1; // 冪右移
base *= base; // 保持累乘,後面利用
}
if(exponent < 0){ // 負次冪,結果取倒數
rs = 1 / rs;
}
return rs;
}
}
13
調整陣列順序,奇數位於偶數前面,且相對順序不變(穩定性)
// 分治,思路明瞭
import java.util.ArrayList;
public class Solution {
public void reOrderArray(int [] array) {
ArrayList<Integer> list = new ArrayList();
for(int i = 0; i < array.length; i++){
if(array[i] % 2 != 0){
list.add(array[i]);
}
}
for(int i = 0; i < array.length; i++){
if(array[i] % 2 == 0){
list.add(array[i]);
}
}
for(int i = 0; i < array.length; i++){
array[i] = (Integer)list.get(i);
}
}
}
// 相對位置不變:保持穩定性即可,冒泡、直接插入等
// 類似冒泡演算法,前偶後奇數就交換:從後往前
public class Solution {
public void reOrderArray(int [] array) {
for(int i = 0; i < array.length; i++){
for(int j = array.length-1; j > i; j--){
if(array[j] % 2 == 1 && array[j-1] % 2 == 0){ // 整體奇數前移
int temp = array[j];
array[j] = array[j-1];
array[j-1] = temp;
}
}
}
}
}
// 就地演算法,不借助輔助,原地修改資料結構
// i 前面的奇數都排好了
public class Solution {
public void reOrderArray(int [] array) {
int temp;
int i = 0;
for(int j = 0; j < array.length; j++){
if(array[j] % 2 != 0){ // j遇到奇數,非奇後移
temp = array[j]; // 儲存移動覆蓋的奇數
for(int k = j - 1; k >= i; k--){ // i到j的偶數後移一位
array[k+1] = array[k];
}
array[i] = temp; // 奇數往前跳動
i++; // i指向奇數排好的下一個
}
}
}
}
14
返回連結串列倒數第K個節點
// 設定快慢指標
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
ListNode node,pre;
node = pre = head;
for(int i = 1; i <= k; i++){ // 先驅走k步
if(pre == null) return null; // 連結串列長沒有k步長
pre = pre.next;
}
while(pre != null){ // 同步走
pre = pre.next;
node = node.next;
}
return node;
}
}
15
反轉連結串列
// 需要三個指標
public class Solution {
public ListNode ReverseList(ListNode head) {
ListNode pre,next; // 如果head為空,那麼next.next就空指標異常
pre = next = null; // 所以只能在head不為空的迴圈內next了
while(head != null){ // head表示當前節點
next = head.next; // 上面說的next,這個要先寫
head.next = pre;
pre = head;
head = next;
}
return pre; // 這裡注意:上面的迴圈結束條件為head為空,那麼前驅節點才是真正的頭節點
}
}
16
合併兩個連結串列,並且按大小排序
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode head,node; // 要有一個表頭head來返回,node儲存節點,防止斷鏈
head = node = new ListNode(0); // 這裡居然這樣
while(list1 != null && list2 != null){
if(list1.val < list2.val){
node.next = list1;
node = node.next; // 指標移動
list1 = list1.next;
}else{
node.next = list2;
node = node.next;
list2 = list2.next;
}
}
if(list1 == null) node.next = list2;
if(list2 == null) node.next = list1;
return head.next;
}
}
17
判斷是否某樹的子結構(兩個遞迴,一個遍歷樹,一個匹配)
// 思路:先序遍歷父樹,每次遍歷都用來匹配
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
// 遞迴出口:包含了先序遍歷的 root != null
if(root1 == null || root2 == null) return false;
// 先序遍歷:當前節點,然後左子樹,最後右子樹
return recur(root1,root2)
|| HasSubtree(root1.left,root2)
|| HasSubtree(root1.right,root2);
}
private boolean recur(TreeNode root1,TreeNode root2){
if(root2 == null) return true; // 可先判斷root2為空,即完成遍歷匹配
if(root1 == null) return false; // 則root1先被擊穿
if(root1.val != root2.val) return false;// 當前節點不匹配
return recur(root1.left,root2.left) && recur(root1.right,root2.right);
}
}
18
轉變成二叉樹的映象
// 遞迴:也是前序遍歷
public class Solution {
public void Mirror(TreeNode root) {
if(root == null) return ; // 遞迴出口
swap(root); // 交換根節點的左右孩子
Mirror(root.left); // 孩子的孩子也交換
Mirror(root.right); // 孩子的孩子也交換
}
private void swap(TreeNode root){
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
}
}
19
螺旋列印矩陣
// 上下左右,四個邊界分別為l,r,t,b
// 思路1:按題意列印
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printMatrix(int [][] matrix) {
ArrayList<Integer> list = new ArrayList();
int left = 0, right = matrix[0].length - 1; // 四個邊界,-1方便後面用等於判斷
int top = 0, bottom = matrix.length - 1;
while(true){ // 無限迴圈遍歷,出口在內部的邊界判斷
for(int i = left; i <= right; i++) list.add(matrix[top][i]);
top++; // 遍歷完一行,判斷上下是否遍歷完
if(top > bottom) break;
for(int i = top; i <= bottom; i++) list.add(matrix[i][right]);
right--;
if(left > right) break;
for(int i = right; i >= left; i--) list.add(matrix[bottom][i]);
bottom--;
if(top > bottom) break;
for(int i = bottom; i >= top; i--) list.add(matrix[i][left]);
left++;
if(left > right) break;
}
return list;
}
}
20
用兩個棧求最小元素----O(1)
import java.util.Stack;
public class Solution {
Stack<Integer> data = new Stack();
Stack<Integer> min = new Stack();
public void push(int node) {
data.push(node);
if(min.isEmpty() || node < min.peek()){
min.push(node);
}
}
public void pop() {
if(data.peek() == min.peek()){
min.pop();
}
data.pop();
}
public int top() {
return data.peek();
}
public int min() {
return min.peek();
}
}