本文关于笔试中有关数组的算法题的解题思路,
算法不仅考验智慧,同样也有做题的套路
适合人群
本文适合无算法基础,想系统学习算法的学习人,也同样适用有些基础但是做题卡壳的学习人。
不多哔哔,直接看题,建议有条件练习的,一定多自己去实现代码,算法也是一个积累的过程,从恍然大悟到熟能生巧。
温馨提示:本文篇幅较长
拿到这个题先分析:
- 给出的条件:排序好的数组
- 要求:
原地
删除重复的元素- 返回移除元素数组之后的数组的长度
原地删除,不能使用额外的空间,要删除重复的元素,首先要获得数组中的元素,那一定就要遍历数组,提供的是排序,因此只需要比较互相相邻的多个元素就可以确定是否是重复的元素
class Solution {
public int removeDuplicates(int[] nums) {
//1.要求原地删除数组中重复的元素
//首先的想法是如何删除数组中重复的元素---暴力法->遍历数组
//原地删除,就是不使用额外的空间
if (nums.length==0){
return 0;
}
int i = 0;
for (int j = 1; j < nums.length; j++) {
if (nums[j] != nums[i]){
i++;
nums[i] = nums[j];
}
}
return i + 1;
}
}
时间复杂度分析:使用了双指针的方式,在数组的原地重新赋值,实现了对重复元素的删除,时间复杂度:只使用了一层的for循环来遍历数组,因此时间复杂度是O(N)
这里给出遍历数组访问前一个元素和该元素的后一个元素的代码
public static void main(String[] args) {
int [] arr = new int[10];
for (int i = 0; i < arr.length; i++) {
for (int j = i+1; j < arr.length-1 ; j++) {
}
}
}
先给出链表的数据结构在Java中的实现
import java.util.Queue;
import java.util.Stack;
/**
* @author 雷雨
* @date 2020/9/16 19:49
* 链表的实现
*/
public class ListNodes {
public static void main(String[] args) {
Stack stack = new Stack();
}
MyNodes head = null;
public void add(int data){
MyNodes newNode = new MyNodes(data);
if (head==null){
head=newNode;
return;
}
MyNodes temp = head;
while (temp!=null){
temp = temp.next;
}
temp.next = newNode;
}
}
class MyNodes{
MyNodes next = null;
int data;
public MyNodes(int data){
this.data =data;
}
}
反转链表的处理思路是:
第一种思路:可以使用额外的存储空间的话,那么就创建一个新的链表,然后一直去访问原链表的尾节点将其放入新创建的链表中
第二种思路:不使用额外的存储空间的话,那就采用双指针,一个指针指向头,一个指针指向尾,每次都同时这两个结点的前后节点(用来改变链表的指针指向).
归根结底就是链表的基本操作的变形,特别去注意链表的插入和删除时的代码实现.
做算法题,
如果刚开始没有思路,先想一些比较low的作法,只要是能完成题目的要求也是可以的
,然后再想办法将算法优化.往往第一个思路会给后面算法的优化提供很好的思路.
当我拿到这道题目的时候,没思路,那么就暴力法
针对该题的暴力法:就是遍历整个数组,将数组中的每个数都与数组中其他的数配对,寻找是否有相加的结果是target的组合,如果有,那么就输出他们的下标值。
class Solution {
public int[] twoSum(int[] nums, int target) {
//1.比较容易想到的方法就是使用暴力法,穷举出所有的数的组合
//缺点也很明显,就是时间复杂度比较高是n^2
//时间复杂度比较低的算法是使用哈希表的方式
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length ; j++) {
if (nums[i] == target - nums[j]){
return new int[]{i,j};
}
}
}
throw new IllegalArgumentException("No two sum solution");
}
}
想要提升算法的能力,一定要在做题之前和做题之后分析自己算法的时间复杂度(一般情况下,不太考虑空间复杂度)
我们使用了暴力法,在遍历数组中每个元素和别的元素进行组合的时候使用了双层的for循环,因此时间复杂度是n^2,这个时间复杂度算是比较高阶的时间复杂度了,是比较low的算法。
要解决这个两数之和的算法,我们还可以使用哈希表,但是我希望做一个系统方法记录,因此哈希表的算法在之后整理了哈希表的算法总结之后再回过头来讲解这道题的解法。
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map = new HashMap<Integer,Integer>();
for(int i = 0; i< nums.length; ++i){
map.add(num[i],i);
}
for(int j = 0;j < nums.length;++j){
if(map.containKey(target -nums[j]) && map.get(target - nums[j]) !=j){
return new int[] {i,map.get(target - nums[j])};
}
}
throw new IllegalArgumentException("No two sum solution");
}
}
使用哈希表的方式解决两数之和的问题避免了使用嵌套的for loop,使用map来判断元素是否存在的时间复杂度是O(1)的,不需要额外的遍历数组。
这个题和删除排序数组中的重复项非常的相似,可以同样使用数组+双指针的方式解答
class Solution {
public int removeElement(int[] nums, int val) {
int i = 0;
for (int j = 0; j < nums.length; j++) {
if (nums[j] != val){
nums[i] =nums[j];
i++;
}
}
return i;
}
}
时间复杂度分析:单层for循环,O(n)的时间复杂度
这个题是学习数据结构和算法中非常常见的一道题
拿到题,可以直接根据题目的需求,可以直接思考暴力求解的方式(因为该题目的思路,题目已经说明的非常清晰了)
class Solution {
public int fib(int N) {
//1.第一种方法,就是使用暴力法,递归迭代算出所以的N对应的值,直到算出N对应的fib返回
if (N==0){
return 0;
}else if (N==1){
return 1;
}else {
return fib(N-1)+fib(N-2);
}
}
}
时间复杂度分析:使用了递归,循环调用自己的函数,时间复杂度是N^2
思考:可优化的地方,我们在求fib的时候会发现,我们其实重复的求了很多个值的fib,而这都是耗费时间的。如果能够将求出的fib(n)的值都存储起来,在需要的时候直接访问到
,那么时间的花费应该会减少
因此就有了第二种方法:记忆式的自底向上fib求值
class Solution {
public int fib(int N) {
/**第二种方式:记忆式的自底向上
* 相当于每次都求出了n的fib的具体数值并存储起来(下次需要的时候就不用重复的求值),这样的算法就是
* 降低了时间复杂度的算法,然后n一直的递增的,直到n递增到N的时候,计算出fib(N),然后返回
* */
if (N <= 1){
return N;
}
return memoize(N);
}
public int memoize(int N){
int [] cache = new int[N+1];
cache[1] = 1;
for (int i = 2; i <= N ; i++) {
cache[i] = cache[i - 1] + cache[i - 2];
}
return cache[N];
}
}
整体的思路:使用了一个数组将之前求出的比较小的fib(n)的值存储起来,在求fib(n)比较大的数值时,使用之前存储起来的值即可,不需要重复的求fib(n)
**时间复杂度分析:**因为要层fib(2)开始循环求值到fib(n),因此使用了一个for循环,因此复杂度应为0(n).
需要注意的点:
- 在解决算法问题时,往往暴力求解方法中重复求值的地方(或者有规律可循的地方)就是我们可优化算法的地方
- 思考如何将重复的事情优化并转化为数据结构是非常重要的。
示例:
//
// 输入: [-2,1,-3,4,-1,2,1,-5,4]
// 输出: 6
// 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
思路分析:(使用动态规划的方法)
这个问题的思考策略就是思考当前位置nums[i]
表示当前位置i的最大子序列和
是否是需要将该位置的下一个位置加进去组成新的最大子序列和。考虑的逻辑也非常好思考:就是如果加入了下一个位置的元素组成的新的最大子序列的和是小于不加入之前的最大子序列的和,那么就不加入
总的来说,就是分解子问题,子问题都有相同的解题步骤,使用循环访问到了每一个子问题,在循环中维护了一个当前的最大子序列和
代码实现
class Solution {
public int maxSubArray(int[] nums) {
//先看题目要求,要求O(1)的空间复杂度和O(n)的时间复杂度
//求连续的子序列和,那么就是要看该位置加上下一个位置的值和该位置的值是增大还是减小了
//也就是假如: pre 代表的是当前位置的最大的子序列和
//那么求Math.max(pre+x,x)取大的赋值给当前位置的最大子序列和也就是pre
//用来维护当前位置和当前位置的前一个位置的值
int pre = 0;
//定义初始的最大子序列和为数组的第一个元素
int maxAnx = nums[0];
for( int i = 0; i = nums.length ;i++ ) {
pre = Math.max(pre + nums[i], nums[i]);
maxAns = Math.max(pre,maxAns);
}
return maxAns;
}
}
最近在刷算法题的时候,发现了很多人的算法题解的for loop中很喜欢写成++i而不是i++,到底两者之间有什么区别呢?
特意进行了测试
这个题:
给定一个整数数组,判断是否存在重复元素。
// 如果任意一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。
class Solution {
public boolean containsDuplicate(int[] nums) {
//注意在for loop中的++i和i++的循环效果都是一样的,但是++i的效率比i++更高,原因是
//i++ 取值 复制 +1 返回
//++i 取值 加一 返回
Arrays.sort(nums);
for (int i = 0; i < nums.length - 1; ++i) {
if (nums[i] == nums[i+1])return true;
}
return false;
}
}
这个for loop中的i++和++i的循环效果是一样的,都是先执行,然后在加一再循环。
但是他们之间的效率是有差异的,这也就是很多大佬(大佬都很注意细节)都喜欢在for loop中写++i的原因。
注意在for loop中的++i和i++的循环效果都是一样的,但是++i的效率比i++更高,原因是
i++ 取值 复制 +1 返回
++i 取值 加一 返回
//给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k。
思路:
本道题刚拿到题时,可能会想到暴力解法,使用双层循环访问数组中该位置的元素和该位置之前的所有元素,看是否有相同的元素,但是由于使用双层循环(一层循环控制该元素的位置一直后移,内部的循环控制了要循环访问该位置之前的每个节点),因此时间复杂度是O(n^2)的。
这样的解答超过了要求的时间。
总结:我们希望在访问元素时能在常熟的时间复杂度内实现查询,插入和删除
,然后用单层循环来控制当前元素向后移,该算法的时间复杂度是O(n)的
class Solution {
public boolean containsNearbyDuplicate(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(nums[i]))return true;
map.put(nums[i],i);
if (map.size()>k) map.remove(nums[i-k]);
}
return false;
}
}
注意在该算法中有一个小的技巧:既然要求数组中两个元素之间的下标位置不能超过k,那么我们就动态的删除map中的元素使循环时需要判断的元素数变少
10.24程序员快乐
给定一个已按照升序排列的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
class Solution {
public int[] twoSum(int[] numbers, int target) {
for (int i = 0; i < numbers.length - 1; i++) {
for (int j = i + 1; j < numbers.length ; j++){
if (numbers[i] == target - numbers[j]){
return new int[]{i+1,j+1};
}
}
}
return new int[] {-1,-1};
}
}
这种双层for loop的解法的时间复杂度较高,在笔试和竞赛中并不占优势,不能完全展示我们的算法编程能力。
仔细分析题目,我们其实还有一个条件数组是有序,题目的条件基本上都是有用的,一定要注意该数组是一个有序的数组啊!!! 而有序的数组就特别容易就要想出二分查找,接着思路就比较明晰了。使用二分查找的特性去优化算法的查询的效率。
class Solution {
public int[] twoSum(int[] numbers, int target) {
for (int i = 0; i < numbers.length ; ++i) {
int left = i + 1;
int right = numbers.length - 1;
//一定要注意二分查找算法中 left是可以与right相等的情况
while (left <= right){
int mid = (right - left)/2 + left;
if (numbers[mid] == target - numbers[i]){
return new int[] { i+1, mid +1};
}else if(numbers[mid] < target - numbers[i]){
left = mid + 1;
}else{
right = mid - 1;
}
}
}
return new int[] {-1,-1};
}
}
总结:
思路:
创建两个指针,第一个指针用于进行循环遍历数组将不为0的位置重新赋值,实现了0的删除。第二个指针用于将之前删除的0补齐到数组的后面,可以用不为0的数值的个数来控制要补的0的个数,因为数组的长度是固定的。
public void moveZeroes(int[] nums) {
int index = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0){
nums[index] = nums[i];
index = index + 1;
}
}
for (int i = index; i < nums.length; i++) {
nums[i] = 0;
}
}
题目的意思就是找到数组的中心索引位置,中心索引位置的左边所有的数的和等于中心索引位置右边所有数的和。
思路:
本题进行一个思维的转化,就是将中心索引转化为:
可以求出总的数组的和sum
那么leftsum(左边所有数的和) = sum - arr[i] - leftsum
public int pivotIndex(int[] nums) {
int sum = 0;
int leftsum = 0;
//求出了sum,数组中元素的总和
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
for (int j = 0; j < nums.length ; ++j) {
if (leftsum == sum - nums[j] - leftsum )return j;
leftsum += nums[j];
}
return -1;
}
题目:给定一个
有序的数组
和一个target值,找到数组中存在的目标值,如果没找到,那么就将该数顺序插入到数组中
思路:
给定的是一个有序的数组,那么可以直接就可以想到使用二分查找的方式进行搜索,这样的效率比遍历数组的效率高
public int searchInsert(int[] nums, int target) {
//注意该数组是一个有序的数组
//注意需要特别判断,因为要求插入的位置,为了不让数组越界,我们在外面特判
//思路二分查找
//二分查找的条件:
//1.单调性(在一定的范围内单调)
//2.有上限和下限(查找是在一定的范围内的)
//3.能够通过索引位置进行访问(数组是可以通过元素的下标进行访问的)
int left = 0;
int right = nums.length-1;
int mid = 0;
if (nums.length == 0){
return 0;
}
if (nums[nums.length-1] < target){
return nums.length;
}
while (left < right){
mid = left + (right - left)/2;
if(nums[mid] < target){
left = mid + 1;
}else {
right = mid;
}
}
return right;
}
给定一个已经排好序的数组,删除该数组中重复的元素(要求原地删除),返回值是排序好的新的数组的长度。
思路:
- 同样是排好序的数组,那么就只需要判断连续的两个元素是否相同就可以保证整个数组的没有重复的元素
- 在判断可存在重复元素后,我们需要原地删除数组中的重复项,那么就需要创建两个数组指针用来原地删除,只有两个连续的数组元素不相同时,才进行数组的赋值,否则就跳过,相当于删除了这个重复的元素。
public int removeDuplicates(int[] nums) {
//1.要求原地删除数组中重复的元素
//首先的想法是如何删除数组中重复的元素---暴力法->遍历数组
//原地删除,就是不使用额外的空间
if (nums.length == 0){
return 0;
}
int i = 0;
for (int j = 0; j < nums.length; j++) {
if (nums[i] != nums[j]) {
i++;
nums[i] = nums[j];
}
}
return i + 1;
}
使用双指针:一个指向数组(字符串)的头,一个指向数组(字符串)的尾,每次操作都互换头尾指针的值,操作完之后头指针向后移动,尾指针向前移动,实现数组(字符串)的反转。如果是字符串,那么就先转化为数组操作后,通过String.valueOf(arr)转化为字符串。
public void resverseString(char[] arr){
int i = 0;
int j = arr.length - 1;
while(i < j){
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i++;
j--;
}
}
总结
双指针的使用场景:当你需要从两端向中间遍历数组时
一个指针从头部开始,一个指针从尾部开始。
给定长度为 2n 的数组, 你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从1 到 n 的 min(ai, bi) 总和最大。
特别要注意:拿到题之后先要分析题目,题目的最终要求是分组为n,并且数组的长度是2n,那么就是两个元素分为一组,又题目要求操作
数组之后,得到 1-n的最小的元素和.
注意要操作数组的,而不是直接根据参数中得到的数组直接进行操作
public int arrayPairSum(int[] nums) {
//这道题我的思路是直接求出相邻的两个数的最小值,然后累加出这些最小值的和
//但是程序写出来一直出错
// int sum = 0;
int index = 0;
for (int i = 0; i < nums.length ; i = i+2) {
index = i + 1;
if (nums[i] < nums[index]){
sum += nums[i];
}else {
sum += nums[index];
}
}
return sum ;
//原因是:将题目的要求没有分析准确,题目只是给出了一个数组,要求将数组拆分
//然后得到他的最小值的和,而不是直接在数组的基础上求最小值的和,还要进行操作
Arrays.sort(nums);
int sum = 0;
for (int i = 0; i < nums.length; i += 2) {
sum += nums[i];
}
return sum;
}
给定一个有序的数组和一个目标值,寻找在数组中是否存在两个数的和等于target,如果等于那么就返回一个数组,数组中的两个元素值分别是这两个数的下标
可以采用暴力法:遍历数组中的每一个元素的组合,看其是否和target相等,如果相等直接返回下标的数组。
暴力法遍历数组需要特别熟练的掌握
public int[] twoSum(int [] arr,int target){
for(int i = 0; i < arr.length; i++){
for(int j = i+1; j < arr.length-1; j++){
if(arr[i] == target - arr[j]){
return new int[] {i+1,j+1};
}
}
}
throw new IllegaArgumentException("No two sum solution");
}
题目,给定一个数组,找出该数组中第三大的数,如果第三大的数不存在,那么就输出最大的数
注意相同的数认为是相同大小的,比如[3,2,2,3,1],那么第三大的数是1
思路:排序数组,从后往前遍历,找到第三个不一样的数,输出即可。
public int thirdMax(int [] nums){
Arrays.sort(nums);
//用一个数来记录不同的数(初始化为1是因为我们比较时已经拿出了最大的数了,已经取了一个数)
int index = 1;
for(int i = nums.length - 1;i > 0; --i){
if(nums[i] != nums[i-1]){
index++;
}
if(index == 3){
return nums[i-1];
}
}
//没找到第三大的数就返回最大的数
return nums[nums.length -1];
}
分析:
设计算法:
暴力遍历,按照题目给出的算法的思路直接写出算法即可。
public boolean canPlaceFlowers(int[] flowerbed, int n) {
//用于计数符合种植规则的位置的数
int count = 0;
//我们从左向右遍历(其实感觉也可以从右往左遍历),所以开始的位置是0
int i = 0;
while(i < flowerbed.length){
if(flowerbed[i]== 0 && (i == 0 || flowerbed[i-1] == 0) && (i == flowerbed.length -1 || flowerbed[i+1] == 0)){
flowerbed[i] = 1;
count++;
}
}
//分析题意:这个位置是可以相等的,只要能插入n数量的花就应该是true的
return n>=count;
}
分析:
思路:
三个数的乘积
总结就是:最大的三个数的乘积和一个最大的数和两个最小的数的乘积比较
class Solution {
public int maximumProduct(int[] nums) {
//数组排序,就可以通过索引得到元素的值
Arrays.sort(nums);
return Math.max(nums[0] * nums[1] * nums[len],nums[len] * nums[len - 1] * nums[len - 2]);
}
}
分析:
题目的意思其实就是要求对数组中不同的数进行计数
首先想到的就是使用Map的数据结构,因为我们不仅需要存储元素出现的次数,还需要知道这个出现次数最多的元素是哪个元素。
class Solution {
public int majorityElement(int[] nums) {
Map<Integer,Integer> mymap = new HashMap<Integer,Integer>();
for(int i = 0;i < nums.length; ++i){
map.put(nums[i],map.getOrDefault(nums[i],0)+1);
if(map.get(nums[i]) > nums.length/2){
return nums[i];
}
}
return -1;
}
}
int len = nums.length;
int len2 = nums[0].length;
对于数组int[] [] nums = new int [3] [4];
这是一个三行四列的数组,要遍历整个二维数组
public static void myMethod(int[][] nums){
for(int i = 0; i < 3;++i){
for(int j = 0; j < 4;++j){
//code
}
}
}
思考联系:对于设计二维数组,或者是存在二维的题目,要想到使用二维数组解决,二维数组不仅能够降维,还能通过索引访问到具体的值,比如杨辉三角的问题,就可以化为二维数组的问题,通过位置的特点求值
// Related Topics 数组
// 64 0
public class ImageSmoother{
public static void main(String[] args) {
Solution solution = new ImageSmoother().new Solution();
}
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public int[][] imageSmoother(int[][] M) {
//思路:就是找到二维数组元素的相邻的格子的数的和,它的平均灰度就是再除以该元素周围的元素数
//周围元素的范围是:除了边界以外
//[i-1,j-1] [i-1,j] [i-1,j+1]
//[i,j-1] [i,j] [i,j+1]
//[i+1,j-1] [i+1,j [i+1,j+1]
//既然要得到这些位置的值的和,那么就要遍历整个二维数组
//代表数组的行
int m = M.length;
//代表数组的列
int n = M[0].length;
//创建出结果数组
int [][] ans = new int[m][n];
//开始遍历数组求元素周围值的和
for (int i = 0; i < m ; ++i) {
for (int j = 0; j < n; ++j) {
//整个变量用来计数元素周围元素的个数
int count = 0;
//注意这种特殊的遍历方式,直接使用有规律的范围作为遍历的范围,减少了不必要数的遍历,
//也更加利于计算
for (int mi = i - 1; mi <= i + 1 ; ++mi) {
for (int mj = j - 1; mj <= j + 1 ; ++mj) {
if (0 <= mi && mi < m && 0 <= mj && mj < n){
//这一步是该数组元素周围所以的元素都进行求和的结果
ans[i][j] += M[mi][mj];
count++;
}
}
}
ans[i][j] /= count;
}
}
return ans;
}
}
//leetcode submit region end(Prohibit modification and deletion)
}
分析:
思路:
遍历数组,判断子序列,并获取到子序列的值,要对子序列进行比较,取最长的子序列
class Solution {
public int findLengthOfLCIS(int[] nums) {
//特殊判断
if(nums.length == 0){
return 0;
}
int count = 0;
int maxcount = 0;
//遍历数组并获取最长的子序列
for(int i = 0;i < nums.length - 1; ++i){
if(nums[i] < nums[i+1]){
count++;
maxcount = Math.max(count,maxcount);
}else{
//说明了那个递增序列结束了,让count置0,重新开始计数新的最长子序列
count = 0;
}
}
//注意返回的时候要+1,因为我们记录的是比较的次数,而子序列的长度刚好比比较次数多1。
return maxcount + 1;
}
}
分析:保证每次选择都是在最大的序列和的条件下选择
class Solution {
public int maxSubArray(int[] nums) {
for(int i = 1; i < nums.length; ++i){
nums[i] = Math.max(nums[i], nums[i] + nums[i - 1]);
nums[0] = Math.max(nums[0],nums[i]);
}
return nums[0];
}
}
思路:
先找到数组中0的位置,然后再修改其所在的行和所在的列为0
class Solution {
public void setZeroes(int[][] matrix) {
//思路:
//遍历数组然后记录二维数组中零的位置i ,j 的值
//然后再遍历一遍数组将0赋值
int len = matrix[0].length;
Set<Integer> setH = new HashSet<Integer>();
Set<Integer> setL = new HashSet<Integer>();
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < len; j++) {
if (matrix[i][j] == 0){
setH.add(i);
setL.add(j);
}
}
}
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < len; j++) {
if (setH.contains(i) || setL.contains(j)){
matrix[i][j] = 0;
}
}
}
}
}
这种解法的时间复杂度很高是O(n^2),使用了双层的for loop
ath.max(nums[0],nums[i]);
}
return nums[0];
}
}
# 零矩阵
[外链图片转存中...(img-YItuHRRb-1603848932999)]
思路:
先找到数组中0的位置,然后再修改其所在的行和所在的列为0
```java
class Solution {
public void setZeroes(int[][] matrix) {
//思路:
//遍历数组然后记录二维数组中零的位置i ,j 的值
//然后再遍历一遍数组将0赋值
int len = matrix[0].length;
Set setH = new HashSet();
Set setL = new HashSet();
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < len; j++) {
if (matrix[i][j] == 0){
setH.add(i);
setL.add(j);
}
}
}
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < len; j++) {
if (setH.contains(i) || setL.contains(j)){
matrix[i][j] = 0;
}
}
}
}
}
这种解法的时间复杂度很高是O(n^2),使用了双层的for loop
文章中有什么问题请各位大佬指正!每条评论我都会回复