【算法思維訓練-劍指Offer聯名 一】數組篇

從今天開始通過算法訓練來鍛鍊自己的邏輯思維,在上手階段通過刷《劍指Offer》的一些題來找回手感,由於數據結構也有些遺忘,所以按照題型分類的角度來進行訓練,每篇爲一個主題,每週完成至少一個主題,挑戰值三個主題,同時也能完善下自己數據結構的一些知識。每篇blog先基礎總結下數據結構和核心點,然後訓練相應的題,最後總結下收穫

數據結構

當然在訓練之前回顧下基礎知識,在Java版的數據結構中的【數據結構 一】數組一文中已經進行過詳細的整理,這裏只摘錄一些基本要點如下:

  • 數組變量是引用類型,數組本身就是對象,數組中的每個元素相當於該對象的成員變量,數組對象本身是在中存儲的
  • 數組的長度是固定的,也就是說可以存儲固定個數的數據
  • 數組類型可以是任何數據類型,包括基本數據類型和引用類型,但是同一數組內的元素類型必須一致,也就是說數組中存儲的數據的數據類型一致
  • 數組元素在內存中是一個接着一個線性存放的,是內存中連續分配的。通過第一個元素就能訪問隨後的元素,避免了數據覆蓋的可能性。數組所存在的內存空間爲數組專用,避免了數據被覆蓋的問題。

同時還有些注意事項需要注意:

  • 數組聲明的時候並沒有實例化任何對象,只有在實例化數組對象時,JVM才分配空間,這時才與長度有關。
  • 聲明一個數組的時候並沒有數組真正被創建。
  • 構造一個數組,必須指定長度,二維數組指定第一維。

數組的初始化方式分爲三種:靜態初始化、動態初始化以及默認初始化。靜態即直接賦值,不需要new,直接分配空間。動態即先new一個內存空間出來,然後再賦值。默認初始化則是在new出空間後按照元素類型給元素進行默認賦值。初始化時內存的變化如下,分別爲基本數據類型的初始化和引用數據類型的初始化:

  1. 基本數據類型的初始化:給所有的元素默認初始化賦值爲0,然後再進行初始化賦值

在這裏插入圖片描述

  1. 引用數據類型的初始化:給所有的元素默認初始化賦值爲null,然後再進行初始化賦值
    在這裏插入圖片描述

以上就是數組數據結構的一些理論要點,接下來進行算法訓練。

算法訓練

《劍指offer》關於數組的算法訓練共有3題:構建乘積數組【難度2】、數組中重複的數字【難度3】、二維數組中的查找【難度4】。

構建乘積數組

給定一個數組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];)

分析

這道題比較好理解,當然對於重新撿起來我還是有些難度,題的意思很明確,就是長度爲n的A、B兩個數組,B的每個元素都是除了A中這個元素的其它元素的連乘:

  • A、B的長度是相同的,都是n
  • B的每個元素都是除了A中這個元素的其它元素的連乘
  • 不能用除法
    • 邊界值:數組不存在、數組長度小於2均返回false,不滿足邊界條件

這樣就是這個題的四要素,然後得出如下的圖。
在這裏插入圖片描述

解法

其實最直觀的解法就是把A的元素連乘,然後循環,每個B的元素等於A元素連乘的結果除以當前B元素索引位置A的值。但是這樣與不能用除法相悖。所以還有第二直觀的方法,就是左邊獲取一個乘積,右邊獲取一個乘積,再相乘,但這樣的算法複雜度卻比較高

import java.util.ArrayList;
public class Solution {
    public int[] multiply(int[] A) {
       //設定邊界條件,B[n-1] = A[0] * A[1] * ... * A[n-2];所以A長度必須大於等於2,否則會報索引越界
       if(A==null||A.length<=1)
          return null;
       //動態初始化數組B
       int length = A.length;
       int[] B=new int[length];
       //循環給B賦值,得到下三角
       for(int i=0;i<length;i++){
          int temp1=1; //用temp1接當前元素左邊的連乘積
          int temp2=1; //用temp2接當前元素右邊的連乘積
          //用temp1接當前元素左邊的連乘積
          for(int j=0;j<i;++j){
           temp1*=A[j];
          }
          //用temp2接當前元素右邊的連乘積
          for(int m=i+1;m<length;++m){
           temp2*=A[m];
          }
           B[i]=temp1*temp2;
       }
        return B;
       
    }
}

其實可以從圖中看出,B數組可以看做兩個倒三角的乘積。

import java.util.ArrayList;
public class Solution {
    public int[] multiply(int[] A) {
       //設定邊界條件,B[n-1] = A[0] * A[1] * ... * A[n-2];所以A長度必須大於等於2,否則會報索引越界
       if(A==null||A.length<=1)
          return null;
       //動態初始化數組B
       int length = A.length;
       int[] B=new int[length];
       //循環給B賦值,得到上三角
       B[0]=1;
       for(int i=1;i<length;i++){
           B[i]=B[i-1]*A[i-1];
       }
        
       //循環給B賦值,乘以下三角
       int temp=1;
       for(int i=length-2;i>=0;i--){
           temp*=A[i+1];
           B[i]*=temp;
       }
        
        return B;
       
    }
}

這道題的收穫是:1,要將要求轉換爲分解條件,最好有圖解。2,注意邊界條件

數組中重複的數字

在一個長度爲n的數組裏的所有數字都在0到n-1的範圍內。 數組中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每個數字重複幾次。請找出數組中任意一個重複的數字。 例如,如果輸入長度爲7的數組{2,3,1,0,2,5,3},那麼對應的輸出是第一個重複的數字2。

分析

按照之前的訓練思維來處理,先分解題目信息:

  • 數組長度爲n,整數數組
  • 數組中有重複數字,重複數字的個數爲0到n/2之間
  • 找到第一個重複的數字
  • 邊界值:數組不存在、數組長度爲0均返回false,不滿足邊界條件

這樣就是這個題的四要素,接下來按照要求來做一下題。

解法

自己AC了一種複雜度比較高但比較直觀的解法,就是構造一個數組B用來存儲歷史值, //如果當前值在歷史值中存在則命中,否則加入歷史值。

public class Solution {
    public boolean duplicate(int numbers[],int length,int [] duplication) {
       //如果數組不存在或者只有一個數字,則直接返回false;
       if(numbers==null||length<=1)
             return false;
       int[] B=new int[length];
       B[0]=numbers[0]; //用B存儲左邊的值
       for(int i=1;i<length;i++){
          for(int j=0;j<i;j++){
              //如果當前值在歷史值中存在則命中,否則加入歷史值
              if(numbers[i]==B[j]){ 
                  duplication[0]=numbers[i];
                  return true;
              }else{
                  B[i]=numbers[i];
              }
          }
       }
        return false;
       
    }
    
}

其實還有一種更好的解法,就是構造一個標布爾數組,因爲numbers裏存的都是0到n-1整數,所以其實可以當做另一個數組的索引下標。

public class Solution {
    //boolean只佔一位,所以還是比較省的
    public boolean duplicate(int numbers[], int length, int[] duplication) {
        //構造一個二進制數組
        boolean[] k = new boolean[length];
        for (int i = 0; i < k.length; i++) {
            //把數組值當做這個二進制數組的索引,只要發現該索引位被標記過,就認爲重複
            if (k[numbers[i]] == true) {
                duplication[0] = numbers[i];
                return true;
            }
            //設置二進制數組的索引位數組值爲true
            k[numbers[i]] = true;
        }
        return false;
    }
    
}

這道題的收穫是:思路要靈活,注意題中的0–n-1,思考爲什麼這麼設置

二維數組中的查找

在一個二維數組中(每個一維數組的長度相同),每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。

分析

按照之前的訓練思維來處理,先分解題目信息:

  • 數組爲一個n*n的二維數組,說白了就是一個正方形數組
  • 數組的每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序
  • 判斷數組中是否存在一個整數。
  • 邊界值:數組不存在、數組長度爲0、數組行長度爲0,均返回false,不滿足邊界條件

這樣就是這個題的三要素,接下來按照要求來做一下題。畫個模型來輔助理解。例如我們要命中下面矩陣中的6.
在這裏插入圖片描述

解法

自己AC了一種複雜度比較高但比較直觀的解法,就是遍歷二維數組尋找整數值,這種做法當然不可取,複雜度高也浪費了順序遞增的條件。

public class Solution {
    public boolean Find(int target, int [][] array) {
       //如果數組不存在或者長度爲0,直接返回false
       if(array==null||array.length<1||array[0].length<1)
             return false;
       for(int i=0;i<array.length;i++){
          for(int j=0;j<array.length;j++){
             if(target==array[i][j])
                 return true;
           }
       }
       return false;
 
    }
}

還有一種用上這兩種條件的解法,經過簡單思考就能想到,利用其順序性,逐步縮小範圍

public class Solution {
    public boolean Find(int target, int [][] array) {
       //如果數組不存在或者長度爲0,直接返回false
       if(array==null||array.length<1||array[0].length<1)
             return false;
       //聲明行、列以及檢索下標
       int rows=array.length;
       int columns=array.length;
       int column=columns-1;
       int row=0;
       //移動光標的過程中分別做三次判斷
       while(row<rows&&column>=0){
           if(target==array[row][column])
               return true;
           if(target>array[row][column])
               row++;
           else
               column--;
       }
       return false;
        
    }
}

這道題的收穫是:思路要靈活,注意題中每行每列的順序性,還有就是在矩陣二維數組中,多考慮下指針移動的過程。

思考

其實數組算是一種比較基礎的數據結構,而且一般不會遇到太複雜的數組結構,一般也就到二維,更高維的維度計算也比較少。本次訓練收穫如下:

  • 拆解:遇到問題一定要把題拆解開來,拆解成一系列的條件
  • 畫圖:拆解完成之後,最好依據題目描述構造一個簡單的Demo來輔助理解題目的意思
  • 邊界值:寫代碼的時候一定要注意邊界值,防止溢出
  • 特殊說明:要注意題目中的特殊說明,這些特殊說明可以幫助你跳出常規的複雜計算方式

思維也會因算法訓練更加嚴謹。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章