一、代码随想录刷题——数组

文章目录

  • 1.LeetCode 1:找出数组中重复的数字。
  • 2.LeetCode 9:用两个栈来实现队列
  • 二分查找
    • goodnotes转过来
    • 3.LeetCode 34: 在排序数组中查找元素的第一个和最后一个位置
    • 4.LeetCode 27: 移除元素
  • 5.LeetCode 977. 有序数组的平方
  • 6.LeetCode 209: 长度最小的子数组
  • 7. 59:螺旋矩阵 II(旋转矩阵)
  • 总结


1.LeetCode 1:找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3

class Solution {
    public int findRepeatNumber(int[] nums) {
        Set<Integer> dic = new HashSet<>();
        for(int num : nums) {
            if(dic.contains(num)) return num;
            dic.add(num);
        }
        return -1;
    }
}

这题没有做出来的原因是因为双for循环用时超标了,别再用for了,下一次动动脑子,别暴力了。自己最应该想到的就是set,对set的学习掌握不够,不能只是知道,更要能用这些数据结构,就像英语只会看没有用,还要会写作文,能用上。

set中没有重复元素,有contains方法,可以非常好的查找是否有增元素,一个好的封装方法。
注意set要用包装类,最好再复习一遍看看。

此外,要多用for each方法


2.LeetCode 9:用两个栈来实现队列

主要是实现的具体思路 不能光有个概念 那远远不够
这道题暂且放在这里!

将一个栈当作输入栈,用于压入appendTail 传入的数据;另一个栈当作输出栈,用于deleteHead 操作。

每次 deleteHead 时,若输出栈为空则将输入栈的全部数据依次弹出并压入输出栈

使用java的同学请注意,如果你使用Stack的方式来做这道题,会造成速度较慢; 原因的话是Stack继承了Vector接口,而Vector底层是一个Object[]数组,那么就要考虑空间扩容和移位的问题了。 可以使用LinkedList来做Stack的容器,因为LinkedList实现了Deque接口,

所以Stack能做的事LinkedList都能做,其本身结构是个双向链表,扩容消耗少。

但是我的意思不是像100%代码那样直接使用一个LinkedList当做队列,那确实是快,但是不符题意。 贴上代码,这样的优化之后,效率提高了40%,超过97%。

class CQueue {
    LinkedList<Integer> stack1;
	LinkedList<Integer> stack2;

	public CQueue() {
		stack1 = new LinkedList<>();
		stack2 = new LinkedList<>();
	}

	public void appendTail(int value) {
		stack1.add(value);//就是linkedlist里的addFirst(v)方法
	}

	public int deleteHead() {
		if (stack2.isEmpty()) {
			if (stack1.isEmpty()) return -1;
			while (!stack1.isEmpty()) {
				stack2.add(stack1.pop());
				//linkedlist可以实现pop()方法 pop就是linkedlist里removeFirst方法
			}
			return stack2.pop();
		} else return stack2.pop();
	}
}
其实真的没有那么复杂,不要想复杂了,分情况分清楚就行,就是一个进队和出队。

二分查找

二分查找最重要的就是区间定义不要混,决定是左闭右闭就一直使用这个就可以了!
二分查找的代码应该已经是记得很熟练了,这个是最基础的!

while(left<=right){
mid=left+(right-left)/2;(防止溢出)
if(nums[mid]==target)
return mid;
else if(nums[mid]>target)
right=mid-1;//这里容易混,千万不可以写错,分清楚
else left=mid+1;
} 
return -1;

一、代码随想录刷题——数组_第1张图片

3.LeetCode 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]

这道题也是困扰了我很久,主要是自己一上来就写,然后把自己绕进去了,同时卡哥的寻界没有看懂,后来看忍者算法的代码懂了,最重要的是要分别左右寻界,还是用二分法进行寻界,分别移动right或者left进行逼近。

自己最开始也是这么想的,那就是找到target之后,就看看前面的还是不是目标值,后面的是不是目标值,比较一下不就好了,但是后来绕来绕去的报错又绕昏了,看了忍者算法的代码一下子清晰了!推荐!!!

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int leftBorder = getleftborder(nums, target);
        int rightBorder = getrightborder(nums, target);
        return new int[]{leftBorder,rightBorder};
    }
    private int getleftborder(int[] nums,int target){
 //寻找左边界,先写原始二分法
        int left=0,right=nums.length-1;
        while(left<=right){
            int mid=left+(right-left)/2;
            if(nums[mid]==target){  
//当找到目标值时有两种情况,1,他已经是最左边,那么返回mid,即nums[mid-1]不等于nums[mid]时就是最左边了,找到最左边界
//2.另一个情况就是它在数组最左边 mid=0,那么此时左边没有,mid-1会溢出,所以要单独拿出来判断一下! 
                 if(mid==0||nums[mid-1]!=nums[mid])
                    return mid;
                    else right=mid-1; 
//若不是最左边,那么right=mid-1,mid就会向左边移动,向左寻界!
                    }
            else if(nums[mid]>target)
            right=mid-1;
            else left=mid+1;
         }
         return -1;//一定要写,没有找到的情况
    }

     private int getrightborder(int[] nums,int target){
//一样的,向右寻界
        int left=0,right=nums.length-1;
        while(left<=right){
            int mid=left+(right-left)/2;
            if(nums[mid]==target){  
//如何判断是否在最右边,nums[mid]不等于nums[mid+1],以及mid在数组最右边了,+1就要越界了,单独拿出来讨论。
                 if(mid==nums.length-1||nums[mid]!=nums[mid+1])
                    return mid;
                    else left=mid+1; 
//若不是在最右边,left=mid+1,mid向右移动,向右寻界!
                    }
            else if(nums[mid]>target)
            right=mid-1;
            else left=mid+1;
         }
         return -1;
    }
}

4.LeetCode 27: 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1: 给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 你不需要考虑数组中超出新长度后面的元素。

示例 2: 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

你不需要考虑数组中超出新长度后面的元素。


那么在这里呢就不考虑双for 时间复杂度上不符合要求

数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。

学习了一个新的知识点
快慢指针
双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法

class Solution {
    public int removeElement(int[] nums, int val) {
        // 快慢指针
        int slowIndex=0;
        for (int fastIndex = 0; fastIndex < nums.length; fastIndex++) {
            if (nums[fastIndex] != val) 
          nums[slowIndex++] = nums[fastIndex];
                   }
        return slowIndex;
           
    }
}

5.LeetCode 977. 有序数组的平方

LeetCode 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]

这道题其实没有什么东西,就是对快慢指针的灵活运用,自己在写题时,不要局限对双指针法的扩展。整体来看是对快速排序的变形写法。

class Solution {
    public int[] sortedSquares(int[] nums) {
//双指针法,感觉像是快速排序的变化后结果!
int left=0,right=nums.length-1;
int k=right;
int[] a=new int[nums.length];
while(left<=right){
    if(nums[left]*nums[left]<nums[right]*nums[right]){
        a[k--]=nums[right]*nums[right];
        right--;
    } else { a[k--]=nums[left]*nums[left];
           left++;    
    } 
} return a;
    }
}

6.LeetCode 209: 长度最小的子数组

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

通过这道题学习了移动窗口,感觉还挺奇妙的,就是把双for循环转成了一个for循环,里面放的是终止位置,起始位置通过while来进行判断,降低了时间复杂度。while循环里的东西是这道题的核心

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
int left=0;//起始指针
int  sum=0;//窗口里的和
int  result=Integer.MAX_VALUE;//最后返回的窗口大小
  for (int right = 0; right < nums.length; right++) {
    //终止指针不断地向后移动
     sum+=nums[right];//从0开始加的
     while(sum>=target){
         result=Math.min(result, right - left + 1);
        // 不断更新结果 找最小的 
         sum=sum-nums[left];
         left++;//符合条件 就要把起始指针向后移动,缩小窗口!
     }

} return result == Integer.MAX_VALUE ? 0 : result;
//加一个判断进行返回,如果等于最大值说明没找到 返回0
    }
}

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


7. 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.在写题前,面对这种有点规律的题目,要多多模拟,多走几遍,尽可能找到所有的规律,这道题你画个2、3、4之后就会发现原来中间的是要单独处理的,然后循环是分块的,逆时针一部分一部分的填进去。以及如果你再敏锐一点就会发现,旋转第二圈的时候,填的长度少了一点!
2.一入循环深似海,正如卡哥所说,循环不变量,对于这种有区间的,一定不要变来变去,像我就是一直走左闭右闭来写二分法,所以在这里,我选择左闭右开,即最后一个交给下一个循环去填入!

class Solution {
    public int[][] generateMatrix(int n) {
          int[][] res = new int[n][n];
     int start = 0;  // 每次循环的开始点(start, start)
       int loop = 0;  // 控制循环次数 
       // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
        //这个循环几圈是自己没有想到的 当时没想到这个结束循环条件
        int mid = n / 2; 
        // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2) 
        // 这个也是没有发现的一个规律 就是中间的是要单独处理的,n为奇数时要单独处理
        //所以写代码前还是要好好想想,这个思路究竟是怎么样的!
        int count = 1; // 用来给矩阵中每一个赋值
        int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位 这个很重要 循环的边界是一直在减小的!没有发现这个规律
        int i,j;//循环
        while (loop++ < n / 2) {
            //全部都是左闭右开 
            // 1.从左向右 i不变 j++
            // 最开始写的时候放错误了,脑子里只有循环第一圈的情况,所以就j=0,j
            for(j=start;j<n-offset;j++){
                res[start][j]=count++;
            }
            // 2.从上向下 j不变 i++
             for(i=start;i<n-offset;i++){
                res[i][j]=count++;
            }
            //3.从右向左 i不变 j--
                 for(;j>start;j--){
                res[i][j]=count++;
            }
             // 4.从下到上 j不变 i--
            for (; i > start; i--) {
                res[i][j] = count++;
            }
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
            start++;
 // offset 控制每一圈里每一条边遍历的长度
            offset++;
    }
    // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值 另一个没有发现的点!
        if (n % 2 == 1) {
            res[start][start] = count;
        }

        return res;
}}

总结

在数组这里我们学习了这些方法;
1.二分法
2.二分法的变形,左右寻界
3.快慢指针
4.移动窗口 right是终止位置 不断的移动

最后一题比较考验代码的能力,全部学完之后,二刷在写的时候希望不会卡壳!

你可能感兴趣的:(代码随想录——力扣刷题,java,leetcode,算法)