數組類問題總結

數組與常用的解題算法

文章目錄

  1. 什麼是數組
  2. 二分法
  3. 雙指針
  4. 滑動窗口
  5. 數組基礎操作

什麼是數組

  1. 一維數組:一片連續的存儲空間,下表從0開始,存儲相同類型的數據,具備按照下標隨機訪問,時間複雜度爲o(1),
  2. 二維數組:二維數組的存儲可不是連續的存儲空間,是通過一個數組記錄每個一維數組的地址,然後一維數組存儲數據
  3. 數組中的插入和刪除:當在數組最後面插入或者刪除時,不需要挪動其他元素,時間複雜度o(1),當在數組第k個位置時操作,則需要將k到n的數據往後或者往前挪動,時間複雜度爲o(n),其實每次進行這種插入刪除很耗費時間,其實可以對需要操作的數據進行標記,當空間不足或者再次查詢數組是真正的進行插入或者刪除,也就是標記清除算法
  4. 數組常見的問題,數組越界: 當數組a的長度爲1時,我們訪問a[1]時,就會發生數組越界,因爲訪問了不屬於當前數組的地址,這塊需要說明一下,java中對數組越界進行了判斷,但是在c中並沒有對這個行爲進行判斷,所以很多程序就通過噁心越界去訪問不應該訪問的內存去獲取信息。
  5. 數組爲什麼是0開始的呢?有幾種說法,第一種從cpu計算成本考慮,

二分法

  1. 數組作爲非常常用的數據類型,常用來查詢,查詢時常用的算法爲二分法。
  2. 什麼事二分法:二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。但是,折半查找要求線性表必須採用順序存儲結構,而且表中元素按關鍵字有序排列。
  3. 二分法使用條件:數組有序
  4. 時間複雜度:o(Logn)
  5. 看下面這道題,主要目的爲了理解二分法使用條件,二分過程中遇到數組中存在重複數據如何處理,二分法邊界問題。
 1 /**
2    * 二分法 首先確定不變量,既不變區間【0,nums.length -1】,左閉右閉區間
3    * @param nums
4    * @param target
5    * @return
6    */

7   public int searchInsert(int[] nums, int target) {
8       int left = 0;
9       int right = nums.length -1;
10       while(left <= right){
11          //避免溢出
12           int middle  = left + ((right -left)/2);
13           if(nums[middle]<target){
14               left = middle + 1;
15           }else if(nums[middle]>target){
16               right = middle -1;
17           }else{
18               return middle;
19           }
20       }
21       return right+1;
22   }

當二分法遇到重複值時的處理辦法

 1二分查找重複值問題
2當我們遇到[1,2,2,2,2,2]或者[1,1,1,2]這種情況可能會出現死循環該如何解決。
3其實這種情況只會出現在二分查找的第三個判斷上,既
4nums[middle] == target
5
6所以只需要對這塊進行處理,既向左以及想有找到所有相同元素的下標,至於要取最左還是最右看你的需求,那麼看一下代碼
7
8else {    //表示arr[mid] == val
9               /*思路分析
10               1.在找到mid索引值,不要馬上返回
11               2.向mid索引值的左邊掃描,將所有滿足1000,的元素的下標, 加入到集合ArrayList
12               3.向mid索引值的右邊掃描,將所有滿足1000, 的元素的下標,加入到集合ArrayList
13               4.將ArrayList返回*/

14
15               //向mid左邊掃描
16               int temp = mid - 1;
17               while (true) {
18                   if (temp < 0 || arr[temp] != val)//沒有找到就退出循環
19                       break;
20                   //執行到這裏說明找到了,就把找到的元素添加到集合中,繼續向左找
21                   indexList.add(temp);
22                   temp -= 1;
23               }
24               indexList.add(mid);//加入已經找到了的元素【arr[mid]==val】
25               //向mid右邊掃描
26               temp = mid + 1;
27               while (true) {
28                   if (temp > arr.length - 1 || arr[temp] != val)
29                       break;
30                   //執行到這裏說明找到了,就把找到的元素添加到集合中,繼續向右
31                   indexList.add(temp);
32                   temp += 1;
33               }
34               return indexList;
35           }
36測試用例 :int[] arr = {123566,66789};
37target = 6
38返回的 indexList 就是 [4,5,6,7]既爲數組中爲6的下標;

雙指針

  1. 當我們遍歷數組要通過倆次for循環去尋找並替換某些值時,此時時間複雜度爲o(n2),可以考慮雙指針(快慢指針),通過一次for實現這個功能,時間複雜度o(n)
  2. 看一下題目具體理解一下,如何一次for實現倆次for的工作。
 1移除元素
2給你一個數組 nums 和一個值 val,你需要 原地 移除所有數值等於 val 的元素,並返回移除後數組的新長度。
3不要使用額外的數組空間,你必須僅使用 O(1) 額外空間並 原地 修改輸入數組。
4元素的順序可以改變。你不需要考慮數組中超出新長度後面的元素。
5示例 1:
6給定 nums = [3,2,2,3], val = 3,
7函數應該返回新的長度 2, 並且 nums 中的前兩個元素均爲 2
8你不需要考慮數組中超出新長度後面的元素。
9示例 2:
10給定 nums = [0,1,2,2,3,0,4,2], val = 2,
11函數應該返回新的長度 5, 並且 nums 中的前五個元素爲 01304
12注意這五個元素可爲任意順序。
13你不需要考慮數組中超出新長度後面的元素。
14說明:
15爲什麼返回數值是整數,但輸出的答案是數組呢?
16請注意,輸入數組是以「引用」方式傳遞的,這意味着在函數裏修改輸入數組對於調用者是可見的。
17你可以想象內部操作如下:
18// nums 是以“引用”方式傳遞的。也就是說,不對實參作任何拷貝
19int len = removeElement(nums, val);
20// 在函數裏修改輸入數組對於調用者是可見的。
21// 根據你的函數返回的長度, 它會打印出數組中 該長度範圍內 的所有元素。
22for (int i = 0; i < len; i++) {
23print(nums[i]);
24}
25
26
27public class RemoveElement {
28
29    /**
30     * 快慢指針
31     * @param nums
32     * @param val
33     * @return
34     */

35    public static int removeElement(int[] nums, int val) {
36        //慢指針記錄置換次數
37        int slowPoint = 0;
38        //快指針遍歷數組
39        for (int fastPoint = 0; fastPoint < nums.length; fastPoint++) {
40            if(val!=nums[fastPoint]){
41                nums[slowPoint++] = nums[fastPoint];
42            }
43        }
44        return slowPoint;
45    }
46}

滑動窗口

  1. 當遇到最小子序列這類問題,確定數組中某個範圍內的和大小問題,通過不斷調整這個範圍或者窗口的大小,既滑動窗口。
  2. 時間複雜度 o(n)
  3. 此類問題需要注意 窗口內是什麼,窗口的起始位置,窗口的結束位置,調整起止位置時注意窗口內的值符合要求。
  4. 看下面的題目理解注意事項和滑動
 1public class MinSubArrayLen {
2    /**
3     * 209. 長度最小的子數組
4     * 給定一個含有 n 個正整數的數組和一個正整數 s ,找出該數組中滿足其和 ≥ s 的長度最小的 連續         子數組,並返回其長度。如果不存在符合條件的子數組,返回 0。
5     * <p>  
6     * <p>
7     * <p>
8     * 示例:
9     * <p>
10     * 輸入:s = 7, nums = [2,3,1,2,4,3]
11     * 輸出:2
12     * 解釋:子數組 [4,3] 是該條件下的長度最小的子數組。
13     * <p>
14     * <p>
15     * 進階:
16     * <p>
17     * 如果你已經完成了 O(n) 時間複雜度的解法, 請嘗試 O(n log n) 時間複雜度的解法。
18     *
19     * @param s
20     * @param nums
21     * @return
22     */

23    public static int minSubArrayLen(int s, int[] nums) {
24        int resultLength = Integer.MAX_VALUE;
25        int start = 0;
26        int sum = 0;
27          //此處就是滑動終止的位置
28        for (int end = 0; end < nums.length; end++) {
29            sum += nums[end];
30            //此處就是滑動起始的位置
31            while (sum >= s) {
32                int len = end - start + 1;
33                resultLength = resultLength < len ? resultLength : len;
34                sum -= nums[start++];
35            }
36        }
37        if (resultLength == Integer.MAX_VALUE) {
38            return 0;
39        } else {
40
41            return resultLength;
42        }
43    }
44}

數組基礎操作

  1. 數組的基礎操作插入,如果是一個二維數組插入呢,相當於在一個平面上操作數組,下面這道題就是在二維數組插入,需要注意的就是 不變量,既邊界問題。
 1給定一個正整數 n,生成一個包含 1 到 n2 所有元素,且元素按順時針順序螺旋排列的正方形矩陣。
2示例:
3輸入: 3
4輸出:
5[
6123 ],
7894 ],
8765 ]
9]
10
11public class GenerateMatrix {
12
13    public static void main(String[] args) {
14        int[][] res = generateMatrix(4);
15        for (int i = 0; i < res.length; i++) {
16            for (int j = 0; j < res[i].length; j++) {
17                System.out.print(res[i][j] + " ");
18            }
19            System.out.println();
20        }
21    }
22
23    public static int[][] generateMatrix(int n) {
24        int[][] res = new int[n][n];
25        //開始位置
26        int startX = 0, startY = 0;
27        //循環次數
28        int loop = n / 2;
29        //如果循環次數是奇數,那麼會存在中心點,中心點是最後賦值,中心點位置
30        int mid = n / 2;
31        //每條邊邊界,每循環一圈就加2,因爲進內圈需要加1,同時邊界向內收縮1
32        int offset = 1;
33        //初始賦值
34        int count = 1;
35        //原則 左閉又開
36        // 上面 從左右導遊
37        // 右邊 從上到下
38        // 下面 從右到在
39        // 左邊 從下到上
40        while ((loop--) > 0) {
41            int i = startX;
42            int j = startY;
43
44            // 上面 從左右導遊
45            for (; j < startY + n - offset; j++) {
46                res[startX][j] = count++;
47            }
48
49            // 右邊 從上到下
50            for (; i < startX + n - offset; i++) {
51                res[i][j] = count++;
52            }
53
54            // 下面 從右到在
55            for (; j > startY; j--) {
56                res[i][j] = count++;
57            }
58
59            // 左邊 從下到上
60            for (; i > startX; i--) {
61                res[i][j] = count++;
62            }
63
64            startX++;
65            startY++;
66            offset += 2;
67
68        }
69
70        if (n % 2 == 1) {
71            res[mid][mid] = count;
72        }
73
74        return res;
75    }
76}

總結

  1. 數組的問題注意邊界問題,注意不變量,
  2. 確定數組是否有序,是否有重複值,確定哪類問題對應的解題思路
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章