数组类问题总结

数组与常用的解题算法

文章目录

  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. 确定数组是否有序,是否有重复值,确定哪类问题对应的解题思路
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章