刷题(一)--数组

二维数组中的查找

题目描述:在一个n*m的二维数组中,每一行都按照从左到右递增的顺序排序,每列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和整数,判断数组中是否含有该整数。

示例:
现有矩阵matrix如下:

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

给定target=5,返回true
给定target=20,返回false

方法一:暴力破解法
思路如下:依次遍历数组的 每一行和列,找到返回true,未找到则返回false。

代码:

class Solution{
public  boolean findNumberIn2DArray(int[][] matrix,int target){
        if(matrix == null || matrix.length==0||matrix[0].length==0){
            return false;
        }

        int rows = matrix.length,colums = matrix[0].length;
        for (int i = 0;i<rows;i++){
            for (int j = 0;j<colums;j++){
                if(matrix[i][j] == target){
                    return true;
                }
            }
        }
        return false;
    }
}

时间复杂度:O(nm)
控件复杂度:O(1)
方法二:线性查找
思路如下:由于给定的二维数组具备每行从左到右递增,每列从上到下递增,每当访问到一个元素时,可以排除数组中的部分元素,从左上角开始查找,如果相等则返回true,如果当前元素大于target则移到左边一列,如果当前元素小于目标值则移到下边一行。

代码:

class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if(matrix == null||matrix.length == 0||matrix[0].length==0){
            return false;
        }

        int rows = matrix.length,colunms = matrix[0].length;
        int row = 0;
        int colunm = colunms-1;
        while(row < rows && colunm >= 0){
            int num = matrix[row][colunm];
            if(num == target){
                return true;
            }else if(num>target){
                colunm--;
            }else{
                row++;
           }
        }
        return false;
    }
}

时间复杂度:O(n+m)
空间复杂度:O(1)
方法三:二分法
思路:因为所给数组皆有序,我们可以对每一行进行二分查找。随后挪至下一行,直至结束。
代码:

class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
 if(matrix == null || matrix.length == 0 || matrix[0].length == 0) {
         return false;
     }

     for(int i=0;i<matrix.length;i++){
         int left = 0,right = matrix[0].length-1;
         while(left<=right){
           int mid = left+(right-left)/2;
           if(matrix[i][mid]==target){
               return true;
           }else if(matrix[i][mid]>target){
               right = mid-1;
           }else{
               left = mid+1;
           }
         }
     }
     return false;
    }
}

时间复杂度:因为二分复杂度为O(log2n),加上for循环,应为O(nlog2n)。
空间复杂度:O(1)

旋转数组的最小值

题目描述:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

示例1:

 输入:[3,4,5,1,2]
 输出:1

示例2:

输入:[2,2,2,0,1]
输出:0

思路描述:包含重复元素的升序数组旋转后如下
在这里插入图片描述
左边界为low,右边界为high,中点为pivot
将中轴元素与右边界比较,有如下三种情况。
①:在这里插入图片描述
②:在这里插入图片描述
③:在这里插入图片描述
代码:

class Solution {
    public int minArray(int[] numbers) {
     int left = 0,right = numbers.length-1;
     while(left<right){
         int mid = left+(right-left)/2;
         if(numbers[mid]>numbers[right]){
             left = mid+1;
         }else if(numbers[mid]<numbers[left]){
             right = mid;
         }else{
             right--;
         }
     }
     return numbers[left];
    }
}

最坏情况时间复杂度(元素全相同):O(n)
平均时间复杂度:O(logn)
空间复杂度:O(1)

调整数组使得奇数在前偶数在后

题目描述:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

示例:

输入:nums = [1,2,3,4]
输出:[1,3,2,4] 
注:[3,1,2,4] 也是正确的答案之一。

方法一:辅助数组
思路如下::创建为原数组大小的新数组,遍历两遍数组,第一遍遍历奇数,放入新数组,第二遍遍历偶数,放入新数组。

代码:

class Soultion{
 public  int[] exchange(int[] nums){
        int n = nums.length;
        int[] res = new int[n];
        int index = 0;
        for (int num:nums){
            if(num%2==1){
                res[index++] = num;
            }
        }
        for (int num:nums){
            if (num%2==0){
                res[index++] = num;
            }
        }
        return res;
    }
 }

时间复杂度:O(n)
空间复杂度:O(n)

方法二:头尾双指针
思路:left指向头,right指向尾。left右移直到指向偶数,right左移直到指向奇数。交换left和right的数字,直到left>right。若left>right,则break结束。

代码:

class Solution {
    public int[] exchange(int[] nums) {
     int pre = 0,post = nums.length-1;
     while(pre<=post){
         while(pre<=post && nums[pre] % 2 == 1){
             pre++;
         }
         while(pre<=post && nums[post] % 2 == 0){
             post--;
         }
         if(pre>post){
             break;
         }

         int cur = nums[post];
         nums[post] = nums[pre];
         nums[pre] = cur;
     }
     return nums;
    }
}

时间复杂度
方法三:快慢指针
思路:slow和fast都从头开始遍历,fast右移,如若遇到奇数,则和slow位置数字交换,slow++,fast++。没有遇到奇数,fast++,直至末尾结束。
代码:

class Solution {
    public int[] exchange(int[] nums) {
     int slow = 0,fast = 0;
     while(fast<nums.length){
         if(nums[fast]%2 == 1){
             int tmp = nums[slow];
             nums[slow] = nums[fast];
             nums[fast] = tmp;
             slow++;
         }
         fast++;
     }
     return nums;
    }
}

顺时针打印矩阵

题目描述:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

示例1:

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

示例2:

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]

方法一:模拟矩阵打印路径
思路如下:
按照打印矩阵的路径,初始位置为左上角,初始方向向右,当超出路径或者进入到之前已经访问过的位置,就顺时针旋转进入下一个方向。
我们使用一个辅助矩阵来标记是否访问过这个位置。至于旋转的时机,我们使用一个二维数组,实现四个方向的改变,每次更新下一行和下一列的下标,具体实现请看代码。

代码:

class Solution {
    public int[] spiralOrder(int[][] matrix) {
     if( matrix == null || matrix.length == 0 || matrix[0].length == 0){
         return new int[0];
     }
     int rows = matrix.length,colums = matrix[0].length;
     boolean[][] vis = new boolean[rows][colums];
     int total = rows*colums;
     int[] n = new int[total];
     int row =0,colum = 0;
     int[][] directions = {{0,1},{1,0},{0,-1},{-1,0}};
     int directionIndex = 0;
     for(int i=0;i<total;i++){
         n[i] = matrix[row][colum];
         vis[row][colum] = true;
         int nextRow = row+directions[directionIndex][0],nextColum = colum+directions[directionIndex][1];  //在遍历行时,nextRow的值不变,nextColum的值加1,直到边界,反之亦然。
         if(nextRow<0||nextRow>=rows||nextColum<0||nextColum>=colums||vis[nextRow][nextColum]){
             directionIndex = (directionIndex+1)%4;  //在超过边界或是到访问过的位置,更新dir的值
         }
         row+=directions[directionIndex][0];
         colum+=directions[directionIndex][1];
     } 
     return n;
    }  
}

时间复杂度:O(mn),m,n分别为行列数。
空间复杂度:O(mn)。需要辅助矩阵。

方法二:按层模拟
思路如下:我们可以把矩阵看成很多层,首先打印最外层元素,其次打印内层元素,直到结束。
如下所示,每一层都按同样的顺序打印,就像洋葱一样一层一层剥开。

[[1, 1, 1, 1, 1, 1, 1],
 [1, 2, 2, 2, 2, 2, 1],
 [1, 2, 3, 3, 3, 2, 1],
 [1, 2, 2, 2, 2, 2, 1],
 [1, 1, 1, 1, 1, 1, 1]]

左上角位于 (\textit{top}, \textit{left})(top,left),右下角位于 (\textit{bottom}, \textit{right})(bottom,right),按照如下顺序遍历当前层的元素。

  • 从左到右遍历上侧元素,依次为 (\textit{top}, \textit{left})(top,left) 到 (\textit{top}, \textit{right})(top,right)。
  • 从上到下遍历右侧元素,依次为 (\textit{top} + 1, \textit{right})(top+1,right) 到 (\textit{bottom}, \textit{right})(bottom,right)。
  • 如果 \textit{left} < \textit{right}left<right 且 \textit{top} < \textit{bottom}top<bottom,则从右到左遍历下侧元素,依次为 (\textit{bottom}, \textit{right} - 1)(bottom,right−1) 到 (\textit{bottom}, \textit{left} + 1)(bottom,left+1),以及从下到上遍历左侧元素,依次为 (\textit{bottom}, \textit{left})(bottom,left) 到 (\textit{top} + 1, \textit{left})(top+1,left)

遍历完当前层的元素之后,将 \textit{left}left 和 \textit{top}top 分别增加 11,将 \textit{right}right 和 \textit{bottom}bottom 分别减少 11,进入下一层继续遍历,直到遍历完所有元素为止。
代码:

class Solution {
    public int[] spiralOrder(int[][] matrix) {
    if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
        return new int[0];
    }
    int rows = matrix.length,colums = matrix[0].length;
    int[] n = new int[rows*colums];
    int index = 0;
    int left = 0,right = colums-1,top = 0,bottom = rows-1;
    while(left<=right && top<=bottom){
        for(int colum = left;colum<=right;colum++){
            n[index++]  = matrix[top][colum];
        }
        for(int row = top+1;row<=bottom;row++){
            n[index++] = matrix[row][right];
        }
        if(left<right&&top<bottom){
            for(int colum = right-1;colum>left;colum--){
                n[index++] = matrix[bottom][colum];
            }
            for(int row= bottom;row>top;row--){
                n[index++] = matrix[row][left];
            }
        }
        left++;
        right--;
        top++;
        bottom--;
    } 
    return n;
    }  
}

时间复杂度:O(nm),n,m为行列数,每个元素都需要访问。
空间复杂度:O(1)

数组中出现次数超过一半的数字

题目描述:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例:

输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2

方法一:哈希表
思路如下:我们可以使用哈希映射来保存每个元素和它出现的次数,键位元素,值为该元素出现的次数。
循环遍历数组将元素存入哈希表。之后遍历哈希表返回值最大的键。
代码:

class Solution {
    public int majorityElement(int[] nums) {
    Map<Integer,Integer> counts = countNums(nums);

    Map.Entry<Integer,Integer> majorityElement = null;
    for(Map.Entry<Integer,Integer> entry:counts.entrySet()){
        if(majorityElement == null||entry.getValue()>majorityElement.getValue()){
            majorityElement = entry;
        }
    }
    return majorityElement.getKey();
    }
    private Map<Integer,Integer> countNums(int[] nums){
        Map<Integer,Integer> counts = new HashMap<Integer,Integer>();
        for(int num:nums){
            if(!counts.containsKey(num)){
                counts.put(num,1);
            }else{
                counts.put(num,counts.get(num)+1);
            }
        }
        return counts;
    }
}

时间复杂度:O(n)
空间复杂度:O(n)
方法二:排序
思路如下:如果将数组中的元素排序,无论递增或是递减,那么下标为n/2的元素(下标从0起)一定是众数。
无论数组为偶数还是奇数,因为目标元素出现的次数大于一半,所以n/2的下标位置必定为目标元素。
代码:

class Solution {
    public int majorityElement(int[] nums) {
        Arrays.sort(nums);
        return nums[nums.length / 2];
    }
}

时间复杂度:O(nlog n)
空间复杂度:O(logn)。如果使用语言自带的排序算法,需要使用 O(\log n)O(logn) 的栈空间。如果自己编写堆排序,则只需要使用 O(1)O(1) 的额外空间
方法三:分治策略
思路如下:如果数 a 是数组 nums 的众数,如果我们将 nums 分成两部分,那么 a 必定是至少一部分的众数。这样以来,我们就可以使用分治法解决这个问题:将数组分成左右两部分,分别求出左半部分的众数 a1 以及右半部分的众数 a2,随后在 a1 和 a2 中选出正确的众数。
代码:

class Solution {
   
    public int countInRange(int[] nums,int num,int lo,int hi){
        int count = 0;
        for(int i=lo;i<=hi;i++){
            if(nums[i]==num){
                count++;
            }
        }
        return count;
    }
    private int majorityElementRec(int[] nums,int lo,int hi){
        if(lo == hi) {
            return nums[lo];
        }

        int mid = (hi-lo)/2+lo;
        int left = majorityElementRec(nums,lo,mid);
        int right = majorityElementRec(nums,mid+1,hi);

        if(left == right){
            return left;
        }

        int leftCount = countInRange(nums,left,lo,hi);
        int rightCount = countInRange(nums,right,lo,hi);

        return leftCount>rightCount?left:right;
    }
     public int majorityElement(int[] nums) {
      return majorityElementRec(nums,0,nums.length-1);
    }
}

时间复杂度:O(nlog n)
空间复杂度:O(log n),虽然没有直接借助辅助空间,但是在递归过程中使用了额外的栈空间(栈帧)。

统计一个数在排序数组出现的次数

题目描述:统计一个数字在排序数组中出现的次数。

示例1:

输入: nums = [5,7,7,8,8,10], target = 8
输出: 2

示例2:

输入: nums = [5,7,7,8,8,10], target = 6
输出: 0

思路如下:首先,最简单的方法当然是遍历一遍数组,用变量记录目标元素出现的次数,但是这个方法的时间复杂度为O(n),并且没有用到数组升序的条件。
我们可以考虑使用二分法解题,要记录target出现的次数,那么就要找到target第一次出现的位置,和target最后一次出现的后一个位置。结果就是rightIndex-leftIndex+1。
代码:

class Solution {
    public int search(int[] nums, int target) {
    int leftIndex = binarySearch(nums,target,true);
    int rightIndex = binarySearch(nums,target,false)-1;
    if(leftIndex<=rightIndex&&rightIndex<nums.length&&nums[leftIndex]==target&&nums[rightIndex]==target){
        return rightIndex-leftIndex+1;
    }
    return 0;
    }
    public int binarySearch(int[] nums,int target,boolean lower){
        int left = 0,right = nums.length-1,ans = nums.length;
        while(left<=right){
            int mid = (left+right)/2;
            if(nums[mid]>target||(lower&&nums[mid]>=target)){
                right = mid-1;
                ans = mid;
            }else{
                left =mid+1;
            }
        }
        return ans;
    }

}

求数组中最小的k个数

题目描述:输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

示例1:

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

示例2:

输入:arr = [0,1,2,1], k = 1
输出:[0]

方法一:排序
对数组进行排序然后取出前k个数
代码:

class Solution {
   public int[] getLeastNumbers(int[] arr, int k) {
   int[] v = new int[k];
   Arrays.sort(arr);
   for(int i=0;i<k;i++){
       v[i] = arr[i];
   }
   return v;
   }
}

时间复杂度:O(nlog n)
空间复杂度:O(log n)
方法二:自建堆
我们使用一个大根堆实时维护数组的前k个值。先把k个数存在大根堆,然后从k+1个数遍历,如果比堆顶的数小,就让堆顶的数弹出,插入当前遍历到的数。最后把大根堆里的数放入数组返回。
代码:

class Solution {
   public int[] getLeastNumbers(int[] arr, int k) {
   int[] v = new int[k];
   if(k == 0){
       return v;
   }
   PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>(){
       public int compare(Integer num1,Integer num2){
           return num2-num1;
       }
   });
   for(int i =0 ;i<k;++i){
       queue.offer(arr[i]);
   }
   for(int i = k;i<arr.length;++i){
       if(queue.peek()>arr[i]){
           queue.poll();
           queue.offer(arr[i]);
       }
   }
   for(int i =0;i<k;++i){
       v[i] = queue.poll();
   }
   return v;
   }
}

时间复杂度:O(nlog k)
空间复杂度:O(k)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

降温vae+

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值