算法进阶篇 - 数组

二分查找

704 二分查找 拓展

  • 35.搜索插入位置
  • 34.在排序数组中查找元素的第一个和最后一个位置
  • 69.x 的平方根
  • 367.有效的完全平方数

以上是二分查找的拓展

35 搜索插入位置

和平常的二分查找一样 - 注意定义区间

难点:返回时是 right + 1 - 列举几个情况,寻找结果与mid left right 的关系规律

public int searchInsert(int[] nums, int target) {
    int left = 0;
    // 原则左闭右闭
    int right = nums.length - 1;
    int mid = 0;

    while (left <= right){
        mid = (left + right) / 2;
        if (nums[mid] == target) return mid;
        else if (nums[mid] > target) {
            right = mid - 1;
        } else if (nums[mid] < target) {
            left = mid + 1;
        }
    }
    return right + 1;


}

34 在排序数组中查找元素的第一个和最后一个位置

和平常的二分查找一样

查找完之后滑动指针寻找左右边界

public int[] searchRange(int[] nums, int target) {
    // 二分查找
    int index = midSearch(nums, target);
    if (index == -1) return new int[]{-1,-1};
    else {
        // 滑动左右指针
        int left = index;
        int right = index;

        while (left - 1 >= 0 && nums[left - 1] == target)
        	left--;
        while (right + 1 < nums.length && nums[right + 1] == target)
        	right++;
        return new int[]{left,right};
    }


}

private int midSearch (int[] nums, int target){
    int left = 0;
    int right = nums.length - 1;

    while (left <= right){
        int mid = (left + right) / 2;
        if (nums[mid] > target){
            right = mid - 1;
        } else if (nums[mid] < target) {
            left = mid + 1;
        }
        else return mid;
    }
    return -1;
}

69 x 的平方根

平常的二分查找

在查找到** s <=x 时,res = mid**

  • 小于时:res需要更新一个更大的数(同理 大于时就不用赋值)
  • 等于时:mid就是需要返回的值

另外需要注意用于判断平方和需要是long类型 (在num大的情况下,mid的平方可能会超出int范围)

public int mySqrt(int x) {
	if (x == 1 || x == 0)
	    return x;
	
	int start = 0;
	int end = x / 2;
	int res = -1;
	while (end >= start){
	    int mid = (start + end) / 2;
	    long s = (long)mid * mid;
	    if (s > x)
	        end = mid - 1;
	    else if (s < x) {
	        // 当平方和小于时,结果为mid
	        res = mid;
	        start = mid + 1;
	    }
	    else return mid;
	}
	return res;
}

367 有效的完全平方数

平常的二分查找

和上题一样
需要注意用于判断平方和需要是long类型 (在num大的情况下,mid的平方可能会超出int范围)

public boolean isPerfectSquare(int num) {
	// 特殊情况 - 当num为1时,完全平方数就是自身
	if (num == 1) return true;
	
	int start = 0;
	int end = num / 2;
	
	while (end >= start){
	    int mid = (start + end) / 2;
	    // 注意转换成long类型 - 在num大的情况下,mid的平方可能会超出int范围
	    long s = (long)mid * mid;
	    if (s > num) end = mid - 1;
	    else if (s < num) start = mid + 1;
	    else return true;
	
	}
	return false;
}

双指针

27 移除元素 拓展

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

正常的双指针

public int removeDuplicates(int[] nums) {
    // 判空
    if (nums.length == 0) return 0;

    int low = 0;

    for (int fast = 0; fast < nums.length; fast++){
        if (nums[fast] != nums[low]){
            nums[low + 1] = nums[fast];
            low++;
        }
    }

    return low + 1;


}

283 移动零

正常双指针

public void moveZeroes(int[] nums) {
    int low = 0;

    for (int fast = 0; fast < nums.length; fast++){
        if (nums[fast] != 0){
            int temp = nums[low];
            nums[low] = nums[fast];
            nums[fast] = temp;
            low++;
        }
    }
}

844 比较含退格的字符串

不太正常的双指针(个人感觉不止easy)

根据题目特性,两个指针指向两个数组比较情况,需要考虑情况:

  • 把s t字符串中的#删除
  • 结束判断 - 是否遍历完成(一定是删完#后在判断)
  • 异常情况:匹配元素不相等 - false
  • 相等则继续匹配 - p1-- p2–
public boolean backspaceCompare(String s, String t) {
    // 方法:从后往前双指针
    int p1 = s.length() - 1;
    int p2 = t.length() - 1;
    int sSkip = 0;
    int tSkip = 0;

    while (true){
        // 去掉s字符串的#
        while (p1 >= 0){
            if (s.charAt(p1) == '#'){
                sSkip++;
            }
            else {
                // 元素不为# - 判断是否有skip记录
                // 如果有则skip-- 并且往前一格
                if (sSkip > 0) sSkip--;
                    // 如果没有记录说明既不是要删元素也不是# 则跳出循环 不用后移指针
                else break;
            }
            p1--;
        }

        // 去掉t字符串中的#
        while (p2 >= 0){
            if (t.charAt(p2) == '#'){
                tSkip++;
            }
            else {
                // 元素不为# - 判断是否有skip记录
                // 如果有则skip-- 并且往前一格
                if (tSkip > 0) tSkip--;
                    // 如果没有记录说明既不是要删元素也不是# 则跳出循环 不用后移指针
                else break;
            }
            p2--;
        }

        // 结束条件 - 遍历完了
        if (p1 < 0 || p2 < 0) break;
        // 异常情况:元素不相等 - false
        if (s.charAt(p1) != t.charAt(p2)) return false;
        // 相等则继续-- 匹配前面
        p1--;p2--;
    }

    // p1 p2 是否同时遍历完
    return p1 == -1 && p2 == -1;

}

977 有序数组的平方

不是平常的双指针

根据题目特性:两边的元素较大,两个指针从头尾开始遍历

public int[] sortedSquares(int[] nums) {
    // 双指针
    int k = nums.length - 1;
    int[] result = new int[nums.length];

    // 注意i和j范围,由于i == j时还要判断并且写入新数组,因此为<=
    for (int i = 0, j = nums.length - 1; i <= j; ) {
        if ((nums[i] * nums[i]) > (nums[j] * nums[j])) {
            // 给新数组赋予最大值
            result[k] = nums[i] * nums[i];
            i++;
            k--;
        } else {
            // 给新数组赋予最大值
            result[k] = nums[j] * nums[j];
            k--;
            j--;
        }
    }
    return result;
}

滑动窗口

209 长度最小的子数组 拓展题目:

滑动窗口两个阶段:

  • 右指针右移 - 扩大区域 满足条件
  • 左指针右移 - 缩小区域 取得最优

此外,要明确右指针停止右移,左指针开始右移标志,用于左指针开始右移的条件

记录滑动窗口最大 / 最小值:
最大值:在右指针中更新 - 原因:右指针不断向右为扩大窗口
最小值:在左指针中更新 - 原因:左指针不断向右为优化窗口

209 长度最小的子数组

右指针停止右移,左指针开始右移标志:
sum >= target

求最小值,min在左指针每次向右移后更新

public int minSubArrayLen(int target, int[] nums) {
    int l = 0;
    int r = 0;
    int min = Integer.MAX_VALUE;
    int sum = 0;

    /*for (r = 0; r < nums.length; r++) {
        // 指针内区域相加 - 获得区域
        sum = sum + nums[r];

        // 优化区域
        while (sum >= target) {
            // 记录当前值
            min = Math.min(min, r - l + 1);
            sum = sum - nums[l];
            l++;
        }

    }*/

    // 右指针右移
    while (r < nums.length){
        sum += nums[r];

        // 左指针右移
        while (sum >= target){
            min = Math.min(min,r - l + 1);
            sum = sum - nums[l];
            l++;
        }
        r++;
    }
    return min == Integer.MAX_VALUE? 0 : min;
}

904 水果成篮

右指针停止右移,左指针开始右移标志:
hm.size() > 2

求最大值,max在每次右指针向右移后更新

public int totalFruit(int[] fruits) {
    // 值 出现次数
    HashMap<Integer, Integer> hm = new HashMap<>();
    int l = 0;
    int max = 0;

    for (int r = 0; r < fruits.length;r++){
        hm.put(fruits[r], hm.getOrDefault(fruits[r], 0) + 1);

        // 更新左指针
        while (hm.size() > 2){
            hm.put(fruits[l], hm.get(fruits[l]) - 1);

            if (hm.get(fruits[l]) == 0)
                hm.remove(fruits[l]);

            l++;
        }

        max = Math.max(max, r - l + 1);

    }
    return max;

}

76 最小覆盖子串

本题是hard题,代码很复杂。但最基本思路还是右指针右移,左指针右移,记录最小值。

会引入一个distance变量,加以判断左指针开始右移。

求最小值,min在左指针每次向右移后更新

public String minWindow(String s, String t) {
    int sLen = s.length();
    int tLen = t.length();

    // z的ASCII码为122
    int[] tChar = new int[123];
    int[] sChar = new int[123];
    // 初始化t数组
    for (char c : t.toCharArray()) {
        tChar[c]++;
    }

    char[] sCharArray = s.toCharArray();


    // 初始化变量
    int r = 0;
    int l = 0;
    int distance = 0;
    int min = s.length() + 1;
    int begin = 0;

    // 原则:左闭右开 [l, r)
    while (r < sLen){
        // 当前右指针所指向的元素
        char c = sCharArray[r];
        if (tChar[c] == 0) {
            r++;
            continue;
        }

        if (sChar[c] < tChar[c])
            distance++;

        sChar[c]++;
        r++;

        // 左指针右移
        while (distance == tLen){
            if (min > r - l){
                min = r - l;
                begin = l;
            }


            // 左指针在s中所指的元素
            char c1 = sCharArray[l];

            if (tChar[c1] == 0) {
                l++;
                continue;
            }

            //if (sChar[c1] > tChar[c1]) sChar[c1]--;
            if (sChar[c1] == tChar[c1]) distance--;

            sChar[c1]--;
            l++;

        }
    }

    return min == s.length() + 1 ? "" : s.substring(begin, begin + min);



}

边界处理

59 螺旋矩阵Ⅱ

主要考察对边界处理,左闭右闭 or 左闭右开,并且一直遵从这个原则

绕弯一圈后思考如何将其普遍化(如何加上while循环)

public int[][] generateMatrix(int n) 
    int offset = 0;
    int num = 1;
    int loop = 0;//循环次数
    int[][] arr = new int[n][n]
    while (loop++ < n / 2) {
        // →
        for (int i = offset; i < n - 1 - offset; i++) {
            arr[offset][i] = num++;
        }
        
        // ↓
        for (int j = offset; j < n - 1 - offset; j++) {
            arr[j][n - 1 - offset] = num++;
        }
        
        // ←
        for (int i = n - 1 - offset; i > offset; i--) {
            arr[n - 1 - offset][i] = num++;
        }
        
        // ↑
        for(int j = n - 1 - offset; j > offset; j--){
            arr[j][offset] = num++;
        
        // 改变参数
        offset++;
    
    if (n % 2 == 1) {
        arr[offset][offset] = num;
    
    return arr;
}

54 螺旋矩阵

与59的难点:不再是方方正正的方形矩阵,变成矩形。

就要考虑最后一转是行 列 还是刚好遍历完

public List<Integer> spiralOrder(int[][] matrix) {
    List<Integer> list = new ArrayList<>();

    int min = Math.min(matrix.length, matrix[0].length);

    // 向上取整
    int num = min / 2;
    int offset = 0;
    int n = matrix.length;
    int m = matrix[0].length;
    while (num-- > 0) {

        // →
        for (int i = offset; i < m - 1 - offset; i++) {
            list.add(matrix[offset][i]);
        }

        // ↓
        for (int j = offset; j < n - 1 - offset; j++) {
            list.add(matrix[j][m - 1 - offset]);
        }

        // ←
        for (int i = m - 1 - offset; i > offset; i--) {
            list.add(matrix[n - 1 - offset][i]);
        }
        // ↑
        for (int j = n - 1 - offset; j > offset; j--) {
            list.add(matrix[j][offset]);
        }

        // 改变参数
        offset++;
    }

    if (min % 2 == 1){
        if (min == n){// 剩一行
            for (int i = offset;i <= m - 1 - offset; i++){
                list.add(matrix[offset][i]);
            }
        }
        else {// 剩一列
            for (int j = offset;j <= n - 1 - offset;j++){
                list.add(matrix[j][m - 1 - offset]);
            }
        }
    }

    return list;
}

剑指offer 29 顺时针打印矩阵

与54 螺旋矩阵思路一样,返回的数据结构不一样。上一题是List,本题是int[]数组,稍微改一下就好~

public int[] spiralOrder(int[][] matrix) {
    // 判空
    if (matrix == null || matrix.length == 0)
        return new int[0];

    // 初始化
    int offset = 0;
    int n = matrix.length;
    int m = matrix[0].length;
    int min = Math.min(m, n);
    int num = min / 2;
    int[] list = new int[m * n];
    int count = 0;

    while (num-- > 0) {

        // →
        for (int i = offset; i < m - 1 - offset; i++) {
            list[count++] = matrix[offset][i];
        }

        // ↓
        for (int j = offset; j < n - 1 - offset; j++) {
            list[count++] = matrix[j][m - 1 - offset];
        }

        // ←
        for (int i = m - 1 - offset; i > offset; i--) {
            list[count++] = matrix[n - 1 - offset][i];
        }
        // ↑
        for (int j = n - 1 - offset; j > offset; j--) {
            list[count++] = matrix[j][offset];
        }

        // 改变参数
        offset++;
    }

    if (min % 2 == 1){
        if (min == n){// 剩一行
            for (int i = offset;i <= m - 1 - offset; i++){
                list[count++] = matrix[offset][i];
            }
        }
        else {// 剩一列
            for (int j = offset;j <= n - 1 - offset;j++){
                list[count++] = matrix[j][m - 1 - offset];
            }
        }
    }

    return list;



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值