導讀: 數據是最基本的數據結構,能解決不少問題,好比常見的,求解,使用數組來解決重複遞歸過程,動態規劃使用數組記錄最近解過程當中的各個步驟的解。今天咱們用幾個常見的面試題來談一談算法——數據結構【數組】那些事java
數組是最多見的也是常常使用的數據結構,大多數語言的封裝類型(List/Map)都含有數組的身影。咱們知道數組時一塊連續的內存區域,訪問某個數據時用數組下標訪問,當擴容和刪除時略顯麻煩.面試
那咱們怎麼才能用好數組呢?怎麼能快速使用數組這種簡單的數據結構來解決複雜的問題呢?算法
下面咱們從一到題來引入講解數組解一些常見的算法題.數組
給定一個整數數組 nums ,找到一個具備最大和的連續子數組(子數組最少包含一個元素),返回其最大和。數據結構
示例: 輸入: [-2,1,-3,4,-1,2,1,-5,4],
輸出: 6
解釋: 連續子數組 [4,-1,2,1] 的和最大,爲 6。函數
在個人思惟裏,若是一個問題,你以前沒作過,沒有系統的學習過此類通用算法解決,那麼暴力就是能想到的第一種解法思路。post
下面咱們來看一下暴力解題法,怎麼去作?首先咱們能夠想到學習
列舉 數組中的全部可能的連續區間/子集?而後把連續區間相加,求和,判斷最大.優化
public int maxSubArray(int[] nums) {
int max = Integer.MIN_VALUE;
int sum;
//使用兩個for循環來列舉出全部的連續區間/子集
for (int i = 0; i < nums.length; i++) {
for (int j = i; j < nums.length; j++) {
sum = 0;
//列舉出來的子集,在經過一個for循環相加
for (int k = i; k <= j; k++) {
sum += nums[k];
}
if (sum > max) {
max = sum;
}
}
}
return max;
}
複製代碼
其實咱們在列舉的過程當中就能夠把和相加起來,進行判斷,全部有了解法二網站
public int maxSubArray(int[] nums) {
int max = Integer.MIN_VALUE;
int sum;
for (int i = 0; i < nums.length; i++) {
sum = 0;
for (int j = i; j < nums.length; j++) {
//列舉子序列求和的過程當中解決此問題
sum += nums[j];
if (sum > max)
max = sum;
}
}
return max;
}
複製代碼
看了上述的暴力解題法,咱們發現一些能夠優化的東西,由於他有重複的子結構性質,那就是每次相加都從開始的那個元素去相加,那咱們能夠從動態規劃的角度想一下這個問題。
設sum[i]爲以第i個元素結尾且和最大的連續子數組,那咱們能夠把上述問題描述爲這樣的一個公式
把公式化解一下就是這樣的.
那麼算法能夠描述爲
public int maxSubArray(int[] nums) {
int sums[] = new int[nums.length];
sums[0] = nums[0];
int max = nums[0];
for (int i = 1; i < nums.length; i++) {
if (sums[i-1] > 0) sums[i] += nums[i];
else sums[i] = nums[i];
if (max < sums[i]) max = sums[i];
}
return max;
}
複製代碼
這樣算法的空間複雜度爲,若是原數據數組nums能夠被更改,那麼咱們的sums數組能夠省略.
public int maxSubArray(int[] nums) {
int currnetValue = nums[0];
int max = nums[0];
for (int i = 1; i < nums.length; i++) {
if (currnetValue > 0) currnetValue += nums[i];
else currnetValue = nums[i];
if (max < currnetValue) max = currnetValue;
}
return max;
}
複製代碼
排列問題和組合問題很類似(經典問題有全排列),這裏列舉一道 相關的題
給定一個無重複元素的數組candidates和一個目標數target,找出candidates中全部可使數字和爲target的組合。
candidates中的數字能夠無限制重複被選取。
說明: 全部數字(包括target)都是正整數。 解集不能包含重複的組合。
示例1: 輸入: candidates = [2,3,6,7], target = 7, 所求解集爲: [ [7], [2,2,3] ]示例2: 輸入: candidates = [2,3,5], target = 8, 所求解集爲: [ [2,2,2,2], [2,3,3], [3,5] ]
按個人思路來,第一就去想暴力解法,而後再去想辦法優化(若是你以前作過這題,或者直接能想到什麼動態規劃算法,能夠直接使用,不要在暴力。)
想講一個暴力通用解法模型,就是本節所說的n層for循環求解 . 這能夠解決不少與組合相關的問題,如密碼暴力破解等.
給你一個字符串,列舉裏面全部可能的三位密碼,(字符串裏面字母都不相同,不考慮密碼順序)
這樣咱們能很簡單的想到一個算法
int len = str.length();
for(int i = 0;i < len - 2;i++){
for(int j = i +1;j<len - 1;j++){
for(int k = j+1;k<len;k++){
String password = ""+ str.charAt(i) + str.charAt(j) + str.charAt(k);
}
}
}
複製代碼
那要是如今列舉100位密碼呢?不可能寫這樣的循環了吧?
通常解法,是使用遞歸來替代n層for循環,通常解法模型,僞代碼以下
void find(int start,int count,int passwordLen,int array[]){
if(count == passwordLen){
//獲得結果
return;
}
for(int i = start;i<array.length;i++){
find(i + 1,count+1,passwordLen,array);
}
}
複製代碼
那麼上面那道題,使用這個模型來解決代碼以下:
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> listAll = new ArrayList<>();
List<Integer> currentList = new ArrayList<>();
Arrays.sort(candidates);
find(listAll,currentList,candidates,target,0);
return listAll;
}
private void find(List<List<Integer>> listAll,List<Integer> currentList,int[] candidates,int target,int currentIndex) {
if(target < 0) return;
if(target == 0){
listAll.add(currentList);
}else{
for(int i = currentIndex;i<candidates.length && candidates[i]<=target;i++){
List<Integer> list=new ArrayList<>(currentList);
list.add(candidates[i]);
find(listAll,list,candidates,target - candidates[i],i);
//currentList.remove(currentList.size()-1);
}
}
}
}
複製代碼
假設你正在爬樓梯。須要 n 階你才能到達樓頂。 每次你能夠爬 1 或 2 個臺階。你有多少種不一樣的方法能夠爬到樓頂呢?
若是你以前見過這題,那應該知道這是一個斐波那契數列(類似的問題還有不少,如兔子繁殖問題等都是斐波那契數列),能夠描述爲
那麼求這個公式怎麼解呢?咱們能夠想到一個遞歸過程,以下
public int fib(int n) {
if(n <= 2) return 1;
else{
return fib(n-1) + fib(n-2);
}
}
複製代碼
咱們知道遞歸過程當中會有不少重複計算,這裏列舉一張法f(5)的計算過程,以下
那咱們怎麼解決這個問題呢,使用一個數組來保存結果,避免重複計算,(也被成爲備忘錄法則),這裏其實已是動態規劃了。
public int fib(int n) {
if(n <= 2) return 1;
if(save[n] > 0){
return save[n];
}else{
save[n] = save[n-1]+save[n-2];
return save[n];
}
}
複製代碼
咱們知道遞歸有一個函數棧的調用過程,那咱們能夠嘗試把遞歸去了
public int fib(int n) {
save[0] = 1;
save[1] = 1;
for(int i = 2;i < n;i++){
save[i] = save[i-1]+ save[i-2];
}
return save[n-1];
}
}
複製代碼
其實這個若是隻是一次計算f(n)的結果,不用使用數組,能夠優化爲
int v1 = 1,v2=1;
for(int i = 2;i < n;i++){
int temp = v1 + v2;
v1 = v2;
v2 = temp;
}
return v2;
}
複製代碼
拓展:看完上述內容,你應該能輕鬆解決,若是樓梯不是一次只能上1或者2層,他能上任意層,怎麼解決這個問題呢?歡迎留言區評論
二維數組是在一維數組的基礎上,增長了縱向。那二維數組通常的暴力解法和一維數組基本相似。常見的二維數組經典題目有 【n皇后問題】【走迷宮】 對應的回溯算法,也是暴力算法的一種,有一個剪枝,優化過程。 在這裏舉一個二維數組例子。
一個機器人位於一個 m x n 網格的左上角 (起始點在下圖中標記爲「Start」 )。
機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角(在下圖中標記爲「Finish」)。 問總共有多少條不一樣的路徑?
有了上面的暴力解題思路過程,而且此題也有一些重疊子問題,那咱們這題,直接考慮動態規劃解決. 能夠想到遞歸函數爲
根據遞歸公式代碼,也能輕鬆寫出
class Solution {
public int uniquePaths(int m, int n) {
int sum[][] = new int[m][n];
for(int i = 0;i<m;i++){
sum[i][0] = 1;
}
for(int i = 0;i<n;i++){
sum[0][i] = 1;
}
for(int i = 1;i<m;i++){
for(int j = 1;j<n;j++){
sum[i][j] = sum[i-1][j] + sum[i][j-1];
}
}
return sum[m-1][n-1];
}
}
複製代碼
本文主要介紹了一些數組的算法,一些解題思路,從問題的暴力解法開始(通用的暴力解法:兩個for循環列舉,n層for循環等),逐步優化,一些能想到的優化方式,節省空間,時間。到後面一些能夠用動態規劃解決的問題,(此處沒有列舉一些回溯常看法法,可是思路都是從暴力解法開始,逐步剪枝,優化的過程)
由於本人技術有限,文中若有錯誤之處,還請大佬指教,感謝.
文中題目來源 LeetCode
若是你正在準備面試,或者你喜歡算法,刷題推薦LeetCode,挺不錯的網站.