当前位置: 首页 > news >正文

代码随想录-数组

文章目录

  • 二分查找
    • 704. 二分查找
      • 题目
      • 解答
      • 讲解
    • 35. 搜索插入位置
      • 题目
      • 解答
      • 官方解答
    • 34. 在排序数组中查找元素的第一个和最后一个位置
      • 题目
      • 解答
      • 官方解答
    • 69. x 的平方根
      • 题目
      • 解答
      • 官方解答
    • 367. 有效的完全平方数
      • 题目
      • 解答
  • 移除元素
    • 27. 移除元素
      • 题目
      • 解答
      • 官方题解
    • 26. 删除有序数组中的重复项
      • 题目
      • 解答
    • 283. 移动零
      • 题目
      • 解答
      • 官方题解
    • 844. 比较含退格的字符串
      • 题目
      • 解答
      • 官方题解
        • 解法1:重构字符串
        • 解法2:双指针
    • 977. 有序数组的平方
      • 题目
      • 解答
  • 长度最小的子数组
    • 209. 长度最小的子数组
      • 题目
      • 解答
    • 904. 水果成篮
      • 题目
      • 解答
    • 76. 最小覆盖子串
      • 题目
      • 解答
  • 螺旋矩阵II
    • 59. 螺旋矩阵II
      • 题目
      • 解答
        • 解法一
        • 解法二
    • 54. 螺旋矩阵
      • 题目
      • 解答
        • 解法一
        • 解法二
    • LCR 146. 螺旋遍历二维数组
      • 题目
      • 解答
  • 区间和
    • 58. 区间和
      • 题目
      • 解答
  • 开发商购买土地
    • 44. 开发商购买土地
      • 题目
      • 解答

二分查找

704. 二分查找

题目链接

题目

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1
示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示:

  1. 你可以假设 nums 中的所有元素是不重复的。
  2. n 将在 [1, 10000]之间。
  3. nums 的每个元素都将在 [-9999, 9999]之间。

解答

我的做法,改进点

  1. int mid = (begin + end / 2) 改为 int middle = begin + ((end - begin) / 2); 可以防止计算时的溢出
class Solution {public int search(int[] nums, int target) {int begin = 0;int end = nums.length - 1;while (begin <= end) {int mid = (begin + end) / 2;if (nums[mid] < target){begin = mid + 1;}else if (nums[mid] > target){end = mid - 1;}else {return mid;}}return -1;}
}

讲解

二分查找的两个前提

  1. 数组有序
  2. 数组中无重复元素(一旦重复,二分查找返回的元素下标就不唯一了)

二分查找涉及一些边界条件,到底是 while (left < right) 还是 while(left <= right)。到底是right = middle 还是要right = middle - 1

其实就是看区间,一共两种区间的写法 [left, right] 左闭右闭和 [left, right) 左闭右开

第一种写法,target[left, right] 左闭右开区间,也就是说 while(left <= right) 是可以存在的,因为当 left == right 时,还是存在一个元素的。那么当 nums[middle] > target 时,right = middle - 1, 因为 nums[middle] 一定不等于 target

image-20250307102718150

class Solution {public int search(int[] nums, int target) {int left = 0;int right = nums.size() - 1; // 满足左闭右开的条件 [left, right] 为数组起始与终止位置while (left <= right){int middle = left + ((right - left) / 2); // 防止溢出 等同于 (left + right) / 2if (nums[middle] > target){right = middle - 1; // target 在左区间,所以[left, middle - 1]}else if (nums[middle] < target){left = middle + 1; // target 在右区间,所以[middle + 1, right]}else {return middle;}}return -1;}
}

第二种写法,左闭右开,也就是 [left, right)

  • while(left < right),因为如果 left == right 是没有意义的
  • nums[middle] > target,那么 right = middle;,因为区间上 right 是开的,所以让 right = middle,是不是搜索到 middle
class Solution {public int search(int[] nums, int target) {int left = 0;int right = nums.size(); // 满足左闭右开的条件 [left, right) 为数组起始与终止位置while (left < right){int middle = left + ((right - left) >> 1); // 防止溢出 等同于 (left + right) / 2if (nums[middle] > target){right = middle; // target 在左区间,所以[left, middle)}else if (nums[middle] < target){left = middle + 1; // target 在右区间,所以[middle + 1, right]}else {return middle;}}return -1;}
}

35. 搜索插入位置

题目链接

题目

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

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4

提示:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums无重复元素升序 排列数组
  • -104 <= target <= 104

解答

public int searchInsert(int[] nums, int target) {int left = 0, right = nums.length - 1;while (left <= right) {int middle = left + (right - left) / 2;if (nums[middle] > target){right = middle - 1;}else if (nums[middle] < target){left = middle + 1;}else {return middle;}}return left;
}

这道题二分法无疑,关键在于最后返回哪个位置。如果 target 不存在,最后一定是没有找到元素

那么只有三种情况,target 下标该插入数组中,或者 target 大于数组最大值,或者小于数组最小值

结束循环时一定是 left == right + 1

只需要考虑三种情况下返回什么值即可

  1. target 小于数组最小值时,最后一次判断一定是比较的 nums[0]target。比较时,left == right == 0,比较完后,left = 0, right = -1,应该返回 left
  2. target 大于数组最大值时,最后一次判断一定是比较的 nums[nums.size() - 1]target。比较时,left == right == nums.size() - 1,比较完后,left = nums.size(), right = nums.size() - 1,应该返回 left
  3. 我们不去推算了,我们就想,最后结束循环时一定是 left == right + 1,也就是说 right 在左,left 在右。那么 nums[right] < target < nums[left] 是必然的,不然不会结束循环。所以插入位置一定是 left

最终返回位置为 left


结束循环时一定是 left == right + 1

说明 nums[right] < target < nums[left]

也就是说 nums[right] 是第一个小于 target 的值,nums[left] 是第一个大于 target 的值,插入位置应该为 left 位置!!

官方解答

假设插入下标为 pos
n u m s [ p o s − 1 ] < t a r g e t < = n u m s [ p o s ] nums[pos-1] < target <= nums[pos] nums[pos1]<target<=nums[pos]
思路:查找第一个大于等于 target 值的下标 pos

public int searchInsert(int[] nums, int target) {int left = 0, right = nums.length - 1, ans = nums.length;while (left <= right) {int middle = left + (right - left) / 2;if (target <= nums[middle]){ans = middle;right = middle - 1;}else{left = middle + 1;}}return ans;
}

如果在数组中间位置进行插入,那么所有比 target 大的值一定会被遍历一遍(目的是为了找相等的值),且由于是二分法,所以遍历的值一定是从大到小的。所以最后一次进行比较,且满足 target <= nums[middle] 的下标为 middle 当时的下标。

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

题目链接

题目

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums 是一个非递减数组
  • -109 <= target <= 109

解答

public int[] searchRange(int[] nums, int target) {int maxIndex = nums.length;int left = 0, right = nums.length;// 找右边界, right 从左到右第一个大于 target 的下标while (left < right) {int middle = left + ((right - left) / 2);if(target < nums[middle]){right = middle;maxIndex = right;}else{left = middle + 1;}}left = 0;right = nums.length - 1;int minIndex = 0;// 找左边界, left 为第一个大于或等于 targetwhile (left <= right) {int middle = left + ((right - left) / 2);if(target > nums[middle]){left = middle + 1;minIndex = left;}else{right = middle - 1;}}// 数组不为空,且左边界找到了(意味着一定存在右边界)if(nums.length != 0 && minIndex > -1 && minIndex < nums.length && nums[minIndex] == target){return new int[]{minIndex, maxIndex - 1};}return new int[]{-1, -1};
}

思路:

  1. 与简单二分查找找值不同,因为可能会有多个目标值
  2. 将查找范围改为找边界点
  3. 右边界点可以去找从左到右第一个大于 target 的下标
  4. 左边界点可以去找从左到右最后一个小于等于 target 的下标
  5. 最后结果判断比较繁琐,要考虑越界的问题

[!Note]

  1. 在进行 target 与中间值进行对比后,我们去修改了 left 或者 right,在不同判断条件下去修改这两个下标,其实只有一个是我们最终要得到的结果,另一个只是用来结束循环的

  2. 右边界和左边界的寻找之所以这么做,有部分原因是因为整除

    (1 + 1) / 2 = 1
    (1 + 2) / 2 = 1
    

第一个找右边界的我们还可以写成如下形式

int maxIndex = nums.length;
int left = 0, right = nums.length - 1;
// 找右边界, right 从左到右第一个大于 target 的下标
while (left < right) {int middle = left + ((right - left) / 2);if(target < nums[middle]){right = middle;maxIndex = right;}else{left = middle + 1;}
}if (nums.length != 0 && nums[right] > target) maxIndex = right;

官方解答

public int[] searchRange(int[] nums, int target) {// 如果没有元素就返回 [-1, -1]if(nums == null || nums.length == 0) return new int[]{-1, -1};int firstPosition = findFirstPosition(nums, target);// 如果第一个都找不到,那就一定没有了if(firstPosition == -1)return new int[]{-1, -1};int lastPosition = findLastPosition(nums, target);return new int[]{firstPosition, lastPosition};
}private int findLastPosition(int[] nums, int target) {int left = 0;int right = nums.length - 1;while(left < right) {// 注意此处 left + right + 1int mid = (left + right + 1) >>> 1;if (nums[mid] == target) {left = mid;}else if(nums[mid] < target) {left = mid + 1;}else {right = mid - 1;}}// 退出循环时 left == right, 但是如果找到了第一个元素位置, 那么最后一个元素位置一定是存在的return left;
}private int findFirstPosition(int[] nums, int target) {int left = 0;int right = nums.length - 1;while(left < right) {int mid = (left + right) >>> 1;if (nums[mid] == target) {right = mid;}else if(nums[mid] < target) {left = mid + 1;}else {right = mid - 1;}}// 退出循环时 left == rightif(nums[left] == target)return left;elsereturn -1;
}

[!Caution]

注意此处结束循环的条件

while(left < right) --> 结束的时候一定是 left == right

while(left <= right) --> 结束的时候一定是 left == right - 1


细节注意找最后一个元素时使用 (left + right + 1) >>> 1

原因是因为为了防止陷入死循环

原始的二分法为什么没有考虑陷入死循环?

因为原始的二分法数组元素是不重复的,所以每次进行比较一定会造成范围的缩小,找到元素就直接返回了

为什么这里找第一个元素时没有考虑陷入死循环的问题?

我们先考虑什么情况下会陷入死循环,当 nums[mid] != target 时, 范围一定是变化的,所以不会陷入死循环

而当 nums[mid] == target 时,会取 [left, mid]

因为本身 Java 整除特性存在 (1 + 1) / 2 == 1(1 + 2) / 2 == 1。在查找第一个元素时,mid 是会往左靠的,不会出现死循环

考虑找最后一个元素时,如果还是使用 (left + right) / 2

nums[mid] == target 时,我们取 [mid, right]

那么在重新计算中间值的时候,也就是 (mid + right) / 2,可能会存在 (mid + right) / 2 == mid 的情况,从而陷入死循环了。所以向上加1,来保证差值为 1 的时候往右走,从而不会陷入死循环。

官方的思路非常清晰

  1. 先找第一个元素位置,就用二分法

    • nums[mid] < target 时,范围变为 [mid + 1, right]
    • nums[mid] > target 时,范围变为 [left, mid - 1]
    • nums[mid] == target 时,范围变为 [left, mid]

    最后结束循环时判断一下,该位置是否是要找的 target

  2. 如果第一个元素位置找到了,意味着最后一个元素位置一定是存在的

    • nums[mid] < target 时,范围变为 [mid + 1, right]
    • nums[mid] > target 时,范围变为 [left, mid - 1]
    • nums[mid] == target 时,范围变为 [mid, right]

mid * mid > x 两种情况


69. x 的平方根

题目链接

题目

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

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

**注意:**不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5

示例 1:

输入:x = 4
输出:2

示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

提示:

  • 0 <= x <= 231 - 1

解答

public int mySqrt(int x) {// 找到一个数y, 使得 y^2 <= x, (y + 1)^2 > xif(x == 0 || x == 1) return x;int left = 1;int right = x;while(right - left > 1){int mid = left + (right - left) / 2;// if(mid * mid > x){ // 换为一个不会溢出的计算方式if(x / mid < mid){right = mid;}else{left = mid;}}return left;
}

[left, right] 的范围表示 left * left <= x right * right > x

所以我们使用 left 作为我们想要找的值,由于值一定存在,所以循环结束时 left 就是我们要的结果,什么时候结束循环呢?right - left == 1 时结束即可,所以我们需要保证循环结束时

  • left * left <= x
  • right * right > x

所以当 mid * mid > x 时,即中间值的平方大于 x 时,我们让 right = mid,而不是 mid - 1,因为减 1 后可能 right * right <= x 了。反之,让 left = mid

官方解答

  public int mySqrt(int x) {// 找到一个数y, 使得 y^2 <= x, (y + 1)^2 > xif(x == 0 || x == 1) return x;int left = 1;int right = x;int ans = -1;while(left <= right){int mid = left + (right - left) / 2;// if(mid * mid > x){ // 换为一个不会溢出的计算方式if(x / mid < mid){right = mid - 1;}else{ans = mid;left = mid + 1;}}return ans;
}

367. 有效的完全平方数

题目链接

题目

给你一个正整数 num 。如果 num 是一个完全平方数,则返回 true ,否则返回 false

完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。

不能使用任何内置的库函数,如 sqrt

示例 1:

输入:num = 16
输出:true
解释:返回 true ,因为 4 * 4 = 16 且 4 是一个整数。

示例 2:

输入:num = 14
输出:false
解释:返回 false ,因为 3.742 * 3.742 = 14 但 3.742 不是一个整数。

提示:

  • 1 <= num <= 231 - 1

解答

其实和 69 题完全一样的,都是找第一个平方小于等于 x 的数

public boolean isPerfectSquare(int num) {// left 为第一个平方小于等于 num 的数int left = 1, right = num;while (left < right - 1) {int mid = left + (right - left) / 2;// 为完全平方数if(num / mid < mid){right = mid;}else{left = mid;}}return left * left == num;
}

移除元素

27. 移除元素

题目链接

题目

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

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

  • 更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
  • 返回 k

用户评测:

评测机将使用以下代码测试您的解决方案:

int[] nums = [...]; // 输入数组
int val = ...; // 要移除的值
int[] expectedNums = [...]; // 长度正确的预期答案。// 它以不等于 val 的值排序。int k = removeElement(nums, val); // 调用你的实现assert k == expectedNums.length;
sort(nums, 0, k); // 排序 nums 的前 k 个元素
for (int i = 0; i < actualLength; i++) {assert nums[i] == expectedNums[i];
}

如果所有的断言都通过,你的解决方案将会 通过

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,_,_]
解释:你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。

示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3,_,_,_]
解释:你的函数应该返回 k = 5,并且 nums 中的前五个元素为 0,0,1,3,4。
注意这五个元素可以任意顺序返回。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。

解答

public int removeElement(int[] nums, int val) {int count = 0;int i = 0, j = nums.length - 1;while(i <= j){if(nums[i] == val){count++;// 从后面找第一个不等于 val 的值while(j > i && nums[j] == val) {count++;j--;}// 代表找到了if(j > i)nums[i] = nums[j--];}i++;}return nums.length - count;
}

思路就是双指针,一个从开始 i,一个从末尾 j,当 i 指针移动到等于 val 值的位置时,那么就从末尾往前找到第一个不等于 val 的值,并替代 i 处的值。

需要注意判断条件

  • 开始时 j 处于末尾,未访问。
  • 不能重复访问,同一位置的不能访问两次,不然可能出现计数、移动错误

自己写了一些测试数据

RemoveElement obj = new RemoveElement();
int[] arr1 = {3, 2, 2, 3};
int[] arr2 = {0, 1, 2, 2, 3, 0, 4, 2};
int[] arr3 = {0, 1, 2, 2, 3, 0, 4, 2};
int[] arr4 = {};
int[] arr5 = {1};
System.out.println(obj.removeElement(arr1, 3));
System.out.println(Arrays.toString(arr1));
System.out.println(obj.removeElement(arr2, 2));
System.out.println(Arrays.toString(arr2));
System.out.println(obj.removeElement(arr3, 5));
System.out.println(Arrays.toString(arr3));
System.out.println(obj.removeElement(arr4, 2));
System.out.println(Arrays.toString(arr4));
System.out.println(obj.removeElement(arr5, 1));
System.out.println(Arrays.toString(arr5));

官方题解

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

i,j 都从起始位置开始,遇到不等于 val 的就往前放(如此也能保持数组原顺序不变),并且 i 的大小即不等于 val 的数量,少使用一个计数器。


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

题目链接

题目

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

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

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

判题标准:

系统会用下面的代码来测试你的题解:

int[] nums = [...]; // 输入数组
int[] expectedNums = [...]; // 长度正确的期望答案int k = removeDuplicates(nums); // 调用assert k == expectedNums.length;
for (int i = 0; i < k; i++) {assert nums[i] == expectedNums[i];
}

如果所有断言都通过,那么您的题解将被 通过

示例 1:

输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。

示例 2:

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

提示:

  • 1 <= nums.length <= 3 * 104
  • -104 <= nums[i] <= 104
  • nums 已按 非严格递增 排列

解答

public int removeDuplicates(int[] nums) {if(nums.length <= 1) return nums.length;int left = 0; // 最大值的位置for(int right = 1;right < nums.length; right++) {// 代表某值第一次出现if(nums[right] != nums[left]) {// 依次往前放置nums[++left] = nums[right];}}return left + 1;
}

特点是非严格递增,也是递增的,只不过可能有重复的值。

那我就只需要保留某个值第一次出现的即可,后面每个值第一次出现时都往前面的位置进行补齐。


283. 移动零

题目链接

题目

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

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

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

示例 2:

输入: nums = [0]
输出: [0]

提示:

  • 1 <= nums.length <= 104
  • -231 <= nums[i] <= 231 - 1

解答

public void moveZeroes(int[] nums) {// 统计 0 的个数int countZero = 0;for(int right = 0; right < nums.length; right++) {// 遇到零时计数+1if(nums[right] == 0) {countZero++;}else{// 遇到非零时将数往前移动, 向前移动的距离取决于 0 的个数nums[right - countZero] = nums[right];}}// 最后几位赋值为 0for(int right = nums.length - 1; countZero > 0; countZero--) {nums[right--] = 0;}
}

官方题解

思路都是一样的,只是方法略微不同

这里实际是没有使用双指针的,但是可以用双指针的方法来做,思路是一样的

public void moveZeroes(int[] nums) {int i = 0;// 遇到不为 0 的就往前移动for(int j = 0; j < nums.length; j++){if(nums[j] != 0){nums[i++] = nums[j];}}while(i < nums.length){nums[i++] = 0;}
}

283_1.gif

也可以使用交换的方式来做,但是实质上和上面的方法是一样的

public void moveZeroes(int[] nums) {int i = 0;for(int j = 0; j < nums.length; j++){if(nums[j] != 0){int temp = nums[i];nums[i] = nums[j];nums[j] = temp;i++;}}
}

283_2.gif

844. 比较含退格的字符串

题目链接

题目

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

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

示例 1:

输入:s = "ab#c", t = "ad#c"
输出:true
解释:s 和 t 都会变成 "ac"。

示例 2:

输入:s = "ab##", t = "c#d#"
输出:true
解释:s 和 t 都会变成 ""。

示例 3:

输入:s = "a#c", t = "b"
输出:false
解释:s 会变成 "c",但 t 仍然是 "b"。

提示:

  • 1 <= s.length, t.length <= 200
  • st 只含有小写字母以及字符 '#'

解答

class Solution {public boolean backspaceCompare(String s, String t) {// Java字符串不可修改, 所以转为字符数组char[] sArray = s.toCharArray();int sLen = processString(sArray, s.length());char[] tArray = t.toCharArray();int tLen = processString(tArray, t.length());// 字符数量相等且都相等返回truereturn sLen == tLen && String.valueOf(sArray).substring(0, sLen + 1).equals(String.valueOf(tArray).substring(0, tLen + 1));}public int processString(char[] s, int length){// i 的位置代表已经处理好的字符串, i 及 i 之前的即我们需要的字符串int i = -1;for(int j = 0; j < length; j++){// 不为退格时 i 往前走一步, 且该位置字符为 s[j]if(s[j] != '#'){s[++i] = s[j];}else{// 为退格时应该删去一个字符, 并且要注意判断不能一直退if(i >= 0) i--;}}return i;}
}

官方题解

解法1:重构字符串

使用处理字符串得到最终的字符串形式

class Solution {public boolean backspaceCompare(String S, String T) {return build(S).equals(build(T));}public String build(String str) {StringBuffer ret = new StringBuffer();int length = str.length();for (int i = 0; i < length; ++i) {char ch = str.charAt(i);if (ch != '#') {ret.append(ch);} else {if (ret.length() > 0) {ret.deleteCharAt(ret.length() - 1);}}}return ret.toString();}
}

时间和空间复杂度均为 O ( N + M ) O(N+M) O(N+M)

解法2:双指针

一个字符是否会被删掉,只取决于该字符后面的退格符,而与该字符前面的退格符无关。因此当我们逆序地遍历字符串,就可以立即确定当前字符是否会被删掉。

具体地,我们定义 skip 表示当前待删除的字符的数量。每次我们遍历到一个字符:

若该字符为退格符,则我们需要多删除一个普通字符,我们让 skip1

若该字符为普通字符:

  • skip0,则说明当前字符不需要删去;

  • skip 不为 0,则说明当前字符需要删去,我们让 skip1

这样,我们定义两个指针,分别指向两字符串的末尾。每次我们让两指针逆序地遍历两字符串,直到两字符串能够各自确定一个字符,然后将这两个字符进行比较。重复这一过程直到找到的两个字符不相等,或遍历完字符串为止。

public boolean backspaceCompare(String s, String t){int skipS = 0, skipT = 0;int i = s.length() - 1, j = t.length() - 1;while(i >=0 || j >= 0){while(i >= 0){if(s.charAt(i) == '#'){skipS++;i--;}else if(s.charAt(i) != '#' && skipS > 0){skipS--;i--;}else{// s.charAt(i) != '#' && skipS == 0 代表该位置是存在的字符break;}}while(j >= 0){if(t.charAt(j) == '#'){skipT++;j--;}else if(t.charAt(j) != '#' && skipT > 0){skipT--;j--;}else{// t.charAt(j) != '#' && skipT == 0 代表该位置是存在的字符break;}}// 获取到两个位置的值了if(i >= 0 && j >=0){if(s.charAt(i) != t.charAt(j)){return false;}// 如果 i < 0 && j >=0 或者 i > 0 && j <= 0 则说明返回 false// 如果 i < 0 && j < 0, 说明返回 true, 这里省略了, 因为往下进行也会返回 true}else return i < 0 && j < 0;i--;j--;}return true;
}

时间复杂度为 O ( N + M ) O(N+M) O(N+M),空间复杂度为 O ( 1 ) O(1) O(1)
演示链接

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] 

提示:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums 已按 非递减顺序 排序

解答

由于是非递减的,那么只需要从两边依次比较绝对值大小即可

无论 nums 数组是只包含负数、只包含正数、还是正数负数都有,我们在比较完绝对值后先取最大的即可。

class Solution {public int[] sortedSquares(int[] nums) {// 从两边往中间走就可以了,比较绝对值大小int[] sorted = new int[nums.length];// 从 sorted 数组末尾开始添加int right = nums.length - 1;int i = 0, j = nums.length - 1;// 不用 i <= j 是为了 i == j 时出现 nums[i] == nums[j] 而误放入两个// 所以结束循环时需要判断 i == j 这种情况while(i < j){if(abs(nums[i]) < abs(nums[j])){sorted[right--] = nums[j] * nums[j];j--;}else if(abs(nums[i]) > abs(nums[j])){sorted[right--] = nums[i] * nums[i];i++;}else{// 相等时两个都加进去, 顺序无所谓sorted[right--] = nums[j] * nums[j];j--;sorted[right--] = nums[i] * nums[i];i++;}}// 需要额外判断// 循环结束时 i == j 说明 nums[i] != nums[j]// 循环结束时 i != j 说明 nums[i] == nums[j], 不需要再往数组里添加值了if(i == j){sorted[right] = nums[i] * nums[i];}return sorted;}public int abs(int num){if(num < 0){return -num;}return num;}
}

能不能简化呢?

nums[i] == nums[j] 时我是不是也可以只放入一个,任意一个就可以,这样我就可以把 else 部分进行简化,同理,后面的 if(i == j){} 也可以去掉了,把循环条件改为 (i <= j) 即可。

public int[] sortedSquares(int[] nums) {// 从两边往中间走就可以了,比较绝对值大小int[] sorted = new int[nums.length];int right = nums.length - 1;int i = 0, j = nums.length - 1;while(i <= j){if(abs(nums[i]) < abs(nums[j])){sorted[right--] = nums[j] * nums[j];j--;}else{sorted[right--] = nums[i] * nums[i];i++;}}return sorted;
}public int abs(int num){if(num < 0){return -num;}return num;
}

长度最小的子数组

209. 长度最小的子数组

题目链接

题目

给定一个含有 n 个正整数的数组和一个正整数 target

找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度**。**如果不存在符合条件的子数组,返回 0

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

提示:

  • 1 <= target <= 109
  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 104

解答

public int minSubArrayLen(int target, int[] nums) {int i = 0, sum = 0, j = 0;int minLen = nums.length + 1;for(; j < nums.length; j++) {// 计算当前和sum += nums[j];// 如果和大于目标值了,尝试多次移动 iwhile(sum >= target) {// 更新 minLenif(j - i + 1 < minLen){minLen = j - i + 1;}sum -= nums[i++];}}return minLen == nums.length + 1 ? 0 : minLen;
}

209.长度最小的子数组

思路即一种滑动窗口,也可以理解为一种双指针

j 向后移动时,如果出现 [i, j] 的和大于了 target,意味着尾部位置可以固定了,开始尝试头部位置是否可以继续向后移,即 i++,同时,考虑是否可以多次 i++

如此而已,时间复杂度就为 O ( N ) O(N) O(N),因为每个位置最多被遍历到 2 2 2 次,所以时间复杂度为 O ( N ) O(N) O(N)

904. 水果成篮

题目链接

题目

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

  • 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
  • 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
  • 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。

给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

示例 1:

输入:fruits = [1,2,1]
输出:3
解释:可以采摘全部 3 棵树。

示例 2:

输入:fruits = [0,1,2,2]
输出:3
解释:可以采摘 [1,2,2] 这三棵树。
如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。

示例 3:

输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。

示例 4:

输入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:可以采摘 [1,2,1,1,2] 这五棵树。

提示:

  • 1 <= fruits.length <= 105
  • 0 <= fruits[i] < fruits.length

解答

public int totalFruit(int[] fruits) {// j++ 到能采集的两种的最大水果// i++ 到消灭掉一种水果// j++ 到新增一种水果int typeA = -1, typeB = -1;int numA = 0, numB = 0;int maxLen = 0, i = 0, j =0;for(; j < fruits.length; j++){if (fruits[j] == typeA){numA++;}else if(fruits[j] == typeB){numB++;}else if(typeA == -1){typeA = fruits[j];numA++;}else if(typeB == -1){typeB = fruits[j];numB++;}else{// fruits[i] != typeA && fruits[i] != typeBif(j - i + 1 > maxLen) maxLen = j - i;while(numA > 0 && numB > 0){if(fruits[i] == typeA){numA--;}else if(fruits[i] == typeB){numB--;}i++;}if(numA == 0) typeA = -1;if(numB == 0) typeB = -1;j--; // 当前的 j 再遍历一次}}return Math.max(maxLen, numA + numB);
}

76. 最小覆盖子串

题目链接

题目

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

示例 2:

输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串。

示例 3:

输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

提示:

  • m == s.length
  • n == t.length
  • 1 <= m, n <= 105
  • st 由英文字母组成

**进阶:**你能设计一个在 o(m+n) 时间内解决此问题的算法吗?

解答

class Solution {public String minWindow(String s, String t) {// t 的计数Map<Character, Integer> originCounter = getOriginCounter(t);Map<Character, Integer> charCounter = getCharCounter(originCounter);int i = 0, j = 0;String res = "";// 不满足条件时while(j < s.length()) {// j 后移找到满足条件的字符串while(j < s.length() && !passMuster(charCounter, originCounter)){if(originCounter.containsKey(s.charAt(j))){charCounter.put(s.charAt(j), charCounter.get(s.charAt(j)) + 1);}j++;}// i 前移找到不满足条件的字符串while(passMuster(charCounter, originCounter)){if(res.isEmpty() || res.length() > j - i){res = s.substring(i, j);}if(originCounter.containsKey(s.charAt(i))){charCounter.put(s.charAt(i), charCounter.get(s.charAt(i)) - 1);}i++;}}return res;}public Map<Character, Integer> getOriginCounter(String t) {Map<Character, Integer> countMap = new HashMap<>();for (int i = 0; i < t.length(); i++) {countMap.compute(t.charAt(i), (k, v) -> v == null ? 1 : v + 1);}return countMap;}public Map<Character, Integer> getCharCounter(Map<Character, Integer> originCounter) {Map<Character, Integer> countMap = new HashMap<>();for(Character c: originCounter.keySet()){countMap.put(c, 0);}return countMap;}// c1 中的数量都大于或等于  c2public boolean passMuster(Map<Character, Integer> c1, Map<Character, Integer> c2) {if(!c1.keySet().equals(c2.keySet())){return false;}for(Character key : c1.keySet()){if(c1.get(key) < c2.get(key)){return false;}}return true;}
}

[!Note]

思路如下:

  1. 获取 t 字符串的各种字符的计数(某些字符可能出现两次)
  2. 初始化对 s 字符串的字符计数器 charCounter ,即全为 0
  3. while(j < s.length())
    • j 后移找到满足条件的字符子串
    • i 后移找到不满足条件的字符子串(在这个循环中来更新最小子串结果)

关键点在于如何算是满足条件呢?如函数 passMuster 中定义,假如 t 中有 n 种字符,可只至少有一种字符与 t 中数量相同,其余大于或等于即算满足了。

空间复杂度为 O ( N ) O(N) O(N),时间复杂度为 O ( M N ) O(MN) O(MN),因为 M 轮循环都需要进行 N 次比较。所以优化点应该在如何减少比较次数

可以使用计数器,统计有多少字符是满足条件的,注意某些字符可能不止一个,如下我们使用 count 变量来计数

在这里可以看到我是统一把 count 的修改的判断放在了 charCounter.put 之前,这样写没错,但是放在之后就错了(leetcode 有一个案例不通过,但是我觉得没错)。

如此下来,每轮循环只有在 originCounter.containsKey(s.charAt(j)) 时才去进行判断,且仅比较一次,比较次数大大减少,时间复杂度优化到了 O ( M + N ) O(M+N) O(M+N) O ( N ) O(N) O(N) 为初始统计 t t t 中字符出现次数做的。 O ( M ) O(M) O(M) 为循环,每轮循环都是常数级别,所以总体就为 O ( M + N ) O(M+N) O(M+N)

public String minWindow(String s, String t) {// t 的计数Map<Character, Integer> originCounter = getOriginCounter(t);Map<Character, Integer> charCounter = getCharCounter(originCounter);// count 记录不满足条件的字符数量int count = originCounter.size();int i = 0, j = 0;String res = "";// 不满足条件时while (j < s.length()) {// j 后移找到满足条件的字符串while(j < s.length() && count != 0){if(originCounter.containsKey(s.charAt(j))){// 计算方式:如果加之前 + 1 == 字符数量,那么后面再遇到这个字符时不会影响 Count 的值了if(charCounter.get(s.charAt(j)) + 1 == originCounter.get(s.charAt(j))){count--;}charCounter.put(s.charAt(j), charCounter.get(s.charAt(j)) + 1);// 原来我是在这个位置写的如下形式,会造成同一字符多次修改 count// if(charCounter.get(s.charAt(j)) >= originCounter.get(s.charAt(j))){//     count--;// }}j++;}// i 前移找到不满足条件的字符串while(count == 0){if(res.isEmpty() || res.length() > j - i){res = s.substring(i, j);}if(originCounter.containsKey(s.charAt(i))){if(Objects.equals(charCounter.get(s.charAt(i)), originCounter.get(s.charAt(i)))){count++;}charCounter.put(s.charAt(i), charCounter.get(s.charAt(i)) - 1);}i++;}}return res;
}// getOriginCounter, getCharCounter

螺旋矩阵II

59. 螺旋矩阵II

题目链接

题目

给你一个正整数 n ,生成一个包含 1n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix

示例 1:

img

输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]

示例 2:

输入:n = 1
输出:[[1]]

提示:

  • 1 <= n <= 20

解答

解法一
class Solution {public int[][] generateMatrix(int n) {// 整数数组默认初始化为 0int[][] matrix = new int[n][n];// 四个方向int[][] direction = {{0, 1},{1, 0},{0, -1},{-1, 0}};int row = 0, col = 0, directionIndex = 0;for(int i = 1; i <= n * n; i++) {matrix[row][col] = i;int next_row = row + direction[directionIndex][0];int next_col = col + direction[directionIndex][1];// 切换方向的条件if(next_row == -1 || next_col == -1 || next_row == n || next_col == n || matrix[next_row][next_col] != 0) {directionIndex = (directionIndex + 1) % direction.length;}// 下一个位置row += direction[directionIndex][0];col += direction[directionIndex][1];}return matrix;}
}

主要是根据方向的规律,从左到右,从上到下,从右到左,从下到上的规律,依次填充

解法二
 public int[][] generateMatrix(int n) {// 整数数组默认初始化为 0int[][] matrix = new int[n][n];// 边界点, 从外圈向内一圈一圈填充int left = 0, right = n - 1, top = 0, bottom = n - 1;int value = 1;while(value <= n*n) {for(int i = left; i <= right; i++) {matrix[top][i] = value++;}for(int i = top + 1; i <= bottom; i++) {matrix[bottom][i] = value++;}for(int i = bottom - 1; i >= left; i--) {matrix[i][right] = value++;}for(int i = right - 1; i >= top; i--) {matrix[i][left] = value++;}left++;right--;top++;bottom--;}return matrix;
}

image-20250516093855755

该解法如图所示,最外层是我们第一轮要填充的,次外层填充方式类似于第一层(起始位置 row + 1, col + 1,结束位置 row - 1, col - 1)。

定义好 left,right,bottom,top 位置,填充好第一轮,然后修改四个值即可

54. 螺旋矩阵

题目链接

题目

给你一个 mn 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

示例 1:

img

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

示例 2:

img

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= m, n <= 10
  • -100 <= matrix[i][j] <= 100

解答

解法一
public List<Integer> spiralOrder(int[][] matrix) {List<Integer> array = new ArrayList<>();// m 行 n 列int m = matrix.length;int n = matrix[0].length;// 四个方向int left = 0, right = n - 1, top = 0, bottom = m - 1;while(left <= right && top <= bottom) {for(int i = left; i <= right; i++) {array.add(matrix[top][i]);}for(int i = top + 1; i <= bottom; i++) {array.add(matrix[i][right]);}// 这里需要加上额外的判断for(int i = right - 1; i >= left && bottom != top; i--) {array.add(matrix[bottom][i]);}for(int i = bottom - 1; i > top && left != right; i--) {array.add(matrix[i][left]);}left++;right--;top++;bottom--;}return array;
}
解法二

参考螺旋矩阵II的方向方法,只不过此处需要用一个 visit 数组来记录某个位置是否被访问过

public List<Integer> spiralOrder2(int[][] matrix) {List<Integer> array = new ArrayList<>();// m 行 n 列int m = matrix.length;int n = matrix[0].length;// 记录是否被访问过,默认全为 falseboolean[][] visited = new boolean[m][n];// 四个方向int[][] direction = {{0, 1},{1, 0},{0, -1},{-1, 0}};int row = 0, col = 0, directionIndex = 0;for(int i = 1; i <= m * n; i++) {array.add(matrix[row][col]);visited[row][col] = true;int next_row = row + direction[directionIndex][0];int next_col = col + direction[directionIndex][1];// 切换方向的条件if(next_row == -1 || next_col == -1 || next_row == m || next_col == n || visited[next_row][next_col]) {directionIndex = (directionIndex + 1) % direction.length;}// 下一个位置row += direction[directionIndex][0];col += direction[directionIndex][1];}return array;
}

LCR 146. 螺旋遍历二维数组

题目链接

题目

给定一个二维数组 array,请返回「螺旋遍历」该数组的结果。

螺旋遍历:从左上角开始,按照 向右向下向左向上 的顺序 依次 提取元素,然后再进入内部一层重复相同的步骤,直到提取完所有元素。

示例 1:

输入:array = [[1,2,3],[8,9,4],[7,6,5]]
输出:[1,2,3,4,5,6,7,8,9]

示例 2:

输入:array  = [[1,2,3,4],[12,13,14,5],[11,16,15,6],[10,9,8,7]]
输出:[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]

限制:

  • 0 <= array.length <= 100
  • 0 <= array[i].length <= 100

解答

class Solution {public int[] spiralArray(int[][] array) {List<Integer> res = new ArrayList<>();// m 行 n 列int m = array.length;if(m == 0)return new int[0];int n = array[0].length;if(n == 0)return new int[0];// 记录是否被访问过,默认全为 falseboolean[][] visited = new boolean[m][n];// 四个方向int[][] direction = {{0, 1},{1, 0},{0, -1},{-1, 0}};int row = 0, col = 0, directionIndex = 0;for(int i = 1; i <= m * n; i++) {res.add(array[row][col]);visited[row][col] = true;int next_row = row + direction[directionIndex][0];int next_col = col + direction[directionIndex][1];// 切换方向的条件if(next_row == -1 || next_col == -1 || next_row == m || next_col == n || visited[next_row][next_col]) {directionIndex = (directionIndex + 1) % direction.length;}// 下一个位置row += direction[directionIndex][0];col += direction[directionIndex][1];}return res.stream().mapToInt(Integer::intValue).toArray();}
}

区间和

58. 区间和

题目链接

题目

题目描述

给定一个整数数组 Array,请计算该数组在每个指定区间内元素的总和。

输入描述

第一行输入为整数数组 Array 的长度 n,接下来 n 行,每行一个整数,表示数组的元素。随后的输入为需要计算总和的区间下标:a,b (b > = a),直至文件结束。

输出描述

输出每个指定区间内元素的总和。

输入示例

5
1
2
3
4
5
0 1
1 3

输出示例

3
9

提示信息

数据范围:
0 < n <= 100000

解答

import java.util.Scanner;public class Main{public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();int[] arr = new int[n];int[] sum = new int[n];for (int i = 0; i < n; i++) {arr[i] = scanner.nextInt();}// 计算区间和sum[0] = arr[0];for(int i = 1; i < n; i++) {sum[i] = sum[i - 1] + arr[i]; // sum[i] = [0, i]}while(scanner.hasNext()) {int a = scanner.nextInt();int b = scanner.nextInt();int res = sum[b];if(a > 0){res -= sum[a - 1];}System.out.println(res);}scanner.close();}
}

思路,使用一个额外数组,让 sum[i] 存储的数值为 sum(arr[0]~arr[i])

开发商购买土地

44. 开发商购买土地

题目链接

题目

题目描述

在一个城市区域内,被划分成了n * m个连续的区块,每个区块都拥有不同的权值,代表着其土地价值。目前,有两家开发公司,A 公司和 B 公司,希望购买这个城市区域的土地。

现在,需要将这个城市区域的所有区块分配给 A 公司和 B 公司。

然而,由于城市规划的限制,只允许将区域按横向或纵向划分成两个子区域,而且每个子区域都必须包含一个或多个区块。 为了确保公平竞争,你需要找到一种分配方式,使得 A 公司和 B 公司各自的子区域内的土地总价值之差最小。

注意:区块不可再分。

输入描述

第一行输入两个正整数,代表 n 和 m。

接下来的 n 行,每行输出 m 个正整数。

输出描述

请输出一个整数,代表两个子区域内土地总价值之间的最小差距。

输入示例

3 3
1 2 3
2 1 3
1 2 3

输出示例

0

提示信息

如果将区域按照如下方式划分:

1 2 | 3
2 1 | 3
1 2 | 3

两个子区域内土地总价值之间的最小差距可以达到 0。

数据范围:

1 <= n, m <= 100;
n 和 m 不同时为 1。

解答

import java.util.Scanner;public class Main{public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();int m = scanner.nextInt();int[][] array = new int[n][m];for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {array[i][j] = scanner.nextInt();}}int sum = 0;// 行的和、列的和int[] rowSum = new int[n];int[] colSum = new int[m];for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {rowSum[i] += array[i][j];colSum[j] += array[i][j];sum += array[i][j];}}int min = sum; // 差值最小记录// 按行划分时int t = sum;for (int i = 0; i < n - 1; i++) {t -= rowSum[i];// 更新 minmin = Math.min(min, Math.abs(sum - 2 * t));}t = sum;for (int j = 0; j < m - 1; j++) {t -= colSum[j];min = Math.min(min, Math.abs(sum - 2 * t));}System.out.println(min);scanner.close();}
}

我们是统计行的和和列的和来做的,本质上就是前缀和。

我们还可以有如下做法,也算是一个小小的优化(少遍历几次)

import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();int m = scanner.nextInt();int sum = 0;int[][] vec = new int[n][m];for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {vec[i][j] = scanner.nextInt();sum += vec[i][j];}}int result = Integer.MAX_VALUE;int count = 0; // 统计遍历过的行// 行切分for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {count += vec[i][j];// 遍历到行末尾时候开始统计if (j == m - 1) {result = Math.min(result, Math.abs(sum - 2 * count));}}}count = 0;// 列切分for (int j = 0; j < m; j++) {for (int i = 0; i < n; i++) {count += vec[i][j];// 遍历到列末尾时候开始统计if (i == n - 1) {result = Math.min(result, Math.abs(sum - 2 * count));}}}System.out.println(result);scanner.close();}
}

相关文章:

  • Qt 信号和槽-核心知识点小结(11)
  • 创业分析平台Web端-三大前端核心语言详解-首页index
  • 71. 简化路径
  • 低功耗模式介绍
  • Kotlin协程异常处理全解析
  • 渗透测试核心技术:信息收集与扫描
  • 计算机系统的工作原理
  • 学习wps的书写格式(题目黑体,加粗,三号)
  • Python列表全面解析:从入门到精通
  • defer关键字:延迟调用机制-《Go语言实战指南》
  • 【android bluetooth 协议分析 01】【HCI 层介绍 4】【LeSetEventMask命令介绍】
  • C++实现伽罗华域生成及四则运算(二)
  • UI架构的历史与基础入门
  • 楼宇【复习】
  • python打卡day29@浙大疏锦行
  • AGI大模型(23):LangChain框架快速入门之LangChain介绍
  • unity开发游戏实现角色筛选预览
  • 2025年PMP 学习十九 第12章 项目采购管理
  • 数据结构:二叉树一文详解
  • CSS-in-JS:现代前端样式管理的革新
  • 远洋渔船上的命案
  • 慢品巴陵,看总编辑眼中的岳阳如何书写“山水人文答卷”
  • 下辖各区密集“联手”,南京在下一盘什么样的棋?
  • 穆迪下调美国主权信用评级
  • 阿里上财年营收增6%,蒋凡:会积极投资,把更多淘宝用户转变成即时零售用户
  • 沪指跌0.68%报3380.82点,创指跌1.92%:券商、军工跌幅靠前