Leetcode刷题总结 1:数组

在这里插入图片描述

/二分查找/

704. 二分查找

704. 二分查找

初次提交

2023 年 3 月 13 日

public int search(int[] nums, int target) {
    int start = 0;
    int end = nums.length - 1;
    int half;

    do {
        if (nums[start] == target) return start;
        if (nums[end] == target) return end;

        half = (start + end) / 2;

        if (nums[half] == target) return half;
        if (nums[half] > target) end = half;
        else start = half;

    } while (start + 1 != end && start != end);

    return -1;
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:41.8 MB, 在所有 Java 提交中击败了92.21%的用户

  1. 整数 int 类型的除法是 [截断]处理的。
  2. start + 1 != end && start != end 的判断条件我自己都没搞清楚,稀里糊涂地写的(但居然是对的)
  3. 对二分查找法的思路理解不透彻,逻辑写的比较混乱

优化思路

  1. 首先判断 target 是否在此数组大小区间内,不在的话直接返回 -1;
  2. 当中间数与 target 不相等时,我们采用的赋值方式是 left = mid + 1 或者 right = mid - 1,这也就意味着最终 left 是可能等于 right 的,而此时仍需判断与 target 是否相等;
  3. 如果一直没找到 target,这个 [left, right] 区间会慢慢缩小为 left == right,而此时如果还是没找到 target,则一定会发生 left > right,导致循环结束,最终返回 -1;
public int search(int[] nums, int target) {
    if (target < nums[0] || target > nums[nums.length - 1]) {
        return -1;
    }
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = (left + right)/2;
        if (nums[mid] == target) return mid;
        else if (nums[mid] < target) left = mid + 1;
        else if (nums[mid] > target) right = mid - 1;
    }
    return -1;
}

35. 搜索插入位置:二分查找

35. 搜索插入位置

初次提交

2023 年 3 月 13 日

public int searchInsert(int[] nums, int target) {
    if (target < nums[0]) return 0;
    if (target > nums[nums.length - 1]) return nums.length;

    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 left = mid + 1;
    }
    // 说明最后动的是 right,也就是说 nums[mid] > target
    if (mid == left) return mid;
    else return mid + 1;
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:40.8 MB, 在所有 Java 提交中击败了87.02%的用户

这道题是二分查找法的一个变体,只要牢牢记住:二分查找法没找到 target 的情况下,出循环之前时:left == mid == right,也就是说只需要判断最后是 left 还是 right 改变导致循环结束,从而也就能知道 nums[mid] 和 target 的大小关系。

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

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

初次提交

2023 年 3 月 14 日

public int[] searchRange(int[] nums, int target) {
    int[] range = new int[]{-1, -1};
    if (nums.length == 0) return range;
    if (target < nums[0] || target > nums[nums.length - 1]) return range;

    int left = 0;
    int right = nums.length - 1;
    int mid;

    // 找右界
    while (left <= right) {
        mid = (left + right) / 2;

        if (nums[mid] == target) {
            if (mid + 1 < nums.length && nums[mid + 1] == target) {
                left = mid + 1;
            } else {
                range[1] = mid;
                break;
            }
        } else if (nums[mid] > target) right = mid - 1;
        else left = mid + 1;
    }

    if (range[1] == -1) return range;

    left = 0;
    right = nums.length - 1;

    // 找左界
    while (left <= right) {
        mid = (left + right) / 2;

        if (nums[mid] == target) {
            if (mid - 1 >= 0 && nums[mid - 1] == target) {
                right = mid - 1;
            } else {
                range[0] = mid;
                break;
            }
        } else if (nums[mid] > target) right = mid - 1;
        else left = mid + 1;
    }

    return range;
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:44.6 MB, 在所有 Java 提交中击败了61.49%的用户

失误点:判断 nums[mid + 1] 和 nums[mid - 1] 是需要有个前提条件:mid + 1 < nums.length 以及 mid - 1 >= 0,否则会出现数组越界。

而这两种特殊情况所对应的 mid 实际已经无需再进行判断(因为已经处于了数组的边界)

69. x 的平方根

69. x 的平方根

初次提交

2023 年 3 月 14 日

public int mySqrt(int x) {
    if (x == 0 || x == 1) return x;
    for (int i = 0; i <= x / 2; i++) {
        if ((long) i * i <= x && (long) (i + 1) * (i + 1) > x) return i;
    }
    return 0;
}

执行用时:67 ms, 在所有 Java 提交中击败了5.05%的用户

内存消耗:39 MB, 在所有 Java 提交中击败了26.45%的用户

优化思路

使用二分查找,所有数的算术平方根都在 [0, x] 这个区间内,所以一定可以找到。

还需要注意的是,mid * mid 是有可能会越界的,即超出 MAX_INT 的值,因此需要转为 long

public int mySqrt(int x) {

    int left = 0;
    int right = x;
    int mid;

    while (left <= right) {
        mid = (left + right) / 2;
        
        if ((long) mid * mid <= x && (long) (mid + 1) * (mid + 1) > x) return mid;
        if ((long) mid * mid < x) left = mid + 1;
        else right = mid - 1;
    }
    return 0;
}

执行用时:1 ms, 在所有 Java 提交中击败了94.71%的用户

内存消耗:38.5 MB, 在所有 Java 提交中击败了90.87%的用户

367. 有效的完全平方数

367. 有效的完全平方数

初次提交

2023 年 3 月 14 日

public boolean isPerfectSquare(int num) {
    int left = 0;
    int right = num;
    int mid;

    while (left <= right) {
        mid = (left + right) / 2;

        if ((long)mid * mid == num) return true;
        else if ((long)mid * mid > num) right = mid - 1;
        else left = mid + 1;
    }

    return false;
}

执行耗时:0 ms,击败了 100.00% 的 Java 用户
内存消耗:38.2 MB,击败了 60.17% 的 Java 用户

虽然题目告诉 1 <= num <= 2^31 - 1,但 left 仍指定为 0,是因为防止 left + right 越界

要牢记,此二分法是在 [left, right] 闭区间里找目标!

/数组元素移除/

27. 移除元素

27. 移除元素

初次提交

2023 年 3 月 13 日

public int removeElement(int[] nums, int val) {
    int maxVal = 50;
    int ans = nums.length;
    for (int i = 0; i < nums.length; i ++) {
        if (nums[i] == val) {
            nums[i] = maxVal + 1;
            ans --;
        }
    }

    for (int i = nums.length - 1; i > 0; i --) {
        int max = i;
        for(int j = i - 1;j >= 0; j --) {
            if (nums[j] > nums[max]) max = j;
        }
        if (max != i) {
            int temp = nums[max];
            nums[max] = nums[i];
            nums[i] = temp;
        }
    }
    return ans;
}

执行用时:1 ms, 在所有 Java 提交中击败了3.01%的用户

内存消耗:39.8 MB, 在所有 Java 提交中击败了90.16%的用户

题目说 0 <= nums[i] <=50,因此将所要移除的元素值设置为 51,然后再进行一次排序,但这样做用时很不理想。

优化思路:快慢双指针

在这里插入图片描述

  • 快指针的作用是:循环整个数组,并找到所有和目标元素不相等的元素。
  • 慢指针的作用是:作为最终目标数组的指针。(可以假设在慢指针看来,它是看不到原数组元素的,它只能看到最终数组元素)
public int removeElement(int[] nums, int val) {
    // 快慢指针
    int slowIndex = -1;
    for (int fastIndex = 0; fastIndex < nums.length; fastIndex ++) {
        // 当快指针找到和目标元素不相等的元素时,慢指针才往后挪个位置并把该元素放入其中
        if (nums[fastIndex] != val) 
            nums[++ slowIndex] = nums[fastIndex];
    }
    return slowIndex + 1;
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:39.8 MB, 在所有 Java 提交中击败了90.16%的用户

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

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

初次提交

2023 年 3 月 13 日

public int removeDuplicates(int[] nums) {
        int slowIndex = -1;
        for(int fastIndex = 0; fastIndex < nums.length; fastIndex ++) {
            while(fastIndex < nums.length - 1) {
                if(nums[fastIndex + 1] == nums[fastIndex]) fastIndex ++;
                else break;
            }

            nums[++ slowIndex] = nums[fastIndex];
        }

        return slowIndex + 1;
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:43.2 MB, 在所有 Java 提交中击败了33.86%的用户

完成这道题的时候利用的是快慢双指针

注意题目中描述的:这是个升序数组

优化思路

更好地利用到题目描述的:这是个升序数组。那么慢指针对应的数组当然也是升序数组。

public int removeDuplicates(int[] nums) {
    int slowIndex = 0;
    for(int fastIndex = 1; fastIndex < nums.length; fastIndex ++) {
        if (nums[fastIndex] != nums[slowIndex]) nums[++ slowIndex] = nums[fastIndex];
    }

    return slowIndex + 1;
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:42.8 MB, 在所有 Java 提交中击败了89.20%的用户

283. 移动零

283. 移动零

初次提交

2023 年 3 月 14 日

public void moveZeroes(int[] nums) {
    int lowIndex = -1;

    for (int fastIndex = 0; fastIndex < nums.length; fastIndex ++) {
        if (nums[fastIndex] != 0) nums[++ lowIndex] = nums[fastIndex];
    }
    while (lowIndex + 1 < nums.length) {
        nums[++ lowIndex] = 0;
    }
}

执行耗时:1 ms,击败了 100.00% 的 Java 用户
内存消耗:42.8 MB,击败了 57.93% 的 Java 用户

844. 比较含退格的字符串

844. 比较含退格的字符串

初次提交

2023 年 3 月 14 日

public boolean backspaceCompare(String s, String t) {
    s = backSpace(s);
    t = backSpace(t);
    return s.equals(t);
}

public String backSpace(String s) {
    char[] ss = s.toCharArray();
    int slowIndex = -1;

    for (int fastIndex = 0; fastIndex < ss.length; fastIndex ++) {
        if (ss[fastIndex] != '#') ss[++ slowIndex] = ss[fastIndex];
        else if(slowIndex > -1) slowIndex --;
    }

    if (slowIndex == -1) return "";
    else return String.valueOf(ss).substring(0, slowIndex + 1);
}

执行耗时:0 ms,击败了 100.00% 的 Java 用户
内存消耗:39.8 MB,击败了 34.48% 的 Java 用户

  • 如果想方便地遍历字符串的每个字符的话,最好将其转为 char[]。最后使用 String.valueOf() 转回来。
  • .substring(start, end) 是截取 [start, end),因此 slowIndex 需要加 1 来进行截取

/有序数组的平方/

977. 有序数组的平方

977. 有序数组的平方

初次提交

2023 年 3 月 14 日

public int[] sortedSquares(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        nums[i] = nums[i] * nums[i];
    }
    for (int i = 0; i < nums.length; i++) {
        int min = nums[i];
        for (int j = i + 1; j < nums.length; j++) {
            if (nums[j] < min) {
                int temp = nums[j];
                nums[j] = min;
                min = temp;
            }
        }
        nums[i] = min;
    }

    return nums;
}

执行耗时:441 ms,击败了 5.21% 的 Java 用户
内存消耗:43.9 MB,击败了 5.06% 的 Java 用户

很笨的暴力解法…

优化思路:左右双指针

由于原数组是非降序排列的,那么平方后的最大值只会出现在原数组的最左或者最右,因此此题的思路是:

  1. 定义一个和原数组长度相同的数组,并指向其最后一个元素位置;
  2. 定义左右双指针,每次找到最大值就可以对指针进行挪动。
  3. 循环结束的条件是:左右指针相遇,此时是最后一个需要处理的元素,处理完毕后左右指针错开,循环结束。
public int[] sortedSquares(int[] nums) {
    int[] ans = new int[nums.length];
    int left = 0;
    int right = nums.length - 1;
    int index = right;

    while (left <= right) {
        if (nums[left] * nums[left] < nums[right] * nums[right]) {
            ans[index --] = nums[right] * nums[right];
            right --;
        } else {
            ans[index --] = nums[left] * nums[left];
            left ++;
        }
    }

    return ans;
}

执行耗时:1 ms,击败了 100.00% 的 Java 用户
内存消耗:43.4 MB,击败了 25.40% 的 Java 用户

/长度最小子数组/

209. 长度最小的子数组

209. 长度最小的子数组

初次提交

2023 年 3 月 14 日

public int minSubArrayLen(int target, int[] nums) {
    int min = Integer.MAX_VALUE;
    for (int left = 0; left < nums.length; left ++) {
        int sum = 0;
        for (int right = left; right < nums.length; right ++) {
            sum += nums[right];
            if (sum >= target) {
                if (right - left + 1 < min)
                    min = right - left + 1;
                break;
            }
        }
    }

    return min == Integer.MAX_VALUE? 0 : min;
}

提交失败:Time Limit Exceeded

只想到了暴力求解,然后超时了。

优化思路:双指针之滑动窗口

  • 窗口:满足和 ≥ target 的长度最小的连续子数组
  • 窗口的结束位置如何移动:如果当前窗口值 sum 小于 target,则不断移动 right 指针,直到大于 target
  • 窗口的起始位置如何移动:如果当前窗口值 sum 已经大于 target 了,left 指针不断向前移动,直到窗口值再次小于 target 值。(此过程中,需要记录下窗口长度并与 min 做对比)
class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0;
        int sum = 0;
        int min = Integer.MAX_VALUE;
        for (int right = 0; right < nums.length; right++) {
            sum += nums[right];
            while (sum >= target) {
                min = Math.min(min, right - left + 1);
                sum -= nums[left++];
            }
        }
        return min == Integer.MAX_VALUE ? 0 : min;
    }
}

执行耗时:1 ms,击败了 100.00% 的 Java 用户
内存消耗:48.7 MB,击败了 57.67% 的 Java 用户

不要以为 for 里放一个 while 就以为是 O(n^2) , 主要是看每一个元素被操作的次数,每个元素在滑动窗口进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是 O(n)。

904. 水果成篮

904. 水果成篮

初次提交

2023 年 3 月 15 日

public int totalFruit(int[] fruits) {
    int left = 0;
    int max = 1;
    int[] fruitType = new int[2];
    fruitType[0] = fruits[left];
    boolean type = false;// 表示还未找到第二种类型的水果

    for (int right = 1; right < fruits.length; right++) {

        if (!type && fruits[right] != fruitType[0]) { // 找到第二类水果了
            fruitType[1] = fruits[right];
            type = true;
            max = right - left + 1 > max ? right - left + 1: max;
        } else if (type && fruits[right] != fruitType[0] && fruits[right] != fruitType[1]) {
            // 出现第三种类型的水果了
            // 将第二种水果类型更换为新碰到的水果类型
            fruitType[1] = fruits[right];
            
            // 将第一种水果类型更换为上个窗口遇到的最后一种水果
            fruitType[0] = fruits[right - 1];
            
            // 开始右移窗口左指针
            left = right - 1;
            while (left > 0) {
                if (fruits[left - 1] == fruitType[0]) left--;
                else break;
            }
        } else max = right - left + 1 > max ? right - left + 1: max;
    }

    return max;
}

执行耗时:5 ms,击败了 96.73% 的 Java 用户
内存消耗:50.4 MB,击败了 68.02% 的 Java 用户

这道题看似很复杂,其实质上就是:找某个数组的只包含最多两种数字的最长子数组

因为没有具体的判断条件,所以每次右指针右移过后都需要判断窗口长度与 max 值的大小

76. 最小覆盖子串

76. 最小覆盖子串

初次提交


/螺旋矩阵/

59. 螺旋矩阵 II

59. 螺旋矩阵 II

在这里插入图片描述

初次提交

2023 年 3 月 15 日

public int[][] generateMatrix(int n) {
    int[][] ans = new int[n][n];
    int row = 0;
    int col = 0;
    int direction = 1;
    int topBorder = -1, rightBorder = n, bottomBorder = n, leftBorder = -1;//4 个边界

    for (int i = 1; i <= n * n; i++) {
        ans[row][col] = i;
        // 根据方向来改变行列
        switch (direction) {
            case 1: // 从左到右,列增加
                if(col + 1 == rightBorder) { // 如果已经到达右边界,改变方向
                    row ++; // 这里可以放心地增加行,因为如果右边是右边界,下面不可能会是下边界,否则循环就结束了
                    topBorder ++;//上边界下移
                    direction = 2;//方向改变
                } else col ++;
                break;

            case 2: // 从上到下,行增加
                if(row + 1 == bottomBorder) {
                    col --;
                    rightBorder --;
                    direction = 3;
                } else row ++;
                break;

            case 3: // 从右到左,列减少
                if(col - 1 == leftBorder) {
                    row --;
                    bottomBorder --;
                    direction = 4;
                } else col --;
                break;

            case 4: // 从下到上,行减少
                if(row - 1 == topBorder) {
                    col ++;
                    leftBorder ++;
                    direction = 1;
                } else row --;
                break;
        }
    }
    return ans;
}

执行耗时:0 ms,击败了 100.00% 的 Java 用户
内存消耗:39.3 MB,击败了 86.48% 的 Java 用户

没有技巧,全是体力活。

54. 螺旋矩阵

54. 螺旋矩阵

初次提交

2023 年 3 月 15 日

public List<Integer> spiralOrder(int[][] matrix) {
    int direction = 1;
    int m = matrix.length;
    int n = matrix[0].length;
    int row = 0, col = 0;
    int topBorder = -1, rightBorder = n, bottomBorder = m, leftBorder = -1;
    List<Integer> ans = new ArrayList<>();

    for (int i = 0; i < m * n; i++) {
        ans.add(Integer.valueOf(matrix[row][col]));
        switch(direction) {
            case 1: // 从左往右
                if (col + 1 == rightBorder) {
                    row ++;
                    direction = 2;
                    topBorder ++;
                } else col ++;
                break;

            case 2: // 从上往下
                if (row + 1 == bottomBorder) {
                    col --;
                    direction = 3;
                    rightBorder --;
                } else row ++;
                break;

            case 3: // 从右往左
                if (col - 1 == leftBorder) {
                    row --;
                    direction = 4;
                    bottomBorder --;
                } else col --;
                break;

            case 4: // 从下往上
                if (row - 1 == topBorder) {
                    col ++;
                    direction = 1;
                    leftBorder ++;
                } else row --;
                break;
        }
    }
    return ans;
}

执行耗时:0 ms,击败了 100.00% 的 Java 用户
内存消耗:39.4 MB,击败了 78.87% 的 Java 用户

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值