数组

数组

基础知识

数组是有限个相同类型的变量所组成的有序集合,数组中的每一个变量被称为元素。数组是最简单、最常用的数据结构。

数组中的每个元素都有自己的下标(从零开始计数),在内存中是顺序存储(因此有非常高效的随机访问能力)。

数组的基本操作:

  • 读取元素:通过数组的下标 获取元素, 如 array[3];
  • 更新元素:通过数组下标将新值赋给改元素,如 array[3] = 5;
  • 插入元素:插入时需要先将插入位置之后的每元素向后移动,腾出空位,再把要插入的元素放到对应位置上;
  • 删除元素:与插入相反,要删除的元素之后的所有元素向前移动即可;

适用于读多写少的场景。

实现数组插入/删除操作的完整实现代码:

class MyArray {
  private int[] array;
  private int size;

  public MyArray(int capacity) {
    this.array = new int[capacity];
    size = 0;
  }
  
  // 插入
  public void insert(int element, int index) throw Exception{
    // 1、判断访问的下标是否超出数组范围
    if (index < 0 || index > size) {
      throw new IndexOutOfBoundsException("超出数组的实际元素范围!");
    }
    // 2、因为是插入,增加元素,需考虑数组容量是否够用,如果实际元素达到数组容量上限,则需要对数组进行扩容
    if (size >= array.length) {
      // 2.1、数组扩容
      resize();
    }
    // 3、从右向左循环,将元素逐个向右挪 1位
    for (int i = size - 1; i >= index; i--) {
      array[i+1] = array[i];
    }
    // 4、腾出的位置放置新元素,完成插入
    array[index] = element;
    // 5、当前数组大小加一
    size++;
  }
  
  // 2.1、数组扩容
  public void resize() {
    int[] newArray = new int[array.length * 2];
    // 从旧数组复制到新数组
    System.arraycopy(array, 0, newArray, 0, array.length);
    array = newArray;
  }
  
  // 方便显示,对数组进行遍历输出
  public void output() {
    for (int i = 0; i < size; i++) {
      System.out.println(array[i]);
    }
  }
  
  // 删除
  public int delete(int index) throws Exception {
    // 是否出界
    if (index < 0 || index >= size) {
      throw new IndexOutOfBoundException("超出数组实际元素范围!");
    }
    
    int deletedElement = array[index];
    
    // 从左向右 循环,将元素逐个向左移动一位
    for (int i = index; index < size - 1; i++) {
      array[i] = array[i+1];
    }
    
    // 数组计数减一
    size--;
    return deletedElement;
  }
  
  public static void main(String[] args) throws Exception{
    MyArray ma = new MyArray(4);
    ma.insert(3, 0);
    ma.insert(7, 1);
    ma.insert(9, 2);
    ma.insert(5, 3);
    ma.insert(6, 1);
    ma.output();
  }
}

删除排序数组中的重复项

leetcode 第 26 题 (简单)
要求在原地删除重复出现的元素使得每个元素只出现一次,返回移除后数组的新长度。

@Test
public void removeDuplicates() {
    // 测试样例
    int[] nums = new int[]{0, 0, 1, 1, 1, 2, 2, 3, 3, 4};
    // 慢指针 j,该指针指向的元素前是无重复数据
    int j = 0;
    for (int i = 0; i < nums.length; i++) {
    // 循环快指针 i,由于是有序数组,发现与最后一个元素不同的元素即与前面 所有元素不同,即可添加在 第 j 个元素后
        if (nums[j] != nums[i]) {
            nums[++j] = nums[i];
        }
    }
    System.out.println(Arrays.toString(nums));
}

复杂度分析:

  • 时间复杂度:O(n),假设数组长度为 n , 那么 ij 最多遍历 n步;
  • 空间复杂度:O(1), 在原数组中更新,无额外空间占用;

旋转数组

leetcode 第 189 题 (中等)
给定一个数组,将数组中的元素向右移动 k ≥ 0 k \geq 0 k0 个位置;

@Test
public void rotate() {
    int[] nums = new int[]{1, 2, 3, 4, 5, 6, 7};
    int k = 3;
    int n = nums.length;
    int count = gcd(k % n, n);
    int next;
    int tmp;
    // 4、该循环过程可能未遍历到所有元素,应该从下一个数字开始,重复过程(1-3), 重复 count 次(为什么 count = gcd(k % n, n)次?参考代码后解释)
    for (int i = 0; i < count; i++) {
        int prev = nums[i];
        next = i;
        do {
        // 1、从零开始,将 第i个元素放置在 (i+k)% n 的位置,由于 向后移动超过原数组长度 `n` 以后要补在原数组的开头,所以取 模 n;
            next = (next + k) % n;
            tmp = nums[next];
            nums[next] = prev;
            // 2、将 这轮的 next 的元素暂存,用于替换 下一轮 next 元素
            prev = tmp;
            // 3、退出条件,当回到初始位置(即 i == next)时,退出循环 
        } while (i != next);
    }
    // 输出: 5671234
    System.out.println(Arrays.toString(nums));
}

// 最大公约数,递归实现的辗转相除
private int gcd(int x, int y) {
    return y > 0 ? gcd(y, x % y) : x;
}

注:

  • 为什么 count = gcd(k % n, n)次?

从一个位置开始,向后更新,最终回到该位置,故该过程恰好走了整数数量的圈,不妨设为 a a a 圈,不难发现,每圈 有 n n n 个元素(数组的长度),则 a a a 圈掠过 a n an an个元素;
每替换一个元素掠过 k k k个元素,设回到起始位置时替换了 b b b个元素,此时掠过 b k bk bk个元素;
因此有 a n = b k an=bk an=bk,即 a n an an一定是 n , k n, k n,k的公倍数,又因为是第一次回到起始位置,因此 a a a要尽可能的小,故 a n an an一定是 n , k n, k n,k的最小公倍数 l c m ( n , k ) lcm(n,k) lcm(n,k),所以有 b = l c m ( n , k ) / k b = lcm(n,k)/k b=lcm(n,k)/k,也就是单次遍历访问到 l c m ( n , k ) / k lcm(n,k)/k lcm(n,k)/k个元素,因而遍历所有的元素需要的次数:
c o u n t = n l c m ( n , k ) / k = n k l c m ( n , k ) = g c d ( n , k ) count = \frac{n}{lcm(n,k)/k} = \frac{nk}{lcm(n,k)} = gcd(n,k) count=lcm(n,k)/kn=lcm(n,k)nk=gcd(n,k)

gcd 为最大公约数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值