数组

数组相关算法

 

排序

快排

核心思想是选定一个阈值temp

通过左右指针,将比temp小的放左边,比temp大的放右边。

对两个子序列重复排序。

public static void quickSort(int[] nums){
    doQuickSort(nums,0,nums.length-1);
}
public static void doQuickSort(int[] nums, int left, int right){
    if (left>=right){
        return;
    }
    int temp=nums[left];
    int min=left;
    int max=right;
    while (min<max){
        //右指针左移
        while (min<max&&nums[max]>=temp){
            max--;
        }
        //将找到的小于标志数的放在左指针上,同时左指针右移
        if (min<max){
            nums[min]=nums[max];
            min++;
        }
        //左指针右移
        while (min<max&&nums[min]<=temp){
            min++;
        }
        //将找到的大于标志数的放在右指针上,同时右指针左移
        if (min<max){
            nums[max]=nums[min];
            max--;
        }
    }
    nums[min]=temp;
    doQuickSort(nums,left,min-1);
    doQuickSort(nums,min+1,right);
}

二分查找

二分的前提是有序,通过规定两个边界,每次比较中间的值是否是目标值来决定下次边界的范围。

代码随想录中对边界的总结挺有特点的,在我个人的感知中左开右闭这种处理,既不美观,也不好懂,因此只需要记住left<=right的这种处理就好。

力扣-35搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

public int searchInsert(int[] nums, int target) {
        int left=0,right=nums.length-1;
        while(left<=right){
            int mid=left+(right-left)/2;
            if(nums[mid]==target){
                return mid;
            }else if(nums[mid]>target){
                right=mid-1;
            }else{
                left=mid+1;
            }
        }
        //这里需要思考一下
        //当进入到最后一次循环的时候,由于left=right,那么mid=left=right;
        //所以对于if的三种情况,第一种相等就直接返回下标.
        //后两种,从left角度分析,如果nums[left]>target,那么left正好是要被插入的位置;如果nums[left]<target,那么要插入的位置就是下一个,即left+1,刚好在else中的逻辑就是这样,因此,最后虽然只是return left,但是会根据不同的情况落到不同的位置
        // return left
        //那如果以right角度分析呢,如果nums[right]>target,那就应该返回right,但是由于在elseif中right-1,因此right还得再加回来即right+1,如果nums[right]<target,那就应该返回right+1;
        return right+1;
    }

力扣-69x的平方根

力扣-69x的平方根

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

思路:

其实和上面的题一样,换个说法就是在1,x中,寻找sqrt(x)。但是需要额外注意的有两点,

1是返回值,由于返回的是取整的结构,从上面的分析可以知道return left表示的是返回结果将要占得位置,2.x之类的结果会得到3,所以需要返回left-1或者right。

public int mySqrt(int x) {
        int left=1,right=x;
        while(left<=right){
            int mid=left+(right-left)/2;
            long sum=(long)mid*mid;
            if(sum==x){
                return mid;
            }else if(sum>x){
                right=mid-1;
            }else{
                left=mid+1;
            }
        }
        return right;
    }

双指针

移除元素

力扣27移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

使用双指针,start指针用于记录最终的返回结果,当nums[start]和val不等,表示要保留 因此指针要++,反之则表示当前位置的这个值可以被替换,因此需要另一个指针遍历所有值来进行比较

    public int removeElement(int[] nums, int val) {
        int start=0,cur=0;
        while(cur<nums.length){
            if(nums[cur]!=val){
                nums[start++]=nums[cur];
            }
            cur++;
        }
        return start;
    }

比较含退格的字符串

力扣844. 比较含退格的字符串

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

思路一:

使用双指针覆写的思想,当遇到#代表start指针可以被覆写,和移除元素的思想很像。这种方法也可以使用栈的思想,当不等于#号就入栈,等于#号就弹出顶部的元素。最后比较栈内的结果。

class Solution {
    public boolean backspaceCompare(String s, String t) {
        return doSolution(s).equals(doSolution(t));
    }
    public String doSolution(String s){
        int start=0,cur=0;
        char[] arr=s.toCharArray();
        while(cur<s.length()){
            if(arr[cur]=='#'){
                if(start>0){
                    start--;
                }
                cur++;
                continue;
            }
            arr[start++]=arr[cur];
            cur++;
        }
        StringBuilder build=new StringBuilder();
        for(int i=0;i<start;i++){
            build.append(arr[i]);
        }
        return build.toString();
    }
}

思路二:

上面其实已经用到了双指针,但是会发现必须遍历完整个字符串,才能确定最终是什么,那有没有可能在遍历的过程中直接比较呢?

正着遍历肯定不行,因为比较的时候不清楚后面是不是会删除当前字符,但如果反着遍历呢?

假如反着遍历,当前字符是#,那么表明遇到的下一个不是#的字符要被删除,跳过就好了。

假如字符不是#,那就要看当前字符是否要被删除(即遍历到当前字符的过程中有几个#),要么被删除,要么#号不足保留,同时比较两个字符串每一轮保留的字符,既可以确定是否是相同的字符串了。

另外,两个字符串长度不等也不相同。

public boolean backspaceCompare(String s, String t) {
        int i = s.length() - 1, j = t.length() - 1;
        int delI = 0, delJ = 0;
        while (i >= 0 || j >= 0) {
            //跳出条件为当前不是#,且没有删除标记
            while (i >= 0) {
                //如果是#号,那么增加删除标记
                if (s.charAt(i) == '#') {
                    delI++;
                //如果不是#号,且存在删除标记,那么就减少删除标记,即当前元素被删除
                } else if (delI > 0) {
                    delI--;
                //直到不能删除,那就表示这一位一定在了
                } else {
                    break;
                }
                i--;
            }
            while (j >= 0) {
                if (t.charAt(j) == '#') {
                    delJ++;
                } else if (delJ > 0) {
                    delJ--;
                } else {
                    break;
                }
                j--;
            }
            //如果都没走到头,且当前字符不相等,那表明两个字符串不相等
            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,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

示例 1:

  • 输入:nums = [-4,-1,0,3,10]
  • 输出:[0,1,9,16,100]
  • 解释:平方后,数组变为 [16,1,0,9,100],排序后,数组变为 [0,1,9,16,100]

示例 2:

  • 输入:nums = [-7,-3,2,3,11]
  • 输出:[4,9,9,49,121]

思路:

首先可以想到通过双指针来比较最左和最右两个位置的平方值,可以得出一个较大值并将指针移动。但是不能在原数组原地移动,因为单纯的交换值并不能做到其他位置一起移动。因此需要额外借助另一个数组,即每次将较大值放入result[n–]中去。

class Solution {
    public int[] sortedSquares(int[] nums) {
        int n = nums.length;
        int[] resultArray = new int[n];
        int left = 0, right = n - 1;
        while (left <= right) {
            int leftValue = nums[left] * nums[left];
            int rightValue = nums[right] * nums[right];
            int value = leftValue;
            if (leftValue > rightValue) {
                left++;
            } else {
                value = rightValue;
                right--;
            }
            resultArray[--n] = value;
        }
        return resultArray;
    }
}

滑动窗口

长度最小的子数组

长度最小的子数组

看到题之后首先想到的肯定就是滑动窗口了,而滑动窗口基本上也都是双指针实现,通过控制两个指针来表示窗口。因此这个题我们首先要不断右移指针来判断是否满足条件,当满足条件之后再不断缩小窗口(即移动左指针)。

注意看题,题目中说的是总和大于等于 target,而不是等于,如果是等于len那里要额外判断的。

public int minSubArrayLen(int target, int[] nums) {
        int start = 0, end = 0;
        int sum = 0, len = nums.length;
        boolean flag = false;
        while (end < nums.length) {
            sum = sum + nums[end];
            while (sum >= target) {
                len = Math.min(len, end - start + 1);
                flag = true;
                sum -= nums[start];
                start++;
            }
            end++;
        }
        return flag ? len : 0;
    }

水果成篮

水果成篮

依然是滑动窗口算法,但是窗口里记录什么决定了代码怎么写,下面这种方式好理解且简单,窗口中记录的是每个种类出现的次数,这样子,就是简单的移入,移除就可以了。

而如果是记录的每个种类出现的位置,需要额外关心两个点:

第一个是11223和112213这两种情况不同处理,当size大于2的时候,不能简单的移除left,由于不好决定移除哪个,所以就只能clear重新添加了

第二个112213,map获取上次出现的地址,应该获取最新的位置,而不是最开始的位置

这里我取巧了:当两个相邻的元素不同时表明元素位置需要刷新了。

public int totalFruit(int[] fruits) {
        int left = 0, right = 0,len=0;
        HashMap<Integer, Integer> map = new HashMap<>();
        while (right < fruits.length) {
            map.put(fruits[right], map.getOrDefault(fruits[right], 0) + 1);
            while (map.size() > 2) {
                map.put(fruits[left], map.getOrDefault(fruits[left], 0) - 1);
                if (map.get(fruits[left]) == 0) {
                    map.remove(fruits[left]);
                }
                left++;
            }
            len=Math.max(len,right-left+1);
            right++;
        }
        return len;
    }

    public  int totalFruit(int[] fruits) {
        if(fruits.length<=2){
            return fruits.length;
        }
        int len=0;
        int start=0,end=0,n=fruits.length-1;
        HashMap<Integer,Integer> kindMap=new HashMap<>();
        while(end<=n){
            if(!kindMap.containsKey(fruits[end])||
            (kindMap.size()==2&&fruits[end]!=fruits[end-1])
            ){
                kindMap.put(fruits[end],end);
            }
            //当篮子满了的时候
             if(kindMap.size()>2){
                len=Math.max(len,end-start);
                start=kindMap.get(fruits[end-1]);
                kindMap.clear();
                kindMap.put(fruits[end],end);
                kindMap.put(fruits[end-1],start);
            }
            len=Math.max(len,end-start+1);
            end++;
        }
        return len;
    }

螺旋矩阵2

给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。

主要是边界情况的处理,我习惯的做法是完整写完一个方向后就移动边界。对于这种处理方案,第一次是处理的最上方,因此第一行写完后top要进行++,然后处理最右边这列,right–,以此类推。

public  static int[][] generateMatrix(int n) {
        int[][] result = new int[n][n];
        int count = 1, target = n * n;
        int left = 0, right = n - 1, top = 0, bottom = n - 1;
        int i , j ;
        while (count <= target) {
            for ( i = left; count <= target&&i <= right; i++) {
                result[top][i] = count++;
            }
            top++;
            for ( j = top; count <= target&&j <= bottom; j++) {
                result[j][right] = count++;
            }
            right--;
            for ( i = right;count <= target&& i >= left; i--) {
                result[bottom][i] = count++;
            }
            bottom--;
            for ( j = bottom; count <= target&&j >= top; j--) {
                result[j][left] = count++;
            }
            left++;
        }
        return result;
    }
Licensed under CC BY-NC-SA 4.0
最后更新于 2024-10-18