代码随想录-数组
文章目录
- 二分查找
- 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
提示:
- 你可以假设
nums
中的所有元素是不重复的。 n
将在[1, 10000]
之间。nums
的每个元素都将在[-9999, 9999]
之间。
解答
我的做法,改进点
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;}
}
讲解
二分查找的两个前提
- 数组有序
- 数组中无重复元素(一旦重复,二分查找返回的元素下标就不唯一了)
二分查找涉及一些边界条件,到底是 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
。
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
只需要考虑三种情况下返回什么值即可
target
小于数组最小值时,最后一次判断一定是比较的nums[0]
与target
。比较时,left == right == 0
,比较完后,left = 0, right = -1
,应该返回left
target
大于数组最大值时,最后一次判断一定是比较的nums[nums.size() - 1]
与target
。比较时,left == right == nums.size() - 1
,比较完后,left = nums.size(), right = nums.size() - 1
,应该返回left
- 我们不去推算了,我们就想,最后结束循环时一定是
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[pos−1]<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};
}
思路:
- 与简单二分查找找值不同,因为可能会有多个目标值
- 将查找范围改为找边界点
- 右边界点可以去找从左到右第一个大于
target
的下标 - 左边界点可以去找从左到右最后一个小于等于
target
的下标 - 最后结果判断比较繁琐,要考虑越界的问题
[!Note]
在进行
target
与中间值进行对比后,我们去修改了left
或者right
,在不同判断条件下去修改这两个下标,其实只有一个是我们最终要得到的结果,另一个只是用来结束循环的右边界和左边界的寻找之所以这么做,有部分原因是因为整除
(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
的时候往右走,从而不会陷入死循环。
官方的思路非常清晰
-
先找第一个元素位置,就用二分法
nums[mid] < target
时,范围变为[mid + 1, right]
nums[mid] > target
时,范围变为[left, mid - 1]
nums[mid] == target
时,范围变为[left, mid]
最后结束循环时判断一下,该位置是否是要找的
target
-
如果第一个元素位置找到了,意味着最后一个元素位置一定是存在的
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;}
}
也可以使用交换的方式来做,但是实质上和上面的方法是一样的
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++;}}
}
844. 比较含退格的字符串
题目链接
题目
给定 s
和 t
两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 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
s
和t
只含有小写字母以及字符'#'
解答
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
表示当前待删除的字符的数量。每次我们遍历到一个字符:
若该字符为退格符,则我们需要多删除一个普通字符,我们让 skip
加 1
;
若该字符为普通字符:
-
若
skip
为0
,则说明当前字符不需要删去; -
若
skip
不为0
,则说明当前字符需要删去,我们让skip
减1
。
这样,我们定义两个指针,分别指向两字符串的末尾。每次我们让两指针逆序地遍历两字符串,直到两字符串能够各自确定一个字符,然后将这两个字符进行比较。重复这一过程直到找到的两个字符不相等,或遍历完字符串为止。
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;
}
思路即一种滑动窗口,也可以理解为一种双指针
当 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
s
和t
由英文字母组成
**进阶:**你能设计一个在 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]
思路如下:
- 获取
t
字符串的各种字符的计数(某些字符可能出现两次)- 初始化对
s
字符串的字符计数器charCounter
,即全为0
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
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
示例 1:
输入: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;
}
该解法如图所示,最外层是我们第一轮要填充的,次外层填充方式类似于第一层(起始位置 row + 1, col + 1
,结束位置 row - 1, col - 1
)。
定义好 left,right,bottom,top
位置,填充好第一轮,然后修改四个值即可
54. 螺旋矩阵
题目链接
题目
给你一个 m
行 n
列的矩阵 matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入: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();}
}