算法通关村——不简单的数组增删改查

基本概念

线性表

存储的角度

顺序型就是将数据存放在一段固定的区间,访问元素效率高,删除和增加元素代价大(效率低)

                                                                     扩容只能整体迁移

链表型,元素之间通过地址依次连接,访问的时候从头开始逐步向后遍历,查找效率低

                                                             删除和增加元素方便(无需扩容)

访问限制的角度

栈和队列称为访问受限的线性表,插入和删除存在限制,只能在固定位置进行

Hash内部存储数据是数组,访问是通过映射来实现

线性表常见操作:初始化、求表长、增删改查等

扩容角度

扩容策略

第一种:每次扩充增加固定数目的存储位置(线性增长)

                特点:节省空间,但操作次数多

第二种:每次扩充容量加倍

              特点:减少扩充操作次数(以空间换时间)

数组存储元素特征

1)数组默认初始化为0

2)初始化本质是覆盖已有的值

3)初始化必须从前向后的连续空间 初始化,不可以出现空缺的情况

数组基本操作

数组创建和初始化

int[] nums = {2,5,0,4,6,-10};

查找一个元素

数组由于查找效率高,所以很多题目本质是查找问题需要利用数组来解决问题

包括很多算法也是为了提高查找效率,例如:二分查找、二叉树、红黑树、Hash和堆等

    也有很多算法问题本质是查找问题,例如:滑动窗口问题、回溯问题、动态规划问题等

//查找时如果相等或者当前位置元素比目标值更大就停下了
public static int findByElement(int[] arr, int size, int key) {
        for (int i = 0; i < size; i++) {
            if (arr[i] >= key)
                return i;
        }
        return -1;
    }

增加一个元素

数组的增加和删除元素是基本操作,但容易在处理下标与边界值的关系出现问题=

例题:将给定的元素插入到有序数组的对应位置中

public static int addByElementSequence(int[] arr, int size, int element) {
        //size和arr.length都表示元素的数量,都是从1开始编号
        if (size >= arr.length)
            throw new IllegalArgumentException("Add failed. Array is full.");

        //如果遍历完整个数组都没有找到插入位置,说明直接在数组最后插入
        int index = size;
        //找到新元素的插入位置
        for (int i = 0;i<size;i++){
            if (element < arr[i]){
                index = i;
                break;
            }
        }
        //找到插入位置后开始插入,整体后移挪出位置
        //凡是大于index的数组元素都往后移动一位
        for (int i =size;i>index;i--){
            arr[i] = arr[i-1];
        }
        arr[index] = element;//插入数据
        return index;
    }

删除一个元素

删除的话就不能实现一边从后向前移动一边查找,元素可能不存在

只能从前往后遍历

//如果删除失败则返回数组原来长度
public static int removeByElement(int[] arr, int size, int key) {
        int index = -1;
        for (int i = 0; i < size; i++) {
            if (arr[i] == key) {
                index = i;
                break;
            }
        }
        if (index != -1) {
            for (int i = index + 1; i < size; i++)
                arr[i - 1] = arr[i];
            size--;
        }
        return size;
    }

单调数组问题

leetcode896

思路分析:

单调有递增和递减两种情况,所以可以在一次遍历中同时检查单调递增和单调递减

                                             如果在遍历违背了单调递增或递减的顺序,将相应的布尔标志设为false

  public boolean isMonotonic(int[] nums) {
          //如果遍历完到最后仍未true说明数组单调
          boolean inc = true;
          boolean dec = true;
          for(int i =0;i<nums.length-1;i++){
              if(nums[i]>nums[i+1]){
                  inc = false;
              }else if(nums[i] < nums[i+1]){
                  dec = false;
              }
          }
          return (inc || dec);
    }

判断单调性可以在查找的过程中利用到二分查找来提高查找效率

思路分析::

1. **初始化阶段**:
  n是数组 nums的长度。       left`和 right 是我们要在其中搜索的数组段的开始和结束索引                                                    开始时,left` 为0,`right` 为 `n-1`。
   ans` 被初始化为 `n`,它是一个备选的插入位置。如果 `target` 大于数组中的所有元素,       ans将保持为n,表示 target 应该被插入到数组的末尾。

2. 二分查找循环:
   当 left 不大于 right 时,说明还没有到循环位置,则循环继续。
   计算中间位置 mid。这里,我们使用位操作 >> 1 来代替除以2,因为位操作更快。           (right - left) >> 1 为数组段长度的一半,加上 left 就得到了中间位置。
   
3. 比较阶段:
   3.1如果 target 小于或等于 nums[mid],这意味着目标值应该在数组的左半段或恰好在 mid 位置。
  3.2我们更新 `ans` 为 `mid`,因为这可能是 `target` 的位置或 `target` 应该插入的位置。
  3.3然后,我们更新 `right` 为 `mid - 1`,因为我们知道 `target` 不可能在 `mid` 的右侧。
  3.4否则,`target` 大于 `nums[mid]`,这意味着 `target` 应该在数组的右半段。
  3.5所以,我们只更新 `left` 为 `mid + 1`。
   
4. 返回结果:
   4.1当循环结束时,`ans` 将包含 `target` 在数组中的位置,或者如果 `target` 不在数组中,它将包含 `target` 应该插入的位置。
   4.2最后,`ans` 被返回

//二分查找
public static int searchInsert(int[] nums, int target) {
        int n = nums.length;
        int left = 0, right = n - 1, ans = n;
        while (left <= right) {
            int mid = ((right - left) >> 1) + left;
            if (target <= nums[mid]) {
                ans = mid;
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }

数组合并

leetcode88

题目:合并两个有序数组

解决方案:

方法一:先将B直接合并到A后面,然后再对A排序(没有技术含量)

public static void merge1(int[] nums1, int nums1_len, int[] nums2, int nums2_len) {
        for (int i = 0; i < nums2_len; ++i) {
            nums1[nums1_len + i] = nums2[i];
        }
        Arrays.sort(nums1);
    }

方法二:借助一个新数组C,将选择好的放入到新数组1中去,时间复杂度O(n)

方法三:从后向前插入,由于A和B元素为有序数组,排序最后位置就是A和B元素最大,每次找最大从后向前填(推荐)

 public void merge(int[] nums1, int m, int[] nums2, int n) {
         //数组从0开始遍历
         int length = m+n-1;
         int index1 = m-1;
         int index2 = n-1;
         //从后往前填入到num1中
         while(index1>=0 && index2>=0){
             if(nums1[index1]>=nums2[index2]){
                 nums1[length--] = nums1[index1--];
             }else{
                 nums1[length--] = nums2[index2--];
             }
         }
         //nums2有剩余
         while(index2>=0){
             nums1[length--] =  nums2[index2--];
         }
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值