zoukankan      html  css  js  c++  java
  • 数组类问题总结

    数组与常用的解题算法

    文章目录

    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计算成本考虑,计算啊a[k]的内存地址a[k]_address = base_address + k * type_size,如果此时下标从1开始,则计算公式为a[k]_address = base_address + (k-1)*type_size,每次随机访问,对于cpu来说多了一次减法运算,这是其中一种解释;还有一种说法是历史原因,c的设计者用0开始计数数组下标,其他语言效仿c就也从0开始

    二分法

    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. 确定数组是否有序,是否有重复值,确定哪类问题对应的解题思路
    不会,我可以学;落后,我可以追赶;跌倒,我可以站起来!
  • 相关阅读:
    如何查看操作系统的具体版本号
    Review of Image Super-resolution Reconstruction Based on Deep Learning
    opencv imshow图片显示不全
    Javaweb文件下载异常
    Microsoft Edge出现错误代码:STATUS_INVALID_IMAGE_HASH
    Javaweb导入excel数据
    Java读取execl数据
    浏览器网页左上角小图标实现方式
    Java LDAP验证
    Java JPA新增数据生成UUID
  • 原文地址:https://www.cnblogs.com/xiaoshahai/p/14331714.html
Copyright © 2011-2022 走看看