系列汇总:《刷题系列汇总》
——————《剑指offeer》———————
1. 二维数组查找(二分)
- 题目描述:在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
- 优秀思路:因为数组每行从左到右递增,每列从上到下递增,故可从数组左下角开始二分查找。小于该值则上移,大于则右移,也可从右上角开始。
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix.length == 0 || matrix[0].length == 0) return false;
int i = matrix.length-1;
int j = 0;
while(i >= 0 && j < matrix[0].length){
if(matrix[i][j] == target) return true;
else if(matrix[i][j] < target) j++;
else i--;
}
return false;
}
}
2.斐波那契数列
- **题目描述:**大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。
- 普通思路:逐项计算,缺点是n比较大时,计算速度慢复杂度高
- 优秀思路:每次计算的和都存储起来,避免重复计算,避免了数组的使用
if(n==0 || n==1) return n;
int a = 0;
int b = 1;
int sum = 0;
for(int i = 2;i<n+1;i++){
sum = a + b;
a = b; //上次的第二项 = 这次的第一项
b = sum;//上次的和 = 这次的第二项
}
return sum;
3.数字在升序数组中出现的次数。
- 题目描述:统计一个数字在升序数组中出现的次数。
- 普通思路:遍历比较
- 优秀思路:利用二分查找确定该数字出现的下界位置和上界位置,两位置相减即可获得该数字出现的次数(注意寻找左右边界时的细微条件差别)
int lLimit = 0, rLimit = array.length;
// 1.寻找左边界
int tempL = 0, tempR = array.length;
while(tempL < tempR){
int mid = (tempL + tempR)/2; // 自动取整
if(target > array[mid]){ // 该条件对左边界要求更为严格
//更新左边界
tempL = mid + 1;
}else{
tempR = mid;
}
}
lLimit = tempL;
// 2.寻找右边界
tempL = 0;
tempR = array.length;
while(tempL < tempR){
int mid = (tempL + tempR)/2; // 自动取整
if(target < array[mid]){ // 该条件对右边界要求更为严格
//更新右边界
tempR = mid;
}else{
tempL = mid + 1;
}
}
rLimit = tempL;
// 输出
return rLimit - lLimit;
4.和为S的两个数字(双指针)
- 题目描述:输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
- 普通思路:逐个遍历查找
- 优秀思路:因为是有序数组,所以利用双指针,分别从首尾向中间搜索(注意,根据数学知识可只,最外层的乘积最小,故从外层开始一旦发现符合条件的就立即输出)
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
ArrayList<Integer> result = new ArrayList<Integer>();
int left = 0, right = array.length-1;//双指针
//边界判断
if(array == null || array.length == 0) return result;
while(left < right){
if(array[left] + array[right] == sum){
result.add(array[left]);
result.add(array[right]);
return result;
}
// 双指针更新过程
if(array[left] + array[right] < sum){
left++;
}else{
right--;
}
}
return result;
}
5.数组中出现次数超过一半的数字(题目理解)
- 题目描述:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
- 普通思路:循环数组,计算每个元素出现的次数
- 优秀思路:充分利用“该数字出现的次数超过数组长度的一半”这一条件,若两元素相同,则同归于尽,若某元素过半,则最终留下的元素一定是该元素。最后判断下留下的元素是否数量过半即可
public int MoreThanHalfNum_Solution(int [] array) {
//边界判断
if(array == null || array.length == 0) return 0;
//1.若两元素相同,则同归于尽,若某元素过半,则最终留下的元素一定是该元素
int win = array[0];
int count = 1;//若两元素不同则-1,否则+1
for(int i = 1;i < array.length;i++){
if(array[i] == win){
count++;
}else{
count--;
}
if(count == 0){
win = array[i];
count = 1;
}
}
// 2.判断win元素是否过半
count = 0;
for(int i = 0;i < array.length;i++){
if(array[i] == win) count++;
}
if(count > array.length/2) return win;
return 0;
}
6.把数组排成最小的数(小技巧)
- 题目描述:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
- 优秀思路:利用“比较两个字符串s1, s2大小的时候,先将它们拼接起来,比较s1+s2,和s2+s1那个大,如果s1+s2大,那说明s2应该放前面,所以按这个规则,s2就应该排在s1前面”这一规则,循环比较,造成结果较小的往前移,造成结果较大的往后移,最后组合起来即可
public String PrintMinNumber(int [] numbers) {
int temp = 0;
for(int i = 0;i<numbers.length;i++){
for(int j = i+1;j<numbers.length;j++){
String str1 = Integer.toString(numbers[i]);
String str2 = Integer.toString(numbers[j]);
if(Integer.parseInt(str1+str2) > Integer.parseInt(str2+str1)){
//换位
temp = numbers[i];
numbers[i] = numbers[j];
numbers[j] = temp;
}
}
}
String str = new String();
for(int i = 0;i<numbers.length;i++){
str += numbers[i];
}
return str;
}
7.重建二叉树(数、dfs)
- 题目描述:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。原理参考二叉树的前序序列和中序序列
- 优秀思路:懂原理就好做,大概思路就是根据前序排列确定根节点,在中序节点中根节点以前的是前序排列,根节点之后的是中序排列,递归重复以上步骤。
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
//边界条件
if(pre==null||pre.length==0||in==null||in.length==0) return null;
TreeNode binaryTree = new TreeNode(pre[0]); // 建立以前序序列[0]为根节点开始的二叉树
for(int i = 0;i<in.length;i++){
// 找到中序节点中的根节点
if(in[i]==pre[0]){
// 注意:1.copyOfRange 左闭右开;2.只能用left和right
binaryTree.left = reConstructBinaryTree
(Arrays.copyOfRange(pre,1,i+1),Arrays.copyOfRange(in,0,i));
binaryTree.right = reConstructBinaryTree
(Arrays.copyOfRange(pre,i+1,pre.length),Arrays.copyOfRange(in,i+1,in.length));
}
}
return binaryTree;
}
8.机器人的运动范围
- 题目描述:地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
- 优秀思路:
首先在某个节点处,要调用递归来决定某个位置的下一步去哪,此时有4个选择,每个选择都会进入下一个递归调用。当进入某个位置时,应当标记这个位置已访问过,避免之后又来到这里,从而重复计算,因此设计一个boolean的数组,这里设计的二维,也可以通过压缩,使用一维数组来表征某个位置是否访问。二维就是记录横纵坐标即第i行第j列的数据被访问了,直观,推荐新手用二维。接着就是边界条件和递归结束的条件的判断了。
这类型题也是有套路的,主方法在原点作为起点,调用第一次递归后,在递归方法中,首先判断边界条件以及题目中所提的要求是否满足(本题的要求就是cal方法中的实现),都没问题,说明该位置可以访问,然后改变对应位置的标记。然后就是以该节点为中心,考虑下一步怎么走,本题就是4种走法,可以分开写,也可以一起写,由于本题是计数,所以就直接加在一起。然后return这些求和结果再+1,求和结果是下一步走的结果,而+1是本次访问此时的节点的次数。
public class Solution{
// 主函数:参数分别为门限、总行数、总列数
public int movingCount(int threshold, int rows, int cols){
if(threshold < 0 || rows <= 0 || cols <= 0) return 0;
// 定义一个二维boolean数组记录每个节点被访问的状态
boolean[][] visitStates = new boolean[rows][cols]; // 若已访问则置为true
// 核心函数
int count = movingCount(threshold,rows,cols,0,0,visitStates);
return count;
}
// 核心函数:计算总次数,参数分别为门限、总行数、总列数、当前访问节点行值、列值、访问状态
private int movingCount(int threshold, int rows, int cols, int rowCurrent, int colCurrent, boolean[][] visitStates){
if (rowCurrent < 0 || colCurrent < 0 || rowCurrent >= rows || colCurrent >= cols || visitStates[rowCurrent][colCurrent] || caculate(rowCurrent) + caculate(colCurrent) > threshold)
return 0;
visitStates[rowCurrent][colCurrent] = true;
// 递归调用
int count = 1 + movingCount(threshold,rows,cols,rowCurrent + 1,colCurrent,visitStates) //下移
+ movingCount(threshold,rows,cols,rowCurrent,colCurrent + 1,visitStates) // 右移
+ movingCount(threshold,rows,cols,rowCurrent - 1,colCurrent,visitStates) // 上移
+ movingCount(threshold,rows,cols,rowCurrent,colCurrent - 1,visitStates); // 左移
return count;
}
// 计算某数的所有位之和
private int caculate(int num){
int sum = 0;
while(num>0){
sum += num % 10;
num /= 10;
}
return sum;
}
}
9.顺时针打印矩阵(边界更新)
- 题目描述:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
- 优秀思路:简单来说,就是不断地收缩矩阵的边界。定义四个变量代表范围,up、down、left、right
- 向右走存入整行的值,当存入后,该行再也不会被遍历,代表上边界的 up 加一,同时判断是否和代表下边界的 down 交错
- 向下走存入整列的值,当存入后,该列再也不会被遍历,代表右边界的 right 减一,同时判断是否和代表左边界的 left 交错
- 向左走存入整行的值,当存入后,该行再也不会被遍历,代表下边界的 down 减一,同时判断是否和代表上边界的 up 交错
- 向上走存入整列的值,当存入后,该列再也不会被遍历,代表左边界的 left 加一,同时判断是否和代表右边界的 right 交错
public ArrayList<Integer> printMatrix(int [][] matrix) {
ArrayList<Integer> list = new ArrayList<>();
// 边界条件
if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
return list;
}
// 定义4个范围
int up = 0;
int down = matrix.length-1;
int left = 0;
int right = matrix[0].length-1;
while(true){
// 上行
for(int col=left;col<=right;col++){
list.add(matrix[up][col]);
}
up++;// 更新上边界
if(up > down){
break;
}
// 右列
for(int row=up;row<=down;row++){
list.add(matrix[row][right]);
}
right--;// 更新右边界
if(left > right){
break;
}
// 下行
for(int col=right;col>=left;col--){
list.add(matrix[down][col]);
}
down--;// 更新下边界
// 判断是否越界
if(up > down){
break;
}
// 左列
for(int row=down;row>=up;row--){
list.add(matrix[row][left]);
}
left++;// 更新左边界
if(left > right){
break;
}
}
return list;
}
10.数组中的逆序对(归并排序)
- 题目描述:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007(题目保证输入的数组中没有的相同的数字)
- 优秀思路:利用归并排序(升序),当时候遇到左边需要调整到右边去的元素时既可以计算逆序对的个数了,本身左边和右边都是有序的,所以当左边的
i
比右边的j
要大时,左边比i
大的数一定也比右边的j
要大,这样就有i-mid+1
个逆序对了。继续合并继续计算其他的逆序对
public class Solution {
public int sum = 0;
public void mergeSort(int[] array, int start, int end){
if(start>=end){
return;
}
int mid = (start+end)/2;
mergeSort(array, start, mid);
mergeSort(array, mid+1, end);
merge(array, start, mid, end);
}
public void merge(int[] array, int start, int mid, int end){
// 存合并之后的数组的
int l = end-start+1;
int[] temp = new int[l];
// 前面数组的起始
int i = start;
// 后面数组的起始
int j = mid+1;
// temp的下标
int k = 0;
// 把小的往前放。
while(i<=mid&&j<=end){
// 把小的存到前面
if(array[i]<=array[j]){
temp[k] = array[i];
i++;
}else {
temp[k] = array[j];
j++;
// 如果当前i的元素大于后面的元素,那么当前i元素之后的(从i到mid)所有元素都能和j指向的元素构成逆序对。
sum = (sum+(mid-i+1))%1000000007;
}
k++;
}
// 剩下哪个数组没合并完,就把它全拷贝到后面
while(i<=mid){
temp[k] = array[i];
i++;
k++;
}
while(j<=end){
temp[k] = array[j];
j++;
k++;
}
// 合并完的拷贝回去
for(k=0;k<l;k++){
array[start+k] = temp[k];
}
}
public int InversePairs(int [] array) {
mergeSort(array, 0, array.length-1);
return sum;
}
——————《LeetCode》———————
1.两数之和
- 题目描述:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。
- 优秀思路:为了避免循环寻找 target - x,使用哈希表,可以将寻找 target - x 的时间复杂度降低到从 O(N) 降低到 O(1),对于每一个 x,我们首先查询哈希表中是否存在 target - x
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; ++i) {
if (hashtable.containsKey(target - nums[i])) {
return new int[]{hashtable.get(target - nums[i]), i};
}
hashtable.put(nums[i], i);
}
return new int[0];
}
2.寻找两个正序数组的中位数(困难)
- 题目描述:给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
- 普通思路:归并排序
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
//归并排序
ArrayList<Integer> list=new ArrayList<Integer>();
int i = 0;
int j = 0;
while(i<nums1.length && j<nums2.length){
if(nums1[i]<nums2[j]){
list.add(nums1[i]);
i++;
}else{
list.add(nums2[j]);
j++;
}
}
//把没排序到的其余元素贴在list后面
while(i<nums1.length){ // 前数组没排完
list.add(nums1[i]);
i++;
}
while(j<nums2.length){ // 后数组没排完
list.add(nums2[j]);
j++;
}
//计算中位数
double mid = list.size() % 2 == 0 ? ((Double.valueOf(list.get(list.size()/2-1))+Double.valueOf(list.get(list.size()/2)))/2) : Double.valueOf(list.get((list.size()-1)/2));
return mid;
}
}
- 优秀思路:
- 1、不用存储新矩阵,只需找到中位数的位置即可
- 2、待补充
3.盛水最多的容器`
- 题目描述:给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
- 优秀思路:双指针,从两边往中间搜索,移动较小的数值那边的指针。
class Solution {
public int maxArea(int[] height) {
// 双指针
int i = 0;
int j = height.length - 1;
int m = 0;//高度
int n = 0;//宽度
int maxC = 0;
while(i < j){
n = j - i;
if(height[i]>height[j]){
m = height[j];
j--;
}else{
m = height[i];
i++;
}
maxC = m*n > maxC ? m*n : maxC;
}
return maxC;
}
}
4.接雨水
- 题目描述:给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
- 我的思路:利用高低高的物理结构找出答案
class Solution {
public int trap(int[] height) {
if(height==null || height.length == 0) return 0;
// 能存储水的结构一定是:高 低... 高
// 每一块储水区的高度 height = min(高1,高2)
// 每一块的储水区的雨水 = (索引差-1)*height - 柱子面积
// 以最高值为界限,划分左右两区域
int maxIndex = 0; // 最大值索引
int max = 0;
int sumS = 0; // 总储水量
int tempS = 0; // 某块的柱子面积
// 找到最大值
for(int k = 0;k<height.length;k++){
if(height[k] > max){
max = height[k];
maxIndex = k;
}
}
// 1.最大值左边
int j = 0;//左高点的索引
int limit = height[j]; //左高点
int i = j + 1;
while(i <= maxIndex){
if(height[i] >= limit){ //碰到右高点
sumS += limit*(i-j-1)-tempS;
limit = height[i]; // 重置左高点
tempS = 0; // 重置柱子面积
j = i; // 更新左高点索引
}else{ //进入低点
tempS += height[i];
}
i++;
}
// 2.最大值右边
j = height.length - 1;//右高点的索引
limit = height[j]; //右高点
i = j - 1;
tempS = 0;
while(i >= maxIndex){
if(height[i] >= limit){ //碰到左高点
sumS += limit*(j-i-1)-tempS;
limit = height[i]; // 重置右高点
tempS = 0; // 重置柱子面积
j = i; // 更新右高点索引
}else{ //进入低点
tempS += height[i];
}
i--;
}
return sumS;
}
}
- 优秀思路:双指针,从两边往中间搜索
class Solution {
public int trap(int[] height) {
int ans = 0;
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = Math.max(leftMax, height[left]);
rightMax = Math.max(rightMax, height[right]);
if (height[left] < height[right]) { //总是计算更低处的水面积
ans += leftMax - height[left];
++left;
} else {
ans += rightMax - height[right];
--right;
}
}
return ans;
}
}
5.最大子序和(动态规划)
- 题目描述:给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和
- 输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
- 输出:6
- 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
- 优秀思路:利用动态归划的思想
class Solution {
public int maxSubArray(int[] nums) {
if(nums.length == 0 || nums == null) return 0;
if(nums.length == 1) return nums[0];
int pre = 0;//代指前面部分的和
int max = nums[0];
for(int x: nums){
pre = Math.max(pre+x,x); //意思是如果加上前面的部分反倒变小的话,则抛弃掉前面的部分
max = Math.max(max,pre); //更新最大值
}
return max;
}
}
6.三数之和为0(排序后双指针)
- 题目描述:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
- 注意:答案中不可以包含重复的三元组。
- 优秀思路:本题的难点在于如何去除重复解。
- 特判,对于数组长度 n,如果数组为 null 或者数组长度小于 3,返回 []。
- 对数组进行排序。
- 遍历排序后数组:
- 若 nums[i]>0:因为已经排序好,所以后面不可能有三个数加和等于 0,直接返回结果。
- 若出现重复元素:跳过,避免出现重复解
- 其他情况,选择其中一个数为nums[i],令左指针 L=i+1,右指针 R=n-1,当 L<R 时,执行循环:
- 当 nums[i]+nums[L]+nums[R]==0,执行循环,判断左界和右界是否和下一位置重复,去除重复解。并同时将 L,R移到下一位置,寻找新的解
- 若和大于 0,说明 nums[R]太大,R 左移
- 若和小于 0,说明 nums[L]太小,L 右移
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
//排序
Arrays.sort(nums);
//双指针
int len = nums.length;
for(int i = 0;i < len;++i) {
if(nums[i] > 0) return lists; //最小值都>0
if(i > 0 && nums[i] == nums[i-1]) continue; //重复1:遍历值重复,跳到下一次
int curr = nums[i];
int L = i+1, R = len-1;
while (L < R) {
int tmp = curr + nums[L] + nums[R];
if(tmp == 0) {
List<Integer> list = new ArrayList<>();
list.add(curr);
list.add(nums[L]);
list.add(nums[R]);
lists.add(list);
while(L < R && nums[L+1] == nums[L]) ++L;//重复2:左边界值重复
while (L < R && nums[R-1] == nums[R]) --R;//重复3:有边界重复
++L;
--R;
} else if(tmp < 0) {
++L;
} else {
--R;
}
}
}
return lists;
}
7.买股票的最佳时机
-
题目描述:给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
- 输入:[7,1,5,3,6,4]
- 输出:5
- 解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
-
优秀思路:一次遍历即可,核心在于循环体中的操作
class Solution {
public int maxProfit(int[] prices) {
if(prices.length<2) return 0;
int minValue = Integer.MAX_VALUE;
int maxProfit = 0;
for(int i = 0;i < prices.length;i++){
if(prices[i] <= minValue){
minValue = prices[i]; //若出现更小的值则更新最小值
}else{
maxProfit = Math.max(maxProfit,prices[i]-minValue); // 若未出现更小值,则计算该值与最小值的差值
}
}
return maxProfit;
}
}
8.除自身以外数组的乘积
- 题目描述:给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
- 优秀思路:
class Solution {
public int[] productExceptSelf(int[] nums) {
int[] result = new int[nums.length];
int res1 = 1, res2 = 1; //分别为左三角和右三角的值
// 左三角
for(int i = 0;i < nums.length;i++){
result[i] = res1; // 等于之前所有元素的乘积
res1 *= nums[i];
}
for(int i = nums.length - 1;i >= 0;i--){
result[i] *= res2; //从倒数第2个开始计算右三角
res2 *= nums[i];
}
return result;
}
}
9.数组拆分 I
- 题目描述:给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), …, (an, bn) ,使得从 1 到 n 的 min(ai, bi) 总和最大。
- 我的思路:与标准答案思路一致
class Solution {
public int arrayPairSum(int[] nums) {
if(nums == null || nums.length == 0) return 0;
int sum = 0;
//排序
Arrays.sort(nums);
//分别找出倒数第2个、第4个...相加
for(int i = nums.length -2;i>=0;i -= 2){
sum += nums[i];
}
return sum;
}
}
10.最大矩形(困难)
-
题目描述:给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
-
优秀思路:遍历所有元素,当为1时计算以当前1结尾的连续 1 的个数,并向上遍历寻找以当前1为右下角的所有矩形,计算最大面积。核心是怎么确定遍历矩形的长和宽,图解如下:
class Solution {
public int maximalRectangle(char[][] matrix) {
if (matrix.length == 0) {
return 0;
}
//保存以当前数字结尾的连续 1 的个数
int[][] width = new int[matrix.length][matrix[0].length];
int maxArea = 0;
//遍历每一行
for (int row = 0; row < matrix.length; row++) {
for (int col = 0; col < matrix[0].length; col++) {
//计算width:保存以当前数字结尾的连续 1 的个数
if (matrix[row][col] == '1') {
if (col == 0) {
width[row][col] = 1;
} else {
width[row][col] = width[row][col - 1] + 1;
}
} else {
width[row][col] = 0;
}
//记录所有行中最小的数
int minWidth = width[row][col];
//从本行开始向上扩展行
for (int up_row = row; up_row >= 0 && matrix[up_row][col] == '1'; up_row--) {
int height = row - up_row + 1;
//找最小的数作为矩阵的宽
minWidth = Math.min(minWidth, width[up_row][col]);
//更新面积
maxArea = Math.max(maxArea, height * minWidth);
}
}
}
return maxArea;
}
}