數組與常用的解題算法
文章目錄
- 什麼是數組
- 二分法
- 雙指針
- 滑動窗口
- 數組基礎操作
什麼是數組
- 一維數組:一片連續的存儲空間,下表從0開始,存儲相同類型的數據,具備按照下標隨機訪問,時間複雜度爲o(1),
- 二維數組:二維數組的存儲可不是連續的存儲空間,是通過一個數組記錄每個一維數組的地址,然後一維數組存儲數據
- 數組中的插入和刪除:當在數組最後面插入或者刪除時,不需要挪動其他元素,時間複雜度o(1),當在數組第k個位置時操作,則需要將k到n的數據往後或者往前挪動,時間複雜度爲o(n),其實每次進行這種插入刪除很耗費時間,其實可以對需要操作的數據進行標記,當空間不足或者再次查詢數組是真正的進行插入或者刪除,也就是標記清除算法
- 數組常見的問題,數組越界: 當數組a的長度爲1時,我們訪問a[1]時,就會發生數組越界,因爲訪問了不屬於當前數組的地址,這塊需要說明一下,java中對數組越界進行了判斷,但是在c中並沒有對這個行爲進行判斷,所以很多程序就通過噁心越界去訪問不應該訪問的內存去獲取信息。
- 數組爲什麼是0開始的呢?有幾種說法,第一種從cpu計算成本考慮,
二分法
- 數組作爲非常常用的數據類型,常用來查詢,查詢時常用的算法爲二分法。
- 什麼事二分法:二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。但是,折半查找要求線性表必須採用順序存儲結構,而且表中元素按關鍵字有序排列。
- 二分法使用條件:數組有序
- 時間複雜度:o(Logn)
- 看下面這道題,主要目的爲了理解二分法使用條件,二分過程中遇到數組中存在重複數據如何處理,二分法邊界問題。
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 = {1, 2, 3, 5, 6, 6,6, 6, 7, 8, 9};
37target = 6;
38返回的 indexList 就是 [4,5,6,7]既爲數組中爲6的下標;
雙指針
- 當我們遍歷數組要通過倆次for循環去尋找並替換某些值時,此時時間複雜度爲o(n2),可以考慮雙指針(快慢指針),通過一次for實現這個功能,時間複雜度o(n)
- 看一下題目具體理解一下,如何一次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 中的前五個元素爲 0, 1, 3, 0, 4。
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}
滑動窗口
- 當遇到最小子序列這類問題,確定數組中某個範圍內的和大小問題,通過不斷調整這個範圍或者窗口的大小,既滑動窗口。
- 時間複雜度 o(n)
- 此類問題需要注意 窗口內是什麼,窗口的起始位置,窗口的結束位置,調整起止位置時注意窗口內的值符合要求。
- 看下面的題目理解注意事項和滑動
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給定一個正整數 n,生成一個包含 1 到 n2 所有元素,且元素按順時針順序螺旋排列的正方形矩陣。
2示例:
3輸入: 3
4輸出:
5[
6[ 1, 2, 3 ],
7[ 8, 9, 4 ],
8[ 7, 6, 5 ]
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}
總結
- 數組的問題注意邊界問題,注意不變量,
- 確定數組是否有序,是否有重複值,確定哪類問題對應的解題思路