算法通关村第3关 | 不简单的数组增删改查

数组基本操作

数组创建和初始化

int[] arr = new int[10];
int[] arr = new int[]{0,1,2,3,5,6,8};
//这么写也行:
int[] nums = {2, 5, 0, 4, 6, -10};

查找一个元素

/**
* @param size 已经存放的元素个数
* @param key  待查找的元素
  */
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;
}

增加一个元素

/**
     * @param arr
     * @param size    数组已经存储的元素数量,从1开始编号
     * @param element 待插入的元素
     * @return
     */
public static int addByElementSequence(int[] arr, int size, int element) {
    //问题①:是否应该是size>arr.length
    if (size >= arr.length)
        retrun -1;

    //问题②想想这里是否是index=0或者size-1?
    int index = size;
    //找到新元素的插入位置,问题③ 这里是否应该是size-1?
    for (int i = 0; i < size; i++) {
        if (element < arr[i]) {
            index = i;
            break;
        }
    }
    //元素后移,问题④想想这里为什么不是size-1
    for (int j = size; j > index; j--) {
        arr[j] = arr[j - 1]; //index下标开始的元素后移一个位置
    }
    arr[index] = element;//插入数据
    return index;
}

上面的代码在往期课程里被提出疑问特别多,主要是标记编号的几个位置,这几个全都是边界问题。这里回答几个:
问题①处,注意这里的size是从1开始编号的,表示的就是实际元素的个数。而arr.length也是从1开始的,当空间满的时候就是size=arr.length,此时就不能再插入元素了。
问题② 处只能令index=size, 0或者size-1都不对。例如已有序列为{3,4,7,8},如果插入的元素比8大,例如9,假如index=0,则最后结果是{9,3,4,7,8}。假如index=size-1,最后结果就是{3,4,7,9,8}。
问题③和④处,这个就不用解释了吧,请读者自己思考。

除了上面的方式,还可以一开始就从后向前一边移动一边对比查找,找到位置直接插入。从效率上看这样更好一些,因为只遍历了一次

/**
     * 从后往前遍历,插入
     * @param arr
     * @param size    数组已经存储的元素数量
     * @param element 待插入的元素元素
     * @return
     */
public static int addByElementSequence2(int[] arr, int size, int element) {
    if (size >= arr.length)
        return -1;
    int index = 0;
    for (int i = size; i > 0; i--) {
        if (element >= arr[i - 1]){
            index = i;
            break;
        }
        arr[i] = arr[i - 1];
    }
    arr[index] = element;
    return index;
}

删除一个元素

/**
     * 从数组中删除元素key
     * @param arr 数组
     * @param size 数组中的元素个数,从1开始 
     * @param key   删除的目标值
     */
public  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;
}

算法热身——单调数组问题

先看个热身问题,我们在写算法的时候,数组是否有序是一个非常重要的前提,有或者没有可能会采用完全不同的策略。 LeetCode 896.判断一个给定的数组是否为单调数组。
分析:如果对于所有 i <= j,A[i] <= A[j],那么数组 A 是单调递增的。 如果对于所有 i <= j,A[i]> = A[j],那么数组 A 是单调递减的。所以遍历数组执行这个判定条件就行了,由于有递增和递减两种情况。于是我们执行两次循环就可以了,代码如下:

public  boolean isMonotonic(int[] nums) {
    return isSorted(nums, true) || isSorted(nums, false);
}

public  boolean isSorted(int[] nums, boolean increasing) {
    int n = nums.length;
    for (int i = 0; i < n - 1; ++i) {
        if(increasing){
            if (nums[i] > nums[i + 1]) {
                return false;
            }
        }else{
            if (nums[i] < nums[i + 1]) {
                return false;
            } 
        }          
    }
    return true;
}

这样虽然实现功能了,貌似有点繁琐,而且还要遍历两次,能否优化一下呢?假如我们在i和i+1位置出现了nums[i]>nums[i+1],而在另外一个地方j和j+1出现了nums[j]<nums[j+1],那是不是说明就不是单调了呢?这样我们就可以使用两个变量标记一下就行了,代码如下:

public boolean isMonotonic(int[] nums) {
    boolean inc = true, dec = true;
    int n = nums.length;
    for (int i = 0; i < n - 1; ++i) {
        if (nums[i] > nums[i + 1]) {
            inc = false;
        }
        if (nums[i] < nums[i + 1]) {
            dec = false;
        }
    }
    return inc || dec;
}

我们判断整体单调性不是白干的,很多时候需要将特定元素插入到有序序列中,并保证插入后的序列仍然有序,例如leetcode35:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

示例1:
输入: nums = [1,3,5,6], target = 5
存在5,并且在索引为2的位置,所以输出: 2

示例2:
输入: nums = [1,3,5,6], target = 2
不存在2,2插入之后在索引为1的位置,所以输出: 1

这个问题没有让你将新元素插入到原始序列中,还是比较简单的,只要遍历一下就找到了。如果面试官再问你,该如何更快的找到目标元素呢?那他其实是想考你二分查找。以后凡是提到在单调序列中查找的情况,我们应该马上想到是否能用二分来提高查找效率。二分的问题我们后面专门讨论,这里只看一下实现代码:

public 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:给你两个按非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 应忽略。nums2 的长度为 n 。

例子1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:合并 [1,2,3] 和 [2,5,6] 的结果是 [1,2,2,3,5,6]
public  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);
}

比较好的方式是从后向前插入,A和B的元素数量是固定的,所以排序后最远位置一定是A和B元素都最大的那个,依次类推,每次都找最大的那个从后向前填就可以了,代码如下:

public void merge(int[] nums1, int nums1_len, int[] nums2, int nums2_len) {
    int i = nums1_len + nums2_len - 1;
    int len1 = nums1_len - 1, len2 = nums2_len - 1;
    while (len1 >= 0 && len2 >= 0) {
        if (nums1[len1] <= nums2[len2])
            nums1[i--] = nums2[len2--];
        else if (nums1[len1] > nums2[len2])
            nums1[i--] = nums1[len1--];
    }
    //假如A或者B数组还有剩余
    while (len2 != -1) nums1[i--] = nums2[len2--];
    while (len1 != -1) nums1[i--] = nums1[len1--];
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值