数据结构——leetcode(数组与矩阵)【java】
leetcode-283-移动零
力扣-283
题目:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。
思路:用index记录,当出现0的时候,index++(从0开始)的位置将后面的不为零的元素一次放到前面,然后当index<nums.length,后面的元素全部为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];
}
}
while(index<nums.length){
nums[index++]=0;
}
}
时间复杂度: O(n)
空间复杂度: O(1)
leetcode-566-重塑矩阵
力扣-566
题目:
给出一个由二维数组表示的矩阵,以及两个正整数r和c,分别表示想要的重构的矩阵的行数和列数。
重构后的矩阵需要将原始矩阵的所有元素以相同的行遍历顺序填充。
如果具有给定参数的reshape操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。
示例:
输入:
nums = [[1,2],[3,4]]
r = 1, c = 4
输出:
[[1,2,3,4]]
解释:
行遍历nums的结果是 [1,2,3,4]。新的矩阵是 1 * 4 矩阵, 用之前的元素值一行一行填充新矩阵。
输入:
nums =
[[1,2],
[3,4]]
r = 2, c = 4
输出:
[[1,2],
[3,4]]
解释:
没有办法将 2 * 2 矩阵转化为 2 * 4 矩阵。 所以输出原矩阵。
思路:
1.判断是否可以重塑 mn=rs
2.重塑矩阵的各个元素 reshapenums[i][j] = nums[index/n][index%n];
public int[][] matrixReshape(int[][] nums, int r, int c) {
int m = nums.length;
int n = nums[0].length;
if(m*n!=r*c){
return nums;
}
int index = 0;
int [][] reshapenums = new int [r][c];
for(int i = 0;i<r;i++){
for(int j = 0;j<c;j++){
reshapenums[i][j] = nums[index/n][index%n];
index++;
}
}
return reshapenums;
}
时间复杂度: O(mn)。我们只遍历整个矩阵 mn。这里,m和 n 指的是给定矩阵中的行数和列数。
空间复杂度: O(m*n)。使用大小为 m∗n 的结果矩阵。
leetcode-485-最大连续1的个数
力扣-485
题目:给定一个二进制数组, 计算其中最大连续1的个数。
示例:
输入: [1,1,0,1,1,1]
输出: 3
解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3.
public int findMaxConsecutiveOnes(int[] nums) {
int max = 0;
int count = 0;
for(int i=0;i<nums.length;i++){
if(nums[i]==1){
count++;
}
else{
max = Math.max(max,count);
count = 0;
}
}
return Math.max(max,count);
}
时间复杂度: O(N) N 值得是数组的长度。
空间复杂度: O(1),仅仅使用了 count 和 maxCount
leetcode-240-搜索二维矩阵Ⅱ
力扣-240
题目:编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
示例:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
思路:找到矩阵的row col ,如果当前指向的值大于目标值,则可以 “向上” 移动一行。 否则,如果当前指向的值小于目标值,则可以移动一列。因为行是从左到右排序的,所以我们知道当前值右侧的每个值都较大。 因此,如果当前值已经大于目标值,我们知道它右边的每个值会比较大。也可以对列进行非常类似的论证,因此这种搜索方式将始终在矩阵中找到目标。
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix==null||matrix.length==0||matrix[0].length==0){
return false;
}
int m = matrix.length;
int n = matrix[0].length;
int row = 0;
int col =n-1;
while(row<m&&col>=0){
if(target==matrix[row][col]) return true;
else if(target<matrix[row][col]) col--;
else row++;
}
return false;
}
时间复杂度: O(m+n)
空间复杂度: O(1)
leetcode-378-有序矩阵中第k小的元素
力扣-378
题目:
给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是排序后的第 k 小元素,而不是第 k 个不同的元素
示例:
matrix = [
[ 1, 5, 9],
[10, 11, 13],
[12, 13, 15]
],
k = 8,
返回13
思路:
1.暴力解法
2.二分法查找
3.小根堆,优先级队列数据结构根本是二叉树
//直接排序法
public int kthSmallest(int[][] matrix, int k) {
int m = matrix.length;
int n = matrix[0].length;
int [] sorted = new int [m*n];
int index = 0;
for(int []row:matrix){
for(int num: row){
sorted[index++] = num;
}
}
Arrays.sort(sorted);
return sorted[k-1];
}
//时间复杂度: O (n^2\log{n}) 对 n^2个数排序。
//空间复杂度: O(n^2)
//二分查找
public int kthSmallest(int[][] matrix, int k) {
int n = matrix.length-1;
int left = matrix[0][0];
int right = matrix[n][n];
while(left<right){
int mid = left+(right-left)/2;
int count = FindCountMid(matrix,mid,n);
if(count<k) left = mid+1;
else right = mid;
}
return right;
}
public int FindCountMid(int[][] matrix,int mid,int n){
int x = n;
int y = 0;
int count = 0;
while(x>=0&&y<=n){
if(matrix[x][y]<=mid){
count+=x+1;
++y;
}else{
--x;
}
}
return count;
}
//时间复杂度:O(n\log(r-l),二分查找进行次数为 O(n\log(r-l)),每次操作时间复杂度为 O(n)。
//空间复杂度:O(1)
//小根堆
public int kthSmallest(int[][] matrix, int k) {
int m = matrix.length;
int n = matrix[0].length;
PriorityQueue<Tuple> pq = new PriorityQueue<Tuple>();
for(int j = 0;j<n;j++){
pq.offer(new Tuple(0,j,matrix[0][j]));
}
for(int i=0;i<k-1;i++){
//小根堆,去掉 k - 1 个堆顶元素,此时堆顶元素就是第 k 的数
Tuple t = pq.poll();
if(t.x == m-1) continue;
pq.offer(new Tuple(t.x+1,t.y,matrix[t.x+1][t.y]));
}
return pq.poll().val;
}
class Tuple implements Comparable<Tuple> {
int x, y, val;
public Tuple(int x, int y, int val) {
this.x = x; this.y = y; this.val = val;
}
@Override
public int compareTo(Tuple that) {
return this.val - that.val;
}
}
//时间复杂度:O(k\log{n}),归并 k、k 次,每次堆中插入和弹出的操作时间复杂度均为logn。
//空间复杂度:O(n),堆的大小始终为 n
leetcode-645-错误的集合
力扣-485
题目:一个数组元素在 [1, n] 之间,其中一个数被替换为另一个数,找出重复的数和丢失的数
示例:
输入: nums = [1,2,2,4]
输出: [2,3]
思路:1.数组
2.hashmap [num,count],如果count=2,则对应num为重复值
3.位运算
public int[] findErrorNums(int[] nums) {
Arrays.sort(nums);
int dup = -1;
int missing = 1;
for(int i=0;i<nums.length-1;i++){
if(nums[i+1]==nums[i]){
dup=nums[i+1];
}
else if(nums[i+1]>nums[i]+1){
missing = nums[i]+1;
}
}
return new int []{dup,nums[nums.length-1]!=nums.length?nums.length:missing};
}
//时间复杂度:O(nlogn),排序需要O(nlogn) 的时间。
//空间复杂度:O(logn),排序需要O(logn) 的空间。
//hashmap
public int[] findErrorNums(int[] nums) {
Map<Integer,Integer> map=new HashMap();
int dup=-1;
int missing=1;
for(int n:nums){
map.put(n,map.getOrDefault(n,0)+1);
}
for(int i=1;i<=nums.length;i++){
if(map.containsKey(i)){
if(map.get(i)==2)
dup = i;
}else missing =i;
}
return new int[]{dup,missing};
}
//时间复杂度:O(n)。遍历 nums需要时间O(n),在 map中检查每个数字需要时间O(n)。
//空间复杂度:O(n),map最多需要存储1到n共n个数字。
时间复杂度: O(N) N 值得是数组的长度。
空间复杂度: O(1),仅仅使用了 count 和 maxCount
leetcode-287-寻找重复数
力扣-287
题目:给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数
示例:
输入: [1,3,4,2,2]
输出: 2
输入: [3,1,3,4,2]
输出: 3
说明:
1.不能更改原数组(假设数组是只读的)。
2.只能使用额外的 O(1) 的空间。
3.时间复杂度小于 O(n2) 。
4.数组中只有一个重复的数字,但它可能不止重复出现一次。
思路:1.二分法,找到mid,看一半数组中大于或小于mid 的数量是否大于mid,则可以判断重复数据在哪一侧数组中了
2.双指针解法(不是常规思想)类似于有环链表中找出环的入口:fast走2步,slow走一步,当fast ==slow,重新取或者fast指针指向链表/数组头,走一步遍历和slow速度相同一同继续行走,直到新fast指针与slow指针重合为环入口。
//二分法
public int findDuplicate(int[] nums) {
int left = 1;
int h = nums.length-1;
while(left<=h){
int mid = left+(h-left)/2;
int count = 0;
for(int i=0;i<nums.length;i++){
if(nums[i]<=mid) count++;
}
if(count>mid) h=mid-1;
else left=mid+1;
}
return left;
}
//快慢指针
public int findDuplicate(int[] nums) {
int fast = 0;
int slow = 0;
while(true){
//快指针走两步,慢指针走一步
fast = nums[nums[fast]];
slow = nums[slow];
if(slow==fast){
fast = 0;
while(nums[slow]!=nums[fast]){
fast = nums[fast];
slow = nums[slow];
}
return nums[slow];
}
}
}
leetcode-667-数组相邻差值的个数
力扣-667
题目:
给定两个整数 n 和 k,你需要实现一个数组,这个数组包含从 1 到 n 的 n 个不同整数,同时满足以下条件:
① 如果这个数组是 [a1, a2, a3, … , an] ,那么数组 [|a1 - a2|, |a2 - a3|, |a3 - a4|, … , |an-1 - an|] 中应该有且仅有 k 个不同整数;.
② 如果存在多种答案,你只需实现并返回其中任意一种.
示例:
输入: n = 3, k = 1
输出: [1, 2, 3]
解释: [1, 2, 3] 包含 3 个范围在 1-3 的不同整数, 并且 [1, 1] 中有且仅有 1 个不同整数 : 1
输入: n = 3, k = 2
输出: [1, 3, 2]
解释: [1, 3, 2] 包含 3 个范围在 1-3 的不同整数, 并且 [2, 1] 中有且仅有 2 个不同整数: 1 和 2
思路:数组元素为 1~n 的整数,要求构建数组,使得相邻元素的差值不相同的个数为 k。
让前 k+1 个元素构建出 k 个不相同的差值,序列为:1 k+1 2 k 3 k-1 … k/2 k/2+1.
public int[] constructArray(int n, int k) {
int[] ret = new int[n];
ret[0]=1;
for(int i=1,j=k;i<=k;i++,j--){
ret[i]=i%2==1?ret[i-1]+j:ret[i-1]-j;
}
for(int i=k+1;i<n;i++){
ret[i]=i+1;
}
return ret;
}
leetcode-697-数组的度
力扣-697
题目:
给定一个非空且只包含非负数的整数数组 nums, 数组的度的定义是指数组里任一元素出现频数的最大值。
你的任务是找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。
示例:
输入: [1, 2, 2, 3, 1]
输出: 2
解释:
输入数组的度是2,因为元素1和2的出现频数最大,均为2.
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组[2, 2]的长度为2,所以返回2.
输入: [1,2,2,3,1,4,2]
输出: 6
public int findShortestSubArray(int[] nums) {
if(nums==null||nums.length==0) return 0;
if(nums.length==1) return 1;
//map1 记录每个元素出现次数
Map<Integer,Integer> map1 = new HashMap<>();
//map2 记录数组元素第一次出现时的位置
Map<Integer,Integer> map2 = new HashMap<>();
//map3 记录数组相同元素第一次与最后一次出现的长度
Map<Integer,Integer> map3 = new HashMap<>();
for(int i=0;i<nums.length;i++){
map1.put(nums[i],map1.getOrDefault(nums[i],0)+1);
if(!map2.containsKey(nums[i])){
map2.put(nums[i],i);
}
map3.put(nums[i],i-map2.get(nums[i])+1);
}
int maxCount=0;//记录数组的度
//取出map1中最大值
for(int num:map1.values()){
maxCount = Math.max(maxCount,num);
}
//minLength用来记录最终的答案,保存最短连续子数组的长度,初始化为最大值
int minLength = Integer.MAX_VALUE;
for(int key:map1.keySet()){
if(maxCount==map1.get(key)){
minLength = Math.min(minLength,map3.get(key));
}
}
return minLength;
}
leetcode-766-对角元素相等的矩阵
力扣-766
题目:如果矩阵上每一条由左上到右下的对角线上的元素都相同,那么这个矩阵是 托普利茨矩阵 。
给定一个 M x N 的矩阵,当且仅当它是托普利茨矩阵时返回 True
示例:
matrix = [
[1,2,3,4],
[5,1,2,3],
[9,5,1,2]
]
输出: True
解释:
在上述矩阵中, 其对角线为:
"[9]", "[5, 5]", "[1, 1, 1]", "[2, 2, 2]", "[3, 3]", "[4]"。
各条对角线上的所有元素均相同, 因此答案是True。
思路:检查右下元素是否相同
public boolean isToeplitzMatrix(int[][] matrix) {
for(int i=0;i<matrix.length-1;i++){
for(int j=0;j<matrix[0].length-1;j++){
if(matrix[i][j]!=matrix[i+1][j+1]){
return false;
}
}
}
return true;
}
时间复杂度: O(i*j)
空间复杂度: O(1)
leetcode-565-数组嵌套
力扣-565
题目:索引从0开始长度为N的数组A,包含0到N - 1的所有整数。找到最大的集合S并返回其大小,其中 S[i] = {A[i], A[A[i]], A[A[A[i]]], … }且遵守以下的规则。
假设选择索引为i的元素A[i]为S的第一个元素,S的下一个元素应该是A[A[i]],之后是A[A[A[i]]]… 以此类推,不断添加直到S出现重复的元素。
示例:
输入: A = [5,4,0,3,1,6,2]
输出: 4
解释:
A[0] = 5, A[1] = 4, A[2] = 0, A[3] = 3, A[4] = 1, A[5] = 6, A[6] = 2.
其中一种最长的 S[K]:
S[0] = {A[0], A[5], A[6], A[2]} = {5, 6, 2, 0}
思路:会成环,防止访问多个重复元素,让访问后的元素变为-1.
public int arrayNesting(int[] nums) {
int max=0;
for(int i=0;i<nums.length;i++){
int count =0;
if(nums[i]!=-1){
int j=i;
while(nums[j]!=-1){
int temp=j;
j=nums[j];
nums[temp]=-1;
count++;
}
max=Math.max(count,max);
}
}
return max;
}
leetcode-769-分割数组
力扣-769
题目:
数组arr是[0, 1, …, arr.length - 1]的一种排列,我们将这个数组分割成几个“块”,并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。
我们最多能将数组分成多少块?
示例:
输入: arr = [4,3,2,1,0]
输出: 1
解释:
将数组分成2块或者更多块,都无法得到所需的结果。
例如,分成 [4, 3], [2, 1, 0] 的结果是 [3, 4, 0, 1, 2],这不是有序的数组。
输入: arr = [1,0,2,3,4]
输出: 4
解释:
我们可以把它分成两块,例如 [1, 0], [2, 3, 4]。
然而,分成 [1, 0], [2], [3], [4] 可以得到最多的块数。
思路:
public int maxChunksToSorted(int[] arr) {
if(arr==null) return 0;
int ret = 0;
int right = arr[0];
for(int i=0;i<arr.length;i++){
right = Math.max(arr[i],right);
if(right==i){
ret++;
}
}
return ret;
}
时间复杂度: O(N)
空间复杂度: O(1)