詳解數組刷題上

  • 2019 年 10 月 6 日
  • 筆記

詳解數組刷題上

本專題內容如下:

一、初始定義及原地修改1.283. 移動零2.27. 移除元素3.26. 刪除排序數組中的重複項4.80. 刪除排序數組中的重複項 II二、基礎思想應用1.75. 顏色分類2.88. 合併兩個有序數組3.215. 數組中的第K個最大元素4.167. 兩數之和 II – 輸入有序數組5.209. 長度最小的子數組

一、初始定義及原地修改

類似題目:

  • 283. 移動零
  • 27. 移除元素
  • 26. 刪除排序數組中的重複項

注意的問題

  • 如何定義變數?
  • 如何從數組中刪除?
  • 剩餘元素的排列是否要保證原有的相對順序?
  • 是否有空間複雜度的要求? O(1)

1.283. 移動零

給定一個數組 nums,編寫一個函數將所有 0 移動到數組的末尾,同時保持非零元素的相對順序。

示例:

輸入: [0,1,0,3,12]  輸出: [1,3,12,0,0]  

說明:

  1. 必須在原數組上操作,不能拷貝額外的數組。
  2. 盡量減少操作次數。

實現思路:

  • 拿出非0元素
  • 將非0元素拿出來,然後空位補0

實現:

循環一次找出不為0的index,並將該Index對應的值依次存儲到數組中,後面的全部為0即可!

class Solution {      public void moveZeroes(int[] nums) {          int j=0;          for(int i=0;i<nums.length;i++){              if(nums[i]!=0){                  nums[j++]=nums[i];              }          }          //剩餘位置放0          while(j<nums.length){              nums[j++]=0;          }      }  }  

2.27. 移除元素

給定一個數組 nums 和一個值 val,你需要原地移除所有數值等於 val 的元素,返回移除後數組的新長度。

不要使用額外的數組空間,你必須在原地修改輸入數組並在使用 O(1) 額外空間的條件下完成。

元素的順序可以改變。你不需要考慮數組中超出新長度後面的元素。

示例 1:

給定 nums = [3,2,2,3], val = 3,    函數應該返回新的長度 2, 並且 nums 中的前兩個元素均為 2。    你不需要考慮數組中超出新長度後面的元素。  

實現思路:

找到不等目標值的元素,然後利用不等目標值的個數原地修改數組。

實現:

class Solution {      public int removeElement(int[] nums, int val) {          int i,j=0;          for(i=0;i<nums.length;i++){              if(nums[i]!=val){                  nums[j++]=nums[i];              }          }          return j;      }  }  

3.26. 刪除排序數組中的重複項

給定一個排序數組,你需要在原地刪除重複出現的元素,使得每個元素只出現一次,返回移除後數組的新長度。

不要使用額外的數組空間,你必須在原地修改輸入數組並在使用 O(1) 額外空間的條件下完成。

示例 1:

給定數組 nums = [1,1,2],    函數應該返回新的長度 2, 並且原數組 nums 的前兩個元素被修改為 1, 2。    你不需要考慮數組中超出新長度後面的元素。  

實現思路:

找到當前元素與前一元素不等的位置,開始原地修改數組。

實現:

class Solution {      public int removeDuplicates(int[] nums) {          int i=1,j=0;            while(i<nums.length){              if (nums[i]!=nums[i-1]){                  nums[++j]=nums[i];              }              i++;          }          return j+1;      }  }  

4.80. 刪除排序數組中的重複項 II

給定一個排序數組,你需要在原地刪除重複出現的元素,使得每個元素最多出現兩次,返回移除後數組的新長度。

不要使用額外的數組空間,你必須在原地修改輸入數組並在使用 O(1) 額外空間的條件下完成。

示例 1:

給定 nums = [1,1,1,2,2,3],    函數應返回新長度 length = 5, 並且原數組的前五個元素被修改為 1, 1, 2, 2, 3 。    你不需要考慮數組中超出新長度後面的元素。  

實現思路:

1   2  cur back  1   2   2  pre cur back  

當數組中的某一部分滿足上述兩者之一的時候,就可以移動cur(cur記錄的就是該數組的實際大小)。

實現:

class Solution {      public int removeDuplicates(int[] nums) {          int cur,pre,back;          cur=1;          pre=cur-1;          back=cur+1;          while(back<nums.length){              if(nums[cur]!=nums[back]||(nums[cur]==nums[back] && nums[cur]!=nums[pre])){                  pre=cur;                  nums[cur+1]=nums[back];                  cur++;              }              back++;          }          return cur+1;      }  }  

前面兩個元素不修改,直到到第3個元素,也就是index=2開始比較。使用j來存儲滿足題意的位置,讓nums[i]與nums[j-2]比較,然後賦值即可。

class Solution {      public int removeDuplicates(int[] nums) {          int i=2,j=2;          while(i<nums.length){              if(nums[i]>nums[j-2]){ //這裡一定是j-2而不是i-2,如果是i-2會在下面一行程式碼執行過程中將原來的值覆蓋掉,此時結果就不對了,而如果是j-2,就是正確的,因為j中存放的就是未覆蓋的值,如果所有元素都是不一樣的,那麼i與j的指向一致,而當元素並不一樣的時候,j-2中就存放了i-2實際元素!                  nums[j++]=nums[i];              }              i++;          }          return j;      }  }  

二、基礎思想應用

基本思想:

  • 快速排序
  • 三路快排
  • 二分查找
  • 基數排序

類似題目:

  • 75. 顏色分類
  • 88. 合併兩個有序數組
  • 215. 數組中的第K個最大元素
  • 167. 兩數之和 II – 輸入有序數組
  • 209. 長度最小的子數組

二分查找法模板:

int l = 0, r = n-1; // 在[l...r]的範圍里尋找target:前閉後閉  while( l <= r ){    // 只要還有可以查找的內容。當 l == r時,區間[l...r]依然是有效的      int mid = l + (r-l)/2;      if( arr[mid] == target )          return mid;      //mid已經判斷過了      if( target > arr[mid] )          l = mid + 1;  // target在[mid+1...r]中; [l...mid]一定沒有target      else    // target < arr[mid]          r = mid - 1;  // target在[l...mid-1]中; [mid...r]一定沒有target  }  

注意的問題

  • 求mid值是採用(l+r)/2容易整形溢出
  • 採用mid = l + (r-l)/2;

1.75. 顏色分類

給定一個包含紅色、白色和藍色,一共 n 個元素的數組,原地對它們進行排序,使得相同顏色的元素相鄰,並按照紅色、白色、藍色順序排列。

此題中,我們使用整數 0、 1 和 2 分別表示紅色、白色和藍色。

注意: 不能使用程式碼庫中的排序函數來解決這道題。

示例:

輸入: [2,0,2,1,1,0]  輸出: [0,0,1,1,2,2]  

實現思路:

統計一次紅、白、黑的數量,然後再循環一次原地修改!

實現:

class Solution {      public void sortColors(int[] nums) {          int red=0,white=0,blue=0;          for(int i=0;i<nums.length;i++){              switch (nums[i]){                  case 0:                      red++;                      break;                  case 1:                      white++;                      break;                  default:                      blue++;              }          }          for(int i=0;i<nums.length;i++){              if (i<red){                  nums[i]=0;              }else if(i<red+white){                  nums[i]=1;              }else{                  nums[i]=2;              }          }      }  }  

三路快排

class Solution {      public void sortColors(int[] nums) {          int red_white = -1;          int white_blue= nums.length;          int i=0;          int temp;          while(i<white_blue){              if(nums[i]==0){                  red_white++;                  temp=nums[red_white];                  nums[red_white]=nums[i];                  nums[i]=temp;                  i++;              }else if(nums[i]==2){                  white_blue--;                  temp=nums[white_blue];                  nums[white_blue]=nums[i];                  nums[i]=temp;              }else{                  i++;              }          }      }  }  

2.88. 合併兩個有序數組

給定兩個有序整數數組 nums1nums2,將 nums2 合併到 nums1使得 num1 成為一個有序數組。

說明:

  • 初始化 nums1nums2 的元素數量分別為 mn
  • 你可以假設 nums1 有足夠的空間(空間大小大於或等於 m + n)來保存 nums2 中的元素。

示例:

輸入:  nums1 = [1,2,3,0,0,0], m = 3  nums2 = [2,5,6],       n = 3    輸出: [1,2,2,3,5,6]  

實現思路:

從後往前遍歷,然後去比較兩數組大小,誰大先放誰。

實現:

class Solution {      public void merge(int[] nums1, int m, int[] nums2, int n) {          int n1=m-1,n2=n-1;            for(int i=m+n-1;i>=0;i--){              if((n1>=0 && n2>=0 && nums1[n1]>=nums2[n2]) || (n2<0 && n1>=0)){                  nums1[i]=nums1[n1];                  n1--;              }else if((n1>=0 && n2>=0 && nums1[n1]<nums2[n2]) || (n1<0 && n2>=0)){                  nums1[i]=nums2[n2];                  n2--;              }          }      }  }  

3.215. 數組中的第K個最大元素

在未排序的數組中找到第 k 個最大的元素。請注意,你需要找的是數組排序後的第 k 個最大的元素,而不是第 k 個不同的元素。

示例 1:

輸入: [3,2,1,5,6,4] 和 k = 2  輸出: 5  

示例 2:

輸入: [3,2,3,1,2,4,5,5,6] 和 k = 4  輸出: 4  

說明:

你可以假設 k 總是有效的,且 1 ≤ k ≤ 數組的長度。

實現思路:

直接使用冒泡排序,當冒出k次後,此時的元素就是第K個最大元素。

實現:

class Solution {      public int findKthLargest(int[] nums, int k) {          int i,j;          int n=nums.length;          int temp;          for (i=0;i<n-1;i++){              for (j=i+1;j<n;j++){                  if (nums[i]<nums[j]) {                      temp=nums[i];                      nums[i]=nums[j];                      nums[j]=temp;                  }              }              k--;              if(k==0){                  break;              }          }          return nums[i];      }      }  

交換多次快排

class Solution {      public int findKthLargest(int[] nums, int k) {          quickSort(nums,0,nums.length-1);          return nums[k-1];      }      public void quickSort(int[] nums,int left,int right){          if(left>right){              return;          }          int pivot = nums[left];          int i = left;          int j = right;          int temp;          while(i<j){              while(i<j && nums[j]<=pivot)                  j--;                if(i<j){                  temp=nums[i];                  nums[i]=nums[j];                  nums[j]=temp;              }              while(i<j && nums[i]>=pivot)                  i++;              if (i<j){                  temp=nums[i];                  nums[i]=nums[j];                  nums[j]=temp;              }          }             //nums[left]=nums[i]; 這種方法這行不能要!          nums[i]=pivot;          quickSort(nums,left,i-1);          quickSort(nums,i+1,right);      }    }  

最後交換快排

class Solution {      public int findKthLargest(int[] nums, int k) {          quickSort(nums,0,nums.length-1);          return nums[k-1];      }      public void quickSort(int[] nums,int left,int right){          if(left>right){              return;          }          int pivot = nums[left];          int i = left;          int j = right;          int temp;          while(i<j){              while(i<j && nums[j]<=pivot)                  j--;              while(i<j && nums[i]>=pivot)                  i++;              if (i<j){                  temp=nums[i];                  nums[i]=nums[j];                  nums[j]=temp;              }          }          nums[left]=nums[i]; //必須要!          nums[i]=pivot;          quickSort(nums,left,i-1);          quickSort(nums,i+1,right);      }    }  

4.167. 兩數之和 II – 輸入有序數組

給定一個已按照升序排列 的有序數組,找到兩個數使得它們相加之和等於目標數。

函數應該返回這兩個下標值 index1 和 index2,其中 index1 必須小於 index2

說明:

  • 返回的下標值(index1 和 index2)不是從零開始的。
  • 你可以假設每個輸入只對應唯一的答案,而且你不可以重複使用相同的元素。

示例:

輸入: numbers = [2, 7, 11, 15], target = 9  輸出: [1,2]  解釋: 2 與 7 之和等於目標數 9 。因此 index1 = 1, index2 = 2 。  

實現思路:

二分查找法

實現:

注意二分查找的程式碼中左右可以相等!

class Solution {      public int[] twoSum(int[] numbers, int target) {          int[] res=new int[2];            for(int i=0;i<numbers.length;i++){              int second = binarySearch(numbers, i+1, numbers.length-1, target-numbers[i]);              if(second>-1){                  res[0]=i+1;                  res[1]=second+1;              }          }          return res;        }      public int binarySearch(int[] numbers, int start, int end, int target){          int i=start;          int j=end;          while(i<=j){              int mid=(i+j)/2;              if(numbers[mid]== target){                  return mid;              }else if(numbers[mid]> target){                  j=mid-1;              }else{                  i=mid+1;              }          }          return -1;      }  }  

5.209. 長度最小的子數組

給定一個含有 n 個正整數的數組和一個正整數 s ,找出該數組中滿足其和≥ s 的長度最小的連續子數組如果不存在符合條件的連續子數組,返回 0。

示例:

輸入: s = 7, nums = [2,3,1,2,4,3]  輸出: 2  解釋: 子數組 [4,3] 是該條件下的長度最小的連續子數組。  

進階:

如果你已經完成了O(n) 時間複雜度的解法, 請嘗試 O(n log n) 時間複雜度的解法。

實現思路:

循環+二分

實現:

注意二分查找的程式碼中左右可以相等!

時間複雜度:O(nlogn),空間複雜度O(n)。

思路:

將原先數組所有數字累加求和得到一個新數組,例如:

nums=[2,3,1,2,4,3]  parNums=[0,2,5,6,8,12,15]  

然後循環parNums,對每一個數組中的index對應的值,利用二分法找到最小的窗口。

舉個例子:

nums=[2,3,1,2,4,3]  parNums=[0,2,5,6,8,12,15]  --------------  i=0時  --------------  第一輪  left=0,right=6  left<right,計算出mid=3,此時對應的值為6,mid距離i的和也就是6<7,  調整left=mid+1=4。  第二輪  left=4,right=6  left<right,計算出mid=5,此時對應的值為12,mid距離i的和也就是12>7,  調整right=mid-1。  是不是上述可以看作是查找7的二分法,那麼後面依次類推即可。  當然left調整也可以是left++,right調整也可以是right--,也可以AC,但是效率會低一些!  --------------  ......  

循環+二分

public class Solution {      public int minSubArrayLen(int s, int[] nums) {          int[] parNums = new int[nums.length+1];          for(int i=1;i<parNums.length;i++){              parNums[i]=parNums[i-1]+nums[i-1];          }            int min=0;          for(int i=0;i<parNums.length;i++){              int left=i;              int right=parNums.length-1;              while(left<=right){                  int mid=left+(right-left)/2;                  int subSum=parNums[mid]-parNums[i];                  if(subSum>=s){                      right=mid-1; //可以換成right--;                      min=min==0?mid-i:Math.min(min,mid-i);                  }else{                      left=mid+1; //可以換成left++;                  }              }          }          return min;      }  }