【算法总结】数组

目前刷了一遍代码随想录,跟着剑指再总结一下之前做过的题,参考代码随想录、剑指Offer、力扣等,如有侵权,联系删除

目录

文章目录

  • 目录
  • 数组
    • 理论基础
      • 1. 集合、列表和数组
      • 2.数组的操作
      • 3.二维数组
      • 4.常用方法
        • 4.1 数组中的双指针
        • 4.2 前缀和:累加数组数字求子数组
        • 4.3 二分查找
        • 4.4 反转数组
        • 4.5 哈希表
    • 双指针
    • 累加数组数字求子数组/前缀和
    • 二分查找
    • 模拟
      • 1.合并区间【LC56】
      • 2.零矩阵【LC73】
        • 模拟
      • 3.对角线遍历【LC498】
      • 4. 螺旋矩阵 II【LC59】
      • 5.螺旋矩阵【LC54】
      • 6.分割数组【LC915】
        • 模拟+二次遍历
        • 模拟+一次遍历
    • 反转数组
      • 1.轮换数组【LC189】
        • 反转字符串/反转数组
      • 2.旋转矩阵【LC48】
        • 暴力
        • 按规律反转
    • 哈希表
      • 1.有多少小于当前数字的数字【LC1365】
        • 暴力
        • 排序+哈希表
        • 数值计算
      • 2.独一无二的出现次数【LC1207】
        • 哈希表
        • 哈希表【数组】
    • 排序
      • 1.数组拆分【LC561】

数组

理论基础

1. 集合、列表和数组

  • 集合
    • 定义:由一个或多个确定的元素所构成的整体
    • 特点
      1. 集合里的元素类型不一定相同
      2. 集合里的元素没有顺序
    • 集合并不直接存在于编程语言中。但是,实际编程语言中的很多数据结构,就是在集合的基础上添加了一些规则形成的
  • 列表(又称线性列表)
    • 定义:是一种由数据项构成的有限序列,即按照一定的线性顺序,排列而成的数据项的**集合[**列表的概念是在集合的特征上形成的]
    • 特点
      1. 列表具有顺序
      2. 列表的长度是可变
    • 列表的表现形式
      1. 数组
      2. 链表
      3. 队列
    • 掌握:向列表中添加、删除元素的具体实现方法
  • 数组
    • 特点
      1. 数组是列表的实现方式之一,因此数组也具有顺序,并且长度是可变
      2. 数组利用索引标识每项数据在数组中的位置
        • 索引从0开始的常用编程语言:C、C++、java
        • 索引从1开始的常用编程语言:MATLAB
      3. 数组中的元素在内存中是连续存储的,且每个元素占用相同大小的内存
      4. 在不同的编程语言中,数组的实现方式具有一定的差别
        • 比如在C++和JAVA中,数组中的元素类型必须保持一致,而Python中则可以不同(Python中的数组叫做List)
  • 如何从宏观上区分列表和数组呢?
    • 数组利用索引标识每项数据在数组中的位置,而列表没有索引
    • 数组数组中的元素在内存中是连续存储的,而列表中的元素在内存中不一定是连续存储的,如链表的元素在内存中不一定是连续的。

2.数组的操作

  • 查询元素
    • 通过下标读取数组中的元素:时间复杂度为O(1)
    • 通过元素的查询元素:时间复杂度为O(n)
  • 插入元素
    • 末尾插入:时间复杂度为O(1)
    • 首尾间插入:时间复杂度为O(n)
      • 若需要频繁地对数组元素进行插入操作,使用链表更合适
  • 删除元素
    • 时间复杂度为O(n)

3.二维数组

实际题目中,往往使用二维数组处理矩阵类相关问题,包括矩阵旋转、对角线遍历,以及对子矩阵的操作等。

4.常用方法

4.1 数组中的双指针
  • 反向指针:从两端向中间迭代数组,一个指针从头部开始,另一个指针从尾部开始

    • 用途:求排序数组中的两个数字之和
  • 同向指针:使用两个同向的指针——快慢指针

    • 用途:求正数数组中子数组的和或乘积

    • 需要考虑以下三点

      1. 快指针什么时候移动?
      2. 慢指针什么时候移动?
      3. 快慢指针移动步数时候有联系?
  • 使用双指针解决子数组之和的题目时数组中的所有数字都必须是正数;当数组中存在负数和零时,可以采用累加数组数字求子数组之和的方法

4.2 前缀和:累加数组数字求子数组

使用双指针解决子数组之和的题目时数组中的所有数字都必须是正数;当数组中存在负数和零时,可以采用累加数组数字求子数组之和的方法

  1. 首先进行预处理,计算从数组下标为0的数字开始到以每个数字为结尾的子数组之和,记为S0,S1……Sn
  2. 那么从下标i开始到下标为j结束的子数组的和就是Sj-Si-1
4.3 二分查找

在有序数组或者有序数组旋转后的数组中查询某一个元素

4.4 反转数组

通过按照某种规律反转原数组,最后实现最终结果,达到空间复杂度O(1)

4.5 哈希表

统计数组中的次数

双指针

还没整理完,之后单独贴链接:(

累加数组数字求子数组/前缀和

二分查找

还没整理完,之后单独贴链接:(

模拟

1.合并区间【LC56】

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。

作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/array-and-string/c5tv3/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 题解

    • 解题思路

      • 先对输入数组按照区间左边的值进行升序排列
      • 初始化一个变量 outputs,用于存储合并直接的区间结果
      • 遍历排序后的所有区间,针对每个区间做如下的处理:
        1. 如果当前处理的区间是第一个区间的话,那么直接将区间加入到 outputs
        2. 比较当前处理区间左边的值 (currLeft) 和 outputs 中最后一个区间右边的值 (outputsLastRight) :
          • 如果 outputsLastRight < currLeft,说明没有重叠,那么直接将当前处理的区间加入到 outputs
          • 否则,说明有重叠,那么将 outputs 中最后一个区间的右边的值更新为:当前处理区间右边值和 outputsLastRight 的最大值
        3. 将 outputs 转成数组,并返回
    • 代码

      public int[][] merge(int[][] intervals){
      
          
          //1.先对输入数组按照区间左边的值进行升序排列
          Arrays.sort(intervals, new Comparator<int[]>() {
              @Override
              public int compare(int[] o1, int[] o2) {
                  return o1[0]-o2[0];
              }
          });
          //2.初始化一个变量outputs,用于存储合并直接的区间结果
          List<int []> outputs = new ArrayList<>();
          //将第一个区间直接加入outputs
          outputs.add(intervals[0]);
          for (int i = 1; i < intervals.length; i++) {
              //比较当前处理的区间左边的值和outputs中最后一个区间右边的值
              if(intervals[i][0] > outputs.get(outputs.size()-1)[1]){//没有重叠
                  outputs.add(intervals[i]);
              }else{
                  outputs.get(outputs.size()-1)[1] = Math.max(outputs.get(outputs.size()-1)[1],intervals[i][1]);
      
              }
      
          }
          //3.转换为数组
          return outputs.toArray(new int[outputs.size()][]);
      }
      

2.零矩阵【LC73】

编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。

模拟

2021/10/24

  • 我的题解

    一个rowsSet存行中有为0的数的行号,一个colsSet存列中有为0的数的列号。第一次遍历得到所有包含0值的行和列,第二次遍历、第三次遍历将所有包含0值的行和列置零

    • 时间复杂度: O ( r o w ∗ c o l ) O(row*col) O(rowcol)
    • 空间复杂度: O ( r o w + c o l ) O(row+col) O(row+col)
    public static void setZeroes(int[][] matrix) {
            int row = matrix.length;
            int col = matrix[0].length;
            Set rows = new TreeSet();
            Set cols = new TreeSet();
            //找到为0的元素所在行所在列
            for (int i=0;i<row;i++){
                for (int j=0;j<col;j++){
                    if(matrix[i][j]==0){
                        rows.add(i);
                        cols.add(j);
                    }
                }
            }
            Iterator it = rows.iterator();
            //将指定行的元素置零
            while(it.hasNext()){
                int i = (int)it.next();
                for (int j=0;j<col;j++){
                    matrix[i][j]=0;
                }
            }
            it = cols.iterator();
            //将指定列的元素置零
            while (it.hasNext()){
                int j = (int)it.next();
                for (int i=0;i<row;i++){
                    matrix[i][j] = 0;
                }
            }
        }
    
  • 题解

    • 方法一

      使用两个标记数组分别记录每一行和每一列是否有零出现。

      具体地,我们首先遍历该数组一次,如果某个元素为 0,那么就将该元素所在的行和列所对应标记数组的位置置为true。最后我们再次遍历该数组,用标记数组更新原数组即可。

      • 时间复杂度: O ( r o w ∗ c o l ) O(row*col) O(rowcol)
      • 空间复杂度: O ( r o w + c o l ) O(row+col) O(row+col)
    • 方法二

      我们可以用矩阵的第一行和第一列代替方法一中的两个标记数组,以达到 O(1)的额外空间。但这样会导致原数组的第一行和第一列被修改,无法记录它们是否原本包含 0。因此我们需要额外使用两个标记变量分别记录第一行和第一列是否原本包含 0。

      • 时间复杂度: O ( r o w ∗ c o l ) O(row*col) O(rowcol)
      • 空间复杂度: O ( 1 ) O(1) O(1)
    • 方法三

      我们可以对方法二进一步优化,只使用一个标记变量记录第一列是否原本存在 0。这样,第一列的第一个元素即可以标记第一行是否出现 0。但为了防止每一列的第一个元素被提前更新,我们需要从最后一行开始,倒序地处理矩阵元素。

      • 时间复杂度: O ( r o w ∗ c o l ) O(row*col) O(rowcol)
      • 空间复杂度: O ( 1 ) O(1) O(1)
      class Solution {
          public void setZeroes(int[][] matrix) {
              int m = matrix.length, n = matrix[0].length;
              boolean flagCol0 = false;
              for (int i = 0; i < m; i++) {
                  if (matrix[i][0] == 0) {
                      flagCol0 = true;
                  }
                  for (int j = 1; j < n; j++) {
                      if (matrix[i][j] == 0) {
                          matrix[i][0] = matrix[0][j] = 0;
                      }
                  }
              }
              for (int i = m - 1; i >= 0; i--) {
                  for (int j = 1; j < n; j++) {
                      if (matrix[i][0] == 0 || matrix[0][j] == 0) {
                          matrix[i][j] = 0;
                      }
                  }
                  if (flagCol0) {
                      matrix[i][0] = 0;
                  }
              }
          }
      }
      
      作者:力扣官方题解
      链接:https://leetcode.cn/problems/zero-matrix-lcci/solutions/742402/ling-ju-zhen-by-leetcode-solution-7ogg/
      来源:力扣(LeetCode)
      著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
      

3.对角线遍历【LC498】

给你一个大小为 m x n 的矩阵 mat ,请以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。

  • 我的题解

    • 时间复杂度: O ( r o w ∗ c o l ) O(row*col) O(rowcol)
    • 空间复杂度: O ( r o w ∗ c o l ) O(row*col) O(rowcol)
    //2021/10/24
    public int[] findDiagonalOrder(int[][] mat) {
        int sum = mat.length * mat[0].length; //数组的长度
        int[] res = new int[sum];// 返回的结果
        int num = 0;// 存放进数组的元素的下标
        int i = 0; // 矩阵的行
        int j = 0; // 矩阵的列
        boolean isUp = true;// 记录遍历的方向,当isUp为true时,遍历方向为向右上方遍历;当isUp为false时,遍历方向为向左下方遍历
        boolean isOut = false;// 记录遍历是否已经出界,【在遍历过程中,存在连续两次出界的情况,如果上一次已经出界,那么即使此次遍历再次出界,仍不改变遍历方向】
        while(num < sum){
            boolean lastDir = isUp;//记录上一次遍历的方向
            boolean isLastOut = isOut;//记录上一次遍历是否出界
            if(i>=0 && i<mat.length && j>=0 && j<mat[0].length){//如果索引未超出边界,则将其放入数组中
                isOut = false;
                res[num++] = mat[i][j];
            }else{// 如果索引超出边界,则改变遍历方向
                isOut = true;
                if (!isLastOut){//【如果上一次已经出界,那么即使此次遍历再次出界,仍不改变遍历方向】
                    isUp = !isUp;
                }
            }
            // 根据方向,改变索引
            if (isUp){//遍历方向为右上时
                if(lastDir == isUp){// 当与前一次遍历方向相同时,行号才需减1,否则只进行列加1操作【右】
                    i--;
                }
                j++;
            }else{//遍历方向为左下时
                i++;
                if(lastDir == isUp){// 当与前一次遍历方向相同时,列号才需减1,否则只进行行加1操作【下】
                    j--;
                }
            }
        }
        return res;
    }
    
  • 题解一

    链接:https://leetcode-cn.com/problems/diagonal-traverse/solution/dui-jiao-xian-bian-li-by-leetcode/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    1. 初始化一个布尔变量 direction 表示当前对角线的方向。根据当前方向和尾部位置确定下一条对角线首部。最初 direction 为 1,方向向上。每条对角线遍历完成后更新 direction。
    2. 假设当前对角线首部为 matrix[i][j]matrix[i][j],根据方向遍历该对角线。
      • 向上的对角线,下一个元素是 matrix[i - 1][j + 1]matrix[i−1][j+1]。
      • 向下的对角线,下一个元素是 matrix[i + 1][j - 1]matrix[i+1][j−1]。
    3. 遍历当前对角线元素直到到达矩阵边界结束。
    4. 假设现在到达当前对角线的最后一个元素,寻找下一条对角线首部。
      • 找出向下行走对角线头部需要遵循两个规则:
        如果当前尾部不在矩阵最后一行,则下一个对角线的头部是当前尾部的正下方元素;否则,下一条对角线首部是当前尾部的右边元素。
      • 找出向上行走对角线头部需要遵循两个规则:
        如果当前尾部不在矩阵最后一列,下一条对角线的首部是当前尾部正右方元素;否则,下一条对角线首部是当前尾部的下边元素。
    5. 继续处理对角线元素,当前对角线遍历结束时,使用当前方向和尾部位置找出下一条对角线首部。然后翻转方向,处理下一条对角线。
  • 题解二

    作者:moreality
    链接:https://leetcode-cn.com/problems/diagonal-traverse/solution/498-dui-jiao-xian-bian-li-zui-jian-dan-y-ibu3/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    • 解题思路

      1. 一定要仔细找规律
      2. 充分考虑边界情况
    • 找规律

      注意左下(+1, -1)和右上移动(-1, +1)不会改变当前(行 + 列)的和, 即判断方式为行 + 列的和;if (行 + 列)是偶数, 就是右上, 是奇数就是左下

    • 边界情况

      1. 左下 = [1, -1]
        如果在最后一行 => 向右 [0, 1] (先判断行, 防止在左下角的时候向下则越界)
        如果在第一列 => 向下 [1, 0]
      2. 右上 = [-1, 1]
        如果在最后一列 => 向下 [1, 0] (先判断列, 防止在右上角时候向右则越界)
        如果在第一行 => 向右 [0, 1]

4. 螺旋矩阵 II【LC59】

给你一个正整数 n ,生成一个包含 1n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix

  • 题解1

    • 生成一个n*n的空矩阵mat,随后模拟按照顺时针方向生成矩阵的过程:
      • 定义数组directions记录生成过程中可能出现的方向,directionIndex标志此时的方向
      • 初始值num=1,迭代终止值maxNum=n*n
      • 当 num <= maxNum 时,始终按照 从左到右 从上到下 从右到左 从下到上 填入顺序循环,每次填入后:
        • 判断下一个位置是否到边界处或者下一个位置已经填入(值不为0),若是则改变方向
        • 更新row,col
      • 最终返回mat即可
    • 时间复杂度:O(n2)
    • 空间复杂度:O(1)
  • 题解2

    • 生成一个 n×n 空矩阵 mat,随后模拟整个向内环绕的填入过程:
      • 定义当前左右上下边界 l,r,t,b,初始值 num = 1,迭代终止值 tar = n * n;
      • 当 num <= tar 时,始终按照 从左到右 从上到下 从右到左 从下到上 填入顺序循环,每次填入后:
        • 执行 num += 1:得到下一个需要填入的数字;
        • 更新边界:例如从左到右填完后,上边界 t += 1,相当于上边界向内缩 1。
      • 使用num <= tar而不是l < r || t < b作为迭代条件,是为了解决当n为奇数时,矩阵中心数字无法在迭代过程中被填充的问题。
      • 最终返回 mat 即可
    • 时间复杂度:O(n2)
    • 空间复杂度:O(1)
    class Solution {
        public int[][] generateMatrix(int n) {
            int l = 0, r = n - 1, t = 0, b = n - 1;
            int[][] mat = new int[n][n];
            int num = 1, tar = n * n;
            while(num <= tar){
                for(int i = l; i <= r; i++) mat[t][i] = num++; // left to right.
                t++;
                for(int i = t; i <= b; i++) mat[i][r] = num++; // top to bottom.
                r--;
                for(int i = r; i >= l; i--) mat[b][i] = num++; // right to left.
                b--;
                for(int i = b; i >= t; i--) mat[i][l] = num++; // bottom to top.
                l++;
            }
            return mat;
        }
    }
    
    作者:jyd
    链接:https://leetcode-cn.com/problems/spiral-matrix-ii/solution/spiral-matrix-ii-mo-ni-fa-she-ding-bian-jie-qing-x/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
  • 题解3:按层模拟

    • 生成一个n*n的空矩阵mat,随后模拟按照顺时针方向生成矩阵的过程:
      • 初始值num=1
      • statx,starty记录每循环一个圈的初始位置,初始值均为0,每循环一圈+1
      • offset控制每一圈里每一条边遍历的长度,每循环一圈+2
      • 定义数组loop记录循环的次数,当loop<0时终止迭代
      • 当 loop>0 时,始终按照 从左到右 从上到下 从右到左 从下到上 填入顺序循环,每循环一圈后:
        • statx++,starty++
        • offset +=2
        • loop–
      • 若n为奇数需要单独处理中心的元素
      • 最终返回mat即可
    • 时间复杂度:O(n2)
    • 空间复杂度:O(1)

5.螺旋矩阵【LC54】

给你一个 mn 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

  • 题解2 对应LC59题解2

    在循环的过程中多判定一次count < size

    class Solution {
         public List<Integer> spiralOrder(int[][] matrix) {
            int rowLen = matrix.length;
            int colLen = matrix[0].length;
            int size = rowLen * colLen;
            List<Integer> res = new ArrayList<>(size);
            int r = colLen -1,l = 0,t = 0 ,b = rowLen - 1;
            int count = 0;
    
            while (count < size){
                for (int i = l; count < size && i <= r; i++){
                    res.add(count++,matrix[t][i]);
                }
                t++;
                for (int i = t; count < size && i <= b; i++){
                    res.add(count++,matrix[i][r]);
                }
                r--;
                for (int i = r; count < size && i >= l; i--){
                    res.add(count++,matrix[b][i]);
                }
                b--;;
                for (int i = b; count < size && i >= t; i--){
                    res.add(count++,matrix[i][l]);
                }
                l++;
            }
            return res;
    
        }
    }
    

6.分割数组【LC915】

给定一个数组 nums ,将其划分为两个连续子数组 leftright, 使得:

  • left 中的每个元素都小于或等于 right 中的每个元素。
  • leftright 都是非空的。
  • left 的长度要尽可能小。

在完成这样的分组后返回 left长度

用例可以保证存在这样的划分方法。

模拟+二次遍历

2022/10/24

  • 思路:当左边数组的最大值小于右边数组的最小值时,left 中的每个元素都小于或等于 right 中的每个元素,返回下标+1

  • 实现:使用rightMin数组记录该元素右边的数组的最小值(不包括该元素),使用leftMax记录该元素左边的数组的最大值(包括该元素),当leftMax[i]<=rightMin[i]时,返回i+1

  • 代码

    class Solution {
        public int partitionDisjoint(int[] nums) {
            int len = nums.length;
            int[] leftMax = new int[len];// 记录该元素左边的数组的最大值(包括该元素)
            int[] rightMin = new int[len];// 记录该元素右边的数组的最小值(不包括该元素)
            leftMax[0] = nums[0];
            rightMin[len-1] = nums[len-1];
            for (int i = 1; i < len; i++){
                leftMax[i] = Math.max(leftMax[i-1],nums[i]);
            }
            for (int i = len-2; i >= 0; i--){
                rightMin[i] = Math.min(rightMin[i+1],nums[i+1]);
            }
            for (int i = 0; i <len; i++){
                if (leftMax[i] <= rightMin[i]){
                    return i+1;
                }
            }
            return -1;
        }
    }
    
    • 复杂度
      • 时间复杂度:O(n)
      • 空间复杂度:O(n)
  • 优化

    使用leftmax变量代替数组

    class Solution {
        public int partitionDisjoint(int[] nums) {
            int len = nums.length;
            int leftMax = 0;// 记录该元素左边的数组的最大值(包括该元素)
            int[] rightMin = new int[len];// 记录该元素右边的数组的最小值(不包括该元素)
            rightMin[len-1] = nums[len-1];
            for (int i = len-2; i >= 0; i--){
                rightMin[i] = Math.min(rightMin[i+1],nums[i+1]);
            }
            for (int i = 0; i <len; i++){
                leftMax = Math.max(leftMax,nums[i]);
                if (leftMax <= rightMin[i]){
                    return i+1;
                }
            }
            return -1;
        }
    }
    
模拟+一次遍历
  • 思路:假设一个left的划分,其最大值为maxLeft,划分位置为leftPos,此时nums[0,leftPos]均属于left。

    • 如果leftPos右侧所有元素都大于等于maxLeft,那么该划分方案合法
    • 如果在leftPos右侧找到元素nums[i]小于maxLeft,那么该划分方案不合理,此时更新leftPos = i,maxLeft = max(nums[0,i])
  • 实现:使用变量curMax维护下标i左边的元素的最大值

    class Solution {
        public int partitionDisjoint(int[] nums) {
            int n = nums.length;
            int leftMax = nums[0], leftPos = 0, curMax = nums[0];
            for (int i = 1; i < n - 1; i++) {
                curMax = Math.max(curMax, nums[i]);
                if (nums[i] < leftMax) {
                    leftMax = curMax;
                    leftPos = i;
                }
            }
            return leftPos + 1;
        }
    }
    
    作者:力扣官方题解
    链接:https://leetcode.cn/problems/partition-array-into-disjoint-intervals/solutions/1913934/fen-ge-shu-zu-by-leetcode-solution-t4pm/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
  • 复杂度

    • 时间复杂度:O(n)
    • 空间复杂度:O(1)

反转数组

1.轮换数组【LC189】

给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

  • 借助辅助数组

    class Solution {
        public void rotate(int[] nums, int k) {
            int len = nums.length;
            k = k % len;
            int[] res = new int[len];
            for (int i = 0; i < len; i++){
                res[i] = nums[(i+len-k)%len];
            }
            for (int i = 0; i < len; i++){
                nums[i] = res[i];
            }
        }
    }
    
    • 复杂度
      • 时间复杂度:O(n)
      • 空间复杂度:O(n)
  • 借助辅助变量,轮转k次

    超时

    class Solution {
        public void rotate(int[] nums, int k) {
            int len = nums.length;
            k = k % len;
            int tmp = 0;
            for (int i = 1; i <= k; i++){
                tmp = nums[len-1];
                for (int j = len - 1; j > 0; j--){
                    nums[j] = nums[j-1];
                }
                nums[0] = tmp;
            }
        }
    }
    
    • 复杂度
      • 时间复杂度:O(nk)
      • 空间复杂度:O(1)
反转字符串/反转数组

类似字符串反转的题目,

  1. 反转整个字符串
  2. 反转区间为前k的子串
  3. 反转区间为k到末尾的子串
class Solution {
    private void reverse(int[] nums, int start, int end) {
        for (int i = start, j = end; i < j; i++, j--) {
            int temp = nums[j];
            nums[j] = nums[i];
            nums[i] = temp;
        }
    }
    public void rotate(int[] nums, int k) {
        int n = nums.length;
        k %= n;
        reverse(nums, 0, n - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, n - 1);
    }
}

2.旋转矩阵【LC48】

给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。

不占用额外内存空间能否做到?

暴力
  • 思路:第i行的第 j个元素在旋转后恰好是倒数第i列的第 j个元素。因此对于矩阵中的元素 matrix[i][j],在旋转后,它的新位置为 matrix[j][matrix.length]

  • 复杂度分析

    • 时间复杂度: O ( N 2 ) O(N^2) O(N2),其中 N 是matrix 的边长。

    • 空间复杂度: O ( N 2 ) O(N^2) O(N2),其中 N 是matrix 的边长。

按规律反转
  • 如果是顺时针旋转90°
    一定是先对角线翻转,再水平翻转

  • 如果是逆时针旋转90°
    一定是先水平翻转,再对角线翻转

  • 代码

    class Solution {
        public void rotate(int[][] matrix) {
            //先对角翻转
            for (int i=0;i<matrix.length;i++){
                for (int j=i;j<matrix[i].length;j++){
                    int temp = matrix[i][j];
                    matrix[i][j] = matrix[j][i];
                    matrix[j][i] =temp;
                }
            }
            //再水平翻转
            for (int i=0;i<matrix.length;i++){
                for (int j=0;j<matrix[i].length/2;j++){
                    int temp = matrix[i][j];
                    matrix[i][j] = matrix[i][matrix[i].length-1-j];
                    matrix[i][matrix[i].length-1-j] =temp;
                }
            }
        }
    }
    
    • 复杂度分析

      • 时间复杂度: O ( N 2 ) O(N^2) O(N2),其中 N 是matrix 的边长。

      • 空间复杂度: O ( 1 ) O(1) O(1),原地反转

哈希表

1.有多少小于当前数字的数字【LC1365】

给你一个数组 nums,对于其中每个元素 nums[i],请你统计数组中比它小的所有数字的数目。

换而言之,对于每个 nums[i] 你必须计算出有效的 j 的数量,其中 j 满足 j != i 且 nums[j] < nums[i] 。

以数组形式返回答案。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/how-many-numbers-are-smaller-than-the-current-number
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

暴力
class Solution {
    public int[] smallerNumbersThanCurrent(int[] nums) {
        int lens = nums.length;
        int[] res = new int[lens];
        for (int i = 0; i < lens; i++){
            for (int j = 0; j < lens; j++){
                if (nums[j] < nums[i]){
                    res[i]++;
                }
            }
        }
        return res;
    }
}
  • 复杂度
    • 时间复杂度:O(n2)
    • 空间复杂度:O(1)
排序+哈希表
  1. 定义一个新的数组,将数组从小到大排序,那么元素的下标就代表比它小的元素的数量
  2. 利用哈希表存储数值和排序后的下标的映射,
    • 从前向后遍历sortNums,遇到相同元素时,不需要更新
    • 从后向前遍历sortNums,遇到相同元素时,需要更新
  3. 遍历原数组,找到每一个数值对应的个数
class Solution {
    public int[] smallerNumbersThanCurrent(int[] nums) {
        int lens = nums.length;
        int[] sortNums = Arrays.copyOf(nums, nums.length);
        Arrays.sort(sortNums);
        Map<Integer,Integer> map = new HashMap<>();
        int[] res = new int[lens];
        for (int i = 0; i < lens; i++){
           if (!map.containsKey(sortNums[i])){
               map.put(sortNums[i],i);
           }
        }
        for (int i = 0; i < lens; i++){
            res[i] = map.get(nums[i]);
        }        
        return res;
    }
}
  • 复杂度
    • 时间复杂度:O(nlogn)
    • 空间复杂度:O(n)
数值计算

数组元素的值域为 [0,100][0,100],所以可以考虑建立一个频次数组cnt ,cnt[i] 表示数字 i 出现的次数。那么对于数字ii 而言,小于它的数目就为 cnt[0…i−1] 的总和。

class Solution {
    public int[] smallerNumbersThanCurrent(int[] nums) {
        int[] cnt = new int[101];
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            cnt[nums[i]]++;
        }
        for (int i = 1; i <= 100; i++) {
            cnt[i] += cnt[i - 1];
        }
        int[] ret = new int[n];
        for (int i = 0; i < n; i++) {
            ret[i] = nums[i] == 0 ? 0 : cnt[nums[i] - 1];
        }
        return ret;
    }
}

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/how-many-numbers-are-smaller-than-the-current-number/solution/you-duo-shao-xiao-yu-dang-qian-shu-zi-de-shu-zi--2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 复杂度
    • 时间复杂度:O(n+k)
    • 空间复杂度:O(k)

2.独一无二的出现次数【LC1207】

给你一个整数数组 arr,请你帮忙统计数组中每个数的出现次数。

如果每个数的出现次数都是独一无二的,就返回 true;否则返回 false

哈希表
  • 利用HashMap记录每个元素出现的次数
  • 遍历map,利用HashSet记录出现的次数,若有重复则返回false
class Solution {
    public boolean uniqueOccurrences(int[] arr) {
        Map<Integer,Integer> map = new HashMap<>();
        for (int i = 0; i < arr.length; i++){
            map.put(arr[i],map.getOrDefault(arr[i],0) + 1);    
        }
        Set<Map.Entry<Integer,Integer>> set = map.entrySet();
        Set<Integer> num = new HashSet<>();
        for (Map.Entry<Integer,Integer> node:set){
            if (num.contains(node.getValue())){
                return false;
            }else{
                num.add(node.getValue());
            }
        }
        return true;

    }
}
哈希表【数组】

数组长度和以及数组中的值有限,因此可以利用数组做哈希

class Solution {
    public boolean uniqueOccurrences(int[] arr) {
        int[] count = new int[2001];
        boolean[] flag = new boolean[1001];
        for (int i = 0; i < arr.length; i++){
            count[arr[i]+1000]++;
        }
        for (int i = 0; i < count.length; i++){
            if (count[i] > 0){
                if (!flag[count[i]]){
                    flag[count[i]] = true;
                }else{
                    return false;
                }
            }
        }
        return true;

    }
}

排序

1.数组拆分【LC561】

给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), …, (an, bn) ,使得从 1 到 n 的 min(ai, bi) 总和最大。

返回该最大总和 。

作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/array-and-string/c24he/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 我的题解

    • 2021/11/13

    先对数组进行选择排序,再对下标为0,1,3……2n-1的元素求和

    class Solution {
        public int arrayPairSum(int[] nums) {
               selectSort(nums);//选择排序
               int sum = 0;
               for(int i=0;i<nums.length;i+=2){
                   sum += nums[i];
               }
               return sum;
        }
        public void selectSort(int[] nums){
            for (int i=0;i<nums.length;i++){
                
                int minIndex = i;
                for(int j=i+1;j<nums.length;j++){
                    if(nums[j] < nums[minIndex]){
                        minIndex = j;
                    }
                }
                if(i != minIndex){
                    int temp = nums[i];
                    nums[i] = nums[minIndex];
                    nums[minIndex] = temp;
                }
            }
        }
    }
    

你可能感兴趣的:(算法总结,数组,算法,java,开发语言)