Carl代码随想录算法训练营-Day 1 -数组基础、704. 二分查找、27. 移除元素

本文介绍了数组的基础知识,包括其在计算机中的存储结构、遍历方法以及数组操作(如二分查找和移除元素)的原理和实现,特别是通过二分查找算法的详细步骤和移除元素的两种解决方案。作者强调了算法思维的重要性,即使于看似复杂的任务,通过不断实践也能找到简洁的解决方法。
摘要由CSDN通过智能技术生成

Carl代码随想录算法训练营-Day 1 -数组基础、704. 二分查找、27. 移除元素

数组基础知识心得

  1. 数组在计算机当中是一片连续的存储空间,也就是说假如数组0号元素的地址是a的话,那么后面数组元素的地址=a+数组下标*元素字节数
  2. 数组的起始下标是0,终止下标是数组长度-1,这与我们“第一个元素、第二个元素……第n个元素”的直觉不一样。遍历数组的时候可以使用for(int i=0;i<array.length;i++){},最普遍。
  3. 数组的大小是初始化之后就不能变的,在Java编程中如果不明确需要的数组大小,就可以用ArrayList。
  4. 数组的查询非常迅速,时间复杂度是O(1);插入和删除操作相对较慢,时间复杂度是O(n)。

算法题目

二分查找

704、二分查找
这个是二分查找梦开始的地方了。回想了一下二分查找的思路大致就是,数组必须是排好序的。用数组正中间的元素去和目标元素比大小,如果前者比后者大,就到前者左边那一半数组去找,否则到右边那一半去找。然后在新的一半数组中继续这个过程。大致流程应该是这样:

Created with Raphaël 2.3.0 数组和搜索目标t 更新边界[左,右] 中间下标mid [mid]==t? 找到了 [mid]>t? 右=mid-1 左>右? 不存在 左=mid+1 yes no yes no yes no

二分查找需要注意的主要就是边界的问题,左闭右闭还是左闭右开。我选择左闭右闭,看着舒服。
其中在计算mid的时候,还是要注意会不会溢出,这跟题目描述的数组元素的大小范围有关系。最好就是固定用a+(b-a)/2代替(a+b)/2,计算a+b的时候超出整型范围。

public int search(int[] nums, int target) {
        int low = 0, high = nums.length - 1;
        while (low <= high) {
            int mid = ((high - low) >> 1) + low;
            if (target < nums[mid]) {
                high = mid - 1;
            } else if (target > nums[mid]) {
                low = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

思维是很好理解的。但是写代码的时候细节需要把握好,就是在于循环终止的判断条件和边界的更新。
二分查找的时间复杂度是O(logn),空间复杂度是O(1),相当高效!

移除元素

27、移除元素
在看到这个题目的时候,直觉就告诉我暴力解题肯定报用,如果放在企业面试在线笔试之类的,肯定会给很长的数组,导致超出时间限制。
暴力法就是用一个for循环遍历数组,在碰到需要删除的元素时,就再用一个for循环将其后面所有的元素挨个向前挪一位。这个算法的时间复杂度将时O(n^2)。
双指针法最先想到的,是用一个指针从前面遍历数组,用另一个指针指向数组的尾巴,当碰到需要删除的元素的时候,把后面的指针的元素填到这个位置来(因为最终的数组不要求顺序)。但是后面指针所指的元素,要考虑会不会恰好就等于需要删除的元素,如果是的话就需要进行处理。想法都很简单明了,但是写起来却是另一回事。

//前后指针
public int removeElement(int[] nums, int val) {
        int len = nums.length;
        for (int i = 0; i < len; i++) {
            if (nums[i] == val) {
                while (nums[len - 1] == val) {
                    if (len - 1 == i) return len - 1;
                    len--;
                }
                nums[i] = nums[len - 1];
                len--;
            }
        }
        return len;
    }

在调出这段代码之后,我不禁沉思,难道代码非要这么复杂么。仔细思考了一下,看了一下代码随想录提供的思路,原来两个指针都从前面开始遍历也是可以的,这就是快慢指针法
经过反复思考,反复调试,终于写出以下代码,竟然和卡哥给出的代码一模一样!
快指针正常遍历数组,当碰到的是要删除的元素的时候,慢指针就留在原地,表示这个位置的元素已经删掉了,当快指针再往后遍历到非删除元素的时候,就将快指针的元素补到慢指针的位置。
看似明了的想法,如果不是反复摸索,真的难以想到!

public int removeElement(int[] nums, int val) {
        int j = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != val) {
                nums[j++] = nums[i];
            }
        }
        return j;
    }

这就是典型的,思维上面很精妙,代码表现出来很简单简洁的算法,希望以后都能写出这种代码。

小结

第一天就上了这么两盘大菜,真的学渣哭泣。但是本学渣相信,只要脚踏实地地,用自己的双手去敲够足够的代码,那学渣也能逐渐成为,能解决别的学渣解决不了的问题的学渣!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值