Leetcode-数据结构-数组与矩阵

本文详细解析了LeetCode中涉及数组与矩阵的若干问题,包括将0移到数组末尾、改变矩阵维度、寻找最长连续1、有序矩阵查找、找到有序矩阵的Kth元素、查找数组中的重复和丢失数、找重复数的二分法、数组相邻差值、数组的度、对角元素相等的矩阵、只出现一次的数、嵌套数组、分隔数组和有重复出现元素的问题。文章介绍了多种算法思路,如二分查找、快慢指针、位操作和连通集等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

283 把数组中的 0 移到末尾

思路将非零元素进行左移,剩余的位置用零进行补齐。

public void moveZeroes(int[] nums) {
    int idx = 0;
    for (int num : nums) {
        if (num != 0) {
            nums[idx++] = num;
        }
    }
    while (idx < nums.length) {
        nums[idx++] = 0;
    }
}

566 改变矩阵维度

采用统一计数来得到正确的二维数组位置。

public int[][] matrixReshape(int[][] nums, int r, int c) {
    int m = nums.length, n = nums[0].length;
    if (m * n != r * c) {
        return nums;
    }
    int[][] reshapedNums = new int[r][c];
    int index = 0;
    for (int i = 0; i < r; i++) {
        for (int j = 0; j < c; j++) {
            reshapedNums[i][j] = nums[index / n][index % n];
            index++;
        }
    }
    return reshapedNums;
}

从读入数组入手

    public int[][] matrixReshape(int[][] nums, int r, int c) {        int m=nums.length;        int n=nums[0].length;         if(r*c != m*n){             return nums;         }         int[][] newarray = new int[r][c];         int index=0;         for(int i=0;i<m;i++){             for(int j=0;j<n;j++){                 newarray[index /c][index % c] = nums[i][j];                 index++;             }         }         return newarray;    }

下面的方法要比上面的方法快,从复杂度分析是一样的啊。

485 找出数组中最长的连续 1

  public int findMaxConsecutiveOnes(int[] nums) {
    int max = 0, cur = 0;
    for (int x : nums) {
        cur = x == 0 ? 0 : cur + 1;
        max = Math.max(max, cur);
    }
    return max;
}

240 有序矩阵查找

注意数组本身的特点

public boolean searchMatrix(int[][] matrix, int target) {
    if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return false;
    int m = matrix.length, n = matrix[0].length;
    int row = 0, 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;
}

378 有序矩阵的 Kth Element

二分查找的思想,数组的第一个元素和最后一个元素分别为最大和最小的元素,我们在数组中寻找mid值是第几个元素并和k值进行比较,指代left=right,找到第k个元素。

public int kthSmallest(int[][] matrix, int k) {
    int m = matrix.length, n = matrix[0].length;
    int lo = matrix[0][0], hi = matrix[m - 1][n - 1];
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        int cnt = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n && matrix[i][j] <= mid; j++) {
                cnt++;
            }
        }
        if (cnt < k) lo = mid + 1;
        else hi = mid - 1;
    }
    return lo;
}

每次都循环遍历一次mid值感觉十分浪费空间。
改进减少查找的行数

    public int kthSmallest(int[][] matrix, int k) {    int m = matrix.length, n = matrix[0].length;    int lo = matrix[0][0], hi = matrix[m - 1][n - 1];    while (lo <= hi) {        int mid = lo + (hi - lo) / 2;        int cnt = 0;        for (int i = 0; i < m; i++) {            if(matrix[i][n-1]<mid) {               cnt+=n;               continue;            }            for (int j = 0; j < n && matrix[i][j] <= mid; j++) {                cnt++;            }        }        if (cnt < k) lo = mid + 1;        else hi = mid - 1;    }    return lo;}

使用堆排序计数来进行
下一章进行堆排序的练习

public int kthSmallest(int[][] matrix, int k) {
    int m = matrix.length, 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;
    }
}

645 一个数组元素在 [1, n] 之间,其中一个数被替换为另一个数,找出重复的数和丢失的数

最直接的方法是先对数组进行排序,这种方法时间复杂度为 O(NlogN)。本题可以以 O(N) 的时间复杂度、O(1) 空间复杂度来求解。主要思想是通过交换数组元素,使得数组上的元素在正确的位置上。

public int[] findErrorNums(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        while (nums[i] != i + 1 && nums[nums[i] - 1] != nums[i]) {
            swap(nums, i, nums[i] - 1);
            //把当前位置不符的数据交换到它应当在的位置,不需要借助普通的排序,直接进行交换即可。
        }
    }
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] != i + 1) {
            return new int[]{nums[i], i + 1};
        }
    }
    return null;
}

private void swap(int[] nums, int i, int j) {
    int tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
}

287 找出数组中重复的数,数组值在 [1, n] 之间

常规思路都会被限制,在二分查找过程中,我们知道取中位数,如果小于等于该值的个数是大于中位数值的那么该区间的数是有重复的。

是从数值的角度进行二分,不是从空间划分的角度进行二分。

public int findDuplicate(int[] nums) {
     int l = 1, h = nums.length - 1;
     while (l <= h) {
         int mid = l + (h - l) / 2;
         int cnt = 0;
         for (int i = 0; i < nums.length; i++) {
             if (nums[i] <= mid) cnt++;
         }
         if (cnt > mid) h = mid - 1;
         else l = mid + 1;
     }
     return l;
}

快慢指针具有一定的技巧性,类似有环表中找到环的入口。
快慢指针方法中,可以将数组想象为一根绳子,重复的值会自动链接到前面形成环。
快慢指针数学原理。
思想这是从什么角度进行的呢?

public int findDuplicate(int[] nums) {
    int slow = nums[0], fast = nums[nums[0]];
    while (slow != fast) {
        slow = nums[slow];
        fast = nums[nums[fast]];
    }
    fast = 0;
    while (slow != fast) {
        slow = nums[slow];
        fast = nums[fast];
    }
    return slow;
}

667 数组相邻差值的个数

使用构造法在该问题中k=n-1;我们可以使用1和n进行构造,之后在构造n-2,依次类推,之后剩余的元素就是k+1,之后的数据依次进行填充即可。

public int[] constructArray(int n, int k) {
    int[] ret = new int[n];
    ret[0] = 1;
    for (int i = 1, interval = k; i <= k; i++, interval--) {
        ret[i] = i % 2 == 1 ? ret[i - 1] + interval : ret[i - 1] - interval;
    }
    for (int i = k + 1; i < n; i++) {
        ret[i] = i + 1;
    }
    return ret;
}

697 数组的度

我们只需要记录数据第一次出现以及最后一次出现的位置,记录出现的次数,找到最大的出现次数并返回该次数下的下标的差值。此题注意选择合适的数据结构。

     public int findShortestSubArray(int[] nums) {        Map<Integer, Integer> left = new HashMap(),            right = new HashMap(), count = new HashMap();
        for (int i = 0; i < nums.length; i++) {            int x = nums[i];            if (left.get(x) == null) left.put(x, i);            right.put(x, i);            count.put(x, count.getOrDefault(x, 0) + 1);        }
        int ans = nums.length;        int degree = Collections.max(count.values());        for (int x: count.keySet()) {            if (count.get(x) == degree) {                ans = Math.min(ans, right.get(x) - left.get(x) + 1);            }        }        return ans;    }
public int findShortestSubArray(int[] nums) {
    Map<Integer, Integer> numsCnt = new HashMap<>();
    Map<Integer, Integer> numsLastIndex = new HashMap<>();
    Map<Integer, Integer> numsFirstIndex = new HashMap<>();
    for (int i = 0; i < nums.length; i++) {
        int num = nums[i];
        numsCnt.put(num, numsCnt.getOrDefault(num, 0) + 1);
        numsLastIndex.put(num, i);
        if (!numsFirstIndex.containsKey(num)) {
            numsFirstIndex.put(num, i);
        }
    }
    int maxCnt = 0;
    for (int num : nums) {
        maxCnt = Math.max(maxCnt, numsCnt.get(num));
    }
    int ret = nums.length;
    for (int i = 0; i < nums.length; i++) {
        int num = nums[i];
        int cnt = numsCnt.get(num);
        if (cnt != maxCnt) continue;
        ret = Math.min(ret, numsLastIndex.get(num) - numsFirstIndex.get(num) + 1);
    }
    return ret;
}

766 对角元素相等的矩阵

数学,相邻的对角线上的元素具备什么特点,及横纵坐标的差值是相等的。
相同对角线上的元素进行记录,遇到不等的值就直接返回。选用恰当的数据结构。

   public boolean isToeplitzMatrix(int[][] matrix) {        Map<Integer, Integer> groups = new HashMap();        for (int r = 0; r < matrix.length; ++r) {            for (int c = 0; c < matrix[0].length; ++c) {                if (!groups.containsKey(r-c))                    groups.put(r-c, matrix[r][c]);                else if (groups.get(r-c) != matrix[r][c])                    return false;            }        }        return true;    }

反向思维左上邻居法,对于每一个出现的元素,判断他的左上邻居和当前值是否相等即可。

     public boolean isToeplitzMatrix(int[][] matrix) {        for (int r = 0; r < matrix.length; ++r)            for (int c = 0; c < matrix[0].length; ++c)                if (r > 0 && c > 0 && matrix[r-1][c-1] != matrix[r][c])                    return false;        return true;    }

137 只出现一次的数

可以使用Hashset与Hashmap的线性复杂度以及额外空间来进行。
可以
使用位操作,在常数时间内进行操作。异或操作可以i用了来检测出现奇数次的数据的次数。
因为是要区分一次和三次,所以我们需要两个位掩码。
按位操作总结

0 ^ x = x,
x ^ x = 0;
x & ~x = 0,
x & ~0 =x;

  public int singleNumber(int[] nums) {    int seenOnce = 0, seenTwice = 0;
    for (int num : nums) {      seenOnce = ~seenTwice & (seenOnce ^ num);      seenTwice = ~seenOnce & (seenTwice ^ num);    }
    return seenOnce;  }

565 嵌套数组

该问题就是连通集问题,最大连通集以及有多少连通集,我们可以在在原数组进行访问记录以减少空间开销。

public int arrayNesting(int[] nums) {
    int max = 0;
    for (int i = 0; i < nums.length; i++) {
        int cnt = 0;
        for (int j = i; nums[j] != -1; ) {
            cnt++;
            int t = nums[j];
            nums[j] = -1; // 标记该位置已经被访问
            j = t;

        }
        max = Math.max(max, cnt);
    }
    return max;
}

769 分隔数组

已访问元素位的最大值位当前索引位置,则划分该区间。

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(right, arr[i]);
        if (right == i) ret++;
    }
    return ret;
}

768 有重复出现元素问题

     public int maxChunksToSorted(int[] arr) {        
     LinkedList<Integer> stack = new LinkedList<Integer>();        
     for(int num : arr) {           
      if(!stack.isEmpty() && num < stack.getLast()) {                
      int head = stack.removeLast();               
       while(!stack.isEmpty() && num < stack.getLast()) 
       stack.removeLast();                
       stack.addLast(head);            }            
       else stack.addLast(num);        }       
        return stack.size();    }

部分解答思路参考:https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E7%9B%AE%E5%BD%95.md

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值