【基础算法】数组篇

每天一道算法题,还来的及吗???(留下悔恨的泪水…)

0518二分查找

前提:n个元素是有序的(升序)整型数组,并且数组中的元素是不重复的

要求:若找到目标元素则返回在数组中的位置(下标),若未找到就返回-1或者其他特殊值都可

难点:对区间的定义不清楚,区间的定义就是不变量,边界值模糊不清容易出错

注意:边界值、区间的确定

方法:二分法,确定分界点mid,分成两个子集

public static int search(int[] nums, int target){
        //初始化边界值
        int left = 0;
        int right =nums.length - 1; //这里将区间确定为前闭后闭[left,right],当left=right时是有效的,所以while循环条件是<=
        // 避免当 target 小于nums[0] 大于nums[nums.length - 1]时多次循环运算,减少程序不必要的运行
        if (target < nums[0] || target > nums[nums.length - 1]) {
            return -1;
        }
        while (left <= right){
            int mid = left + ((right - left) / 2); //可以写成mid=(left+right)/2 ,但当数很大时left+right可能会溢出
            if(target < nums[mid])//注意,我们这里用的是<而不是<=,所以target不可能等于nums[mid],下面我们就将right=mid-1;
                right = mid - 1;
            else if(target > nums[mid])//解释同上了
                left = mid + 1;
            else if(target == nums[mid])
                return mid;
        }
        return -1;
    }

0518移除元素

前提:一个数组,元素没特殊要求

要求:给定目标值,将目标值在数组中找到并且移除,原地移除,空间复杂度为1,也就是不能有额外的空间开销,最后返回移除元素之后的数组长度

难点:原地删除不能有额外的空间开销,也就是说只能在原数组上面移除,而不能new一个数组来存储移除元素后的值,这相对来说会简单的多

注意:数组中的元素不能删除只能覆盖覆盖覆盖!!!就本题来说找到重复值就应该用不是重复的值来覆盖,而不是删除哦

方法:双指针左右指针初始位置在左端,左指针指向最后要返回的数组的元素,右指针去遍历原来数组的元素,当元素等于目标值的时候,左指针是不动的,右指针继续,所以左指针慢右指针快

重点知识:双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

public static int removeElement(int[] nums, int target){
        int left = 0;
        for (int right=0; right<nums.length; right++){
            if (target != nums[right]){
                nums[left] = nums[right];
                left++;
            }
        }
        return left;
}

双指针相关题目

26.删除排序数组中的重复项

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
返回 k 。

//该题目简单在于,数组是升序的,重复值都是相邻的
public int removeDuplicates(int[] nums) {
  int left = 0;
  for(int right = 1; right < nums.length; right++){
    //当两个双指针对应的元素不相等的时候,当前right指针的元素赋值给left指针的下一个位置
    if(nums[left] != nums[right]){
      nums[++left] = nums[right];     
    }
  }
  //这里返回为left+1,是因为left是数组的下标,所以实际数组长度等于下标+1
  return left+1;
}

283.移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

public void moveZeroes(int[] nums) {
  int left = 0;
  for(int right = 0; right < nums.length; right++){
    if(nums[right] != 0){
      nums[left] = nums[right];
      left++;//left最终的值其实就是非0元素的数量
    }
  }
  while(nums.length - left > 0){
    nums[left++] = 0;
  }
}

844.比较含退格的字符串

给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。

注意:如果对空文本输入退格字符,文本继续为空。

public boolean backspaceCompare(String s, String t) {
  String s1 = remove(s);
  String t1 = remove(t);
  if(s1.length() != t1.length()){
    return false;
  }
  for(int i = 0; i < s1.length(); i++){
    if(s1.toCharArray()[i] != t1.toCharArray()[i]){
      return false;
    }
  }
  return true;
}
public String remove(String s){
  char[] sc = s.toCharArray();
  StringBuffer sb = new StringBuffer();
  for(int i = 0; i < sc.length; i++){
    //不是退格键就收集结果
    if(sc[i] != '#'){
      sb.append(sc[i]);
    }else{
      //如果是退格键且不是字符串的开头位置,而且当前结果里面已经有收集字符了,那我们就要删除当前收集到的字符的最后一个字符
      //相反,如果当前退格键是字符串的第一个位置,或者当前一个字符都还没有收集,那我们之间下一步
      if(i != 0 && sb.length() >= 1){
        sb.deleteCharAt(sb.length()-1);
      }
    }
  }
  return sb.toString();
}

//双指针做该题
public boolean backspaceCompare(String S, String T) {
  int i = S.length() - 1, j = T.length() - 1;
  int skipS = 0, skipT = 0;
  while (i >= 0 || j >= 0) {
    while (i >= 0) {
      if (S.charAt(i) == '#') {
        skipS++;
        i--;
      } else if (skipS > 0) {
        skipS--;
        i--;
      } else {
        break;
      }
    }
    while (j >= 0) {
      if (T.charAt(j) == '#') {
        skipT++;
        j--;
      } else if (skipT > 0) {
        skipT--;
        j--;
      } else {
        break;
      }
    }
    if (i >= 0 && j >= 0) {
      if (S.charAt(i) != T.charAt(j)) {
        return false;
      }
    } else {
      if (i >= 0 || j >= 0) {
        return false;
      }
    }
    i--;
    j--;
  }
  return true;
}

977.有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

public static int[] sortedSquares(int[] nums) {
  int len = nums.length;
  int[] res = new int[len];
  int left = 0, right = len - 1, k = len;
  while(left <= right){
    if (Math.abs(nums[left]) < Math.abs(nums[right])){
      res[--k] = nums[right] * nums[right];
      right--;
    }else{
      res[--k] = nums[left] * nums[left];
      left++;
    }
  }
  return res;
}

80.删除有序数组中的重复项 II

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

public int removeDuplicates(int[] nums) {
  if(nums.length <= 2){
    return nums.length;
  }
  int left = 2, right = 2;
  for (; right < nums.length; right++){
    if(nums[left - 2] != nums[right]){
      nums[left++] = nums[right];
    }
  }
  return left;
}

0519有序数组的平方

前提:给定数组有序(升序),其中包括负数,

要求:求每一个元素的平方(简单),最后将求得的平方之后的数组有序(升序)排列

难点:如果用暴力解法就是先循环遍历得到平方后的值,然后再循环遍历排序,时间复杂度很高,怎么实现一边遍历求平方,一边就将排序做好?

注意:数组是升序排列,较大的值按理说应该在最右边,但是由于其中包含负数,所以最大值有可能在最左边,可以确定的是最大值一定不在数组的中间,而是在两端

易点:可以在原数组上平方,但是不能在原数组上排序,可以开辟新的数组来存排序后的结果,这就很容易了,想着也是找到一个最大值就从新建的数组的最右边依次放入,最后返回该数组即可

思想:暴力解法的思想就是先求平方,然后再排序咯,但是考虑时间复杂度这样应该不好

关键:双指针,相对于从数组中间位置去找平方后的最小值,由上述的分析易知更简单的是从两边去找最大值,所以双指针分别从两端向中间逼近,当left > right时,数组就遍历完啦

方法:双指针,左右指针分别从两端向中间移动

public static int[] sortedSquares(int[] nums) {
        int len = nums.length;
        int left = 0, right = len - 1;
        int k = len;
        int[] result = new int[k];
        while (left <= right){
            if (nums[left]*nums[left] > nums[right]*nums[right]){//左边平方后大于右边,左指针++
                result[--k] = nums[left]*nums[left];
                left++;
            }else{ //左边平方后小于等于右边,右指针--
                result[--k] = nums[right]*nums[right];
                right--;
            }
        }
        return result;
    }

0519长度最小的子数组

前提:n个正整数数组和一个正整数target

要求:在数组中找到求和>=target的长度最小的子数组

难点:怎么找到求和为target但是长度要最短,怎么比较?都存起来?不可能啊,只能是存一个当前最小值继续遍历满足条件来,又替换存当前最小值

方法:滑动窗口(双指针),左右指针初始位置在左端,右指针跑的快,左指针跑的慢,在两个指针确定的这个窗口中满足求和>=target,然后求出子数组的长度,和前面存的长度进行比较

注意:这里是求大于等于target的长度最小子数组,不是必须等于target的哦!

重点知识:滑动窗口:所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,其实也可以理解为双指针的一种

  • 对应在暴力解法中,一个for循环控制滑动窗口的起始位置,一个for循环控制滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。
public static int minSubArrayLen(int[] nums, int target){
        int left=0, right=0, subLength=0, sum=0, result=Integer.MAX_VALUE;
        for (; right<nums.length; right++){//右指针跑的快
            sum += nums[right];
            while (sum >= target){
                subLength = right - left + 1;//计算子数组的长度
                if (subLength < result){//比较当前子数组的长度是不是小于已经求出的子数组长度
                    result = subLength;
                }
                sum -= nums[left++];//去掉一个元素,再来判断是不是满足>=target的,如果满足子数组长度就继续减小了呀
              	//这里需要注意nums[left++],是先使用nums[left]的值,然后left = left-- 的
              	//拆成两部分写
              	sum -= nums[left];
              	left--;
            }
        }
        return result == Integer.MAX_VALUE ? 0:result;
    }

0520螺旋矩阵

前提:输入大于0小于等于20的正整数n,生成n*n的正方形矩阵

要求:元素按顺时针螺旋状遍历给正方形矩阵赋值,值是按照1,2,3,4,5,,,这样递增的

难点:找出规律、边界值的确定,坚持循环不变量原则;重点考察对代码的掌控能力。

方法:按层模拟,顺时针一圈就是一层

public static int[][] generateMatrix(int n){
        int[][] matrix = new int[n][n];
        int num = 1;
        int left = 0, right = n - 1, up = 0, down = n-1;
        while(num <= n*n){
            for (int i = left; i <= right; i++){ //从左至右,列索引i++,当列索引i>right跳出循环,一行访问完成
                matrix[up][i] = num++;       //这里需要注意的是用i作为中间变量,因为此时的left是作为不变量的
            }
            up++; //从上某一行访问完毕,将up边界下移
            for (int j = up; j <= down; j++){ //从上至下,行索引j++,当行索引j>down跳出循环,一列访问完成
                matrix[j][right] = num++;
            }
            right--; //从右某一列访问完毕,将right边界左移
            for (int p = right; p >= left; p--){ //从右至左,列索引p--,当列索引p<left跳出循环,一行访问完毕
                matrix[down][p] = num++;
            }
            down--; //从下某一行访问完毕,将down边界上移
            for (int q = down; q >= up; q--){ //从下至上,行索引q--,当行索引q<up跳出循环,一列访问完毕
                matrix[q][left] = num++;
            }
            left++; //从左某一列访问完毕,将left边界右移
        }
        return matrix;
    }

总结

数组是较为基础的数据结构,一般题目的思想都不难,重点在于我们对代码的掌控能力。

数组是存放在连续内存空间上的相同类型数据的集合,正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。

注意:数组的元素是不能删除的,只能覆盖!

1.二分法

循环不变量原则,只有在循环中坚持对区间的定义,才能清楚的把握循环中的各种细节。

2.双指针法

双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

双指针法(快慢指针法)在数组和链表的操作中是非常常见的

3.滑动窗口

主要要理解滑动窗口如何移动 窗口起始位置,窗口结束位置,达到动态更新窗口大小的,怎么去正确的控制

滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。

4.模拟行为

不涉及到什么算法,就是单纯的模拟,十分考察对代码的掌控能力。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值