leetcode-hot100-数组

文章目录

  • [31. 下一个排列 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/next-permutation/)
  • [41. 缺失的第一个正数 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/first-missing-positive/)
  • [48. 旋转图像 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/rotate-image/)
  • [215. 数组中的第K个最大元素 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/kth-largest-element-in-an-array/)
  • [238. 除自身以外数组的乘积 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/product-of-array-except-self/)
  • [239. 滑动窗口最大值 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/sliding-window-maximum/)
  • [56. 合并区间 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/merge-intervals/)
  • [15. 三数之和 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/3sum/)
  • [283. 移动零 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/move-zeroes/)
  • [88. 合并两个有序数组 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/merge-sorted-array/)
  • [54. 螺旋矩阵 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/spiral-matrix/)
  • [912. 排序数组 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/sort-an-array/)
  • [448. 找到所有数组中消失的数字 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array/submissions/)
  • [128. 最长连续序列](https://leetcode-cn.com/problems/longest-consecutive-sequence/)

31. 下一个排列 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:
  1. i定位到n-2位置,只要它合法,并且当前数大于前面一个数,那么就i–,向后移动。
  2. 如果i合法,j定位到n-1位置,j只要大于i并且去找到小于i位置数的数,j–。
  3. i,j位置确定,交换。
  4. 最后翻转(i + 1, n - 1)的数

简化理解:先从n-2的位置向后找,找到一个当前数比前一个数小的位置,否则就后移,只要这个位置是合法的,那么再从n-1的您方找到一个当前数比刚才定位的数大的位置,同样,否则就后移。定位到两个位置之后进行交换,交换完成之后进行一个翻转,翻转位置为(i+1,n-1)。

  • 过程

    // 1 2 5 8 7
    //ans
    // 1 2 7 5 8
    //process
    // 1 2 7 8 5
    // 1 2 7 5 8
    
  • 代码:

    class Solution {
        public void nextPermutation(int[] nums) {
            int n = nums.length;
            int i = n - 2;
            while(i >= 0 && nums[i] >= nums[i + 1]){
                i--;
            }
            if(i >= 0){
                int j = n - 1;
                while(j > i && nums[j] <= nums[i]){
                    j--;
                }
                swap(nums, i, j);
            }
            reverse(nums, i + 1, n - 1);
        }
        public void reverse(int[] nums, int low, int high){
            while(low < high){
                swap(nums, low++, high--);
            }
        }
        public void swap(int[] nums, int low, int high){
            int temp = nums[low];
            nums[low] = nums[high];
            nums[high] = temp;
        }
    }
    

41. 缺失的第一个正数 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:使用打标记的方式进行处理。
  1. 计算数组长度。对数组进行遍历,如果当前数小于等于0,那么给其赋值为n + 1。
  2. 当数组改变完之后,又接着遍历数组,取出数组的数,如果当前数小于等于n,那么就将该下标为num[i]-1的数置为负数。
  3. 最后对数组遍历,如果当前数大于0,那么返回i + 1。
  4. 如果遍历完还没有返回值,那么就返回n + 1。
  • 核心思想,最后返回的因为是正整数,那么这个数一定是[1,n+1]。

  • 执行过程:

    [3,4,-1,1]=>[3,4,5,1]=>[-3,4,-5,-1]
    
  • 代码:

    class Solution {
        public int firstMissingPositive(int[] nums) {
            int n = nums.length;
            for(int i = 0; i < n; i++){
                if(nums[i] <= 0){
                    nums[i] = n + 1;
                }
            }
            for(int i = 0; i < n; i++){
                int num = Math.abs(nums[i]);
                if(num <= n){
                    nums[num - 1] = -Math.abs(nums[num - 1]);
                }
            }
            for(int i = 0; i < n; i++){
                if(nums[i] > 0){
                    return i + 1;
                }
            }
            return n + 1;
        }
    }
    

48. 旋转图像 - 力扣(LeetCode) (leetcode-cn.com)

推荐题解

  • 思路
  1. 因为题目说了使用原地算法,那么就找找规律。
    leetcode-hot100-数组_第1张图片
  2. 根据图发现,就是先以左上到右下角对对角线,进行翻转,在根据中心线进行翻转。
  • 代码
class Solution {
   public void rotate(int[][] matrix) {
       int n = matrix.length;
       //先沿斜对角线翻转
       for(int i = 0;i < n;i ++)
           for(int j = 0;j < i;j ++){
               int temp = matrix[i][j];
               matrix[i][j] = matrix[j][i];
               matrix[j][i] = temp;
      }
      //再沿垂直竖线翻转,要使用另外一个变量从倒数第一个进行翻转
      for(int i = 0;i < n;i ++)
           for(int j = 0, k = n - 1; j < k ; j++, k--){
               int temp = matrix[i][k];
               matrix[i][k] = matrix[i][j];
               matrix[i][j] = temp;
       }
   }
}

215. 数组中的第K个最大元素 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:
  1. 使用快速排序的思想:双指针+分治。
  2. 随机选择一个中间值pivot作为比较的基本base,因为求的是topK,因此,比这个基准大的放在左边,比这个基准小的放在右边。
  3. 把选择的基准放在最左边,交换nums[low]和nums[pivot]。
  4. 慢指针i从low位置开始指向比基准大的数字;快指针j从low+1开始遍历。
  5. j <= high进入循环
    1. 如果nums[j]比基准大,那么就交换nums[i + 1]和nums[j],并且i++,只要找到一个数比基准大,那么i向前移动,就是指代下一个位置要换过去。
    2. 不管是否发生了交换,j都要前移。
  6. 循环结束的时候,当前i的位置就是数组中比base大的最后一个位置,将其和最左边的base交换位置,即:交换nums[low]和nums[i];交换完毕之后,i位置之前的都是比它大的,i位置之后都是比它小的。
  7. 进行分治
    1. 如果当前i就是第k个元素,即:i==k-1,那么找到了topK,返回nums[i]
    2. 如果当前i > k-1,i偏大,在[low, i - 1]继续寻找
    3. 如果当前i < k-1,i偏小,在[i + 1,high]继续寻找。
  • 模拟过程

    假设pivot为3,以下标pivot为基准,比nums[3]=5大的在左边,比其小的在右边。
    把5放在最左边,即:交换low和pivot下标的元素。
    此时慢指针i为low,i指向每次找到的比5大的数
        快指针j为low+1,j去寻找比nums[low]大的数,也就是base。
    进循环:
    	找到num[j]比5大的数,交换nums[i+1]和nums[j],并且i++
    	不管有没有找到比5大的数,j都要++
    此时结果是[5,6,1,3,2,4]
    
    再交换low和i小标的元素,因为当前i所在的位置就是数组中比基准大的最后一个位置;交换完成之后,i位置之前的都是比它大的,i位置之后都是比它小的。
    交换后[6,5,1,3,2,4]
    
    但是并不是每次都这么凑巧,所以要分治
    如果当前i就是第k个元素,即:i == k-1,找到topK,返回nums[i]
    如果当前i > k - 1,记住i下标的元素就是base元素,这个时候表示的就是前面有很多比base元素大的,那么就进行区间治理,在[low,i-1]之间找
    如果当前i < k - 1,这个时候表示后面又很多元素比base小的,同样要在[i+1,high]找。因为i指向的是base
    
  • 代码

    class Solution {
        public int findKthLargest(int[] nums, int k) {
            return findTopKth(0, nums.length - 1, nums, k);
        }
        private int findTopKth(int low, int high, int[] nums, int k){
            int pivot = low + (int)(Math.random() * (high - low + 1));
            swap(nums,low,pivot);
            int base = nums[low];
            int i = low, j = low + 1;
            while(j <= high){
                if(nums[j] > base){
                    swap(nums, i + 1, j);
                    i++;
                }
                j++;
            }
            swap(nums, low, i);
            if(i == k - 1){
                return nums[i];
            }else if(i > k - 1){
                return findTopKth(low, i - 1, nums, k);
            }else {
                return findTopKth(i + 1, high, nums, k);
            }
        }
    
        private void swap(int[] nums, int a, int b){
            int temp = nums[a];
            nums[a] = nums[b];
            nums[b] = temp;
        }
    }
    

238. 除自身以外数组的乘积 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:
  1. 先初始化一个和nums同样大小的数组res。
  2. 初始化k为1
  3. 然后遍历数组,res每个位置的值就是当前的k,k每次更新为nums[i] * k,循环完毕之后,res数组保存的是除去当前元素左边的元素乘积。
  4. 重新初始化k为1。
  5. 遍历res,从尾部开始,res[i]的值就是res[i] * k,同时k每次更新为nums[i] * k
  6. 最后返回res。

简化理解:使用一个变量k(初值为1)和一个结果数组res,首先res中记录k变化值,k进行变化(该过程从前往后)。然后k重新设置1,res保存的就是结果,k也进行变化(该过程是从后往前)。k值的变化过程都是nums[i] * k

  • 执行过程,以[1,2,3,4]为例子

    执行完第一循环之后,res为[1,1,2,6]
    这个就是除去数组当前元素的左边元素乘积
    
    重新初始化k之后
    res[3] = res[3] * k // k就是这个数右边的乘积
    k = k * nums[3]		// 更新k为4,因为k代表该数右边的乘积,那么更新之后就是概述右边的乘积 * 该数左边的乘积
    
  • 代码

    class Solution {
        public int[] productExceptSelf(int[] nums) {
            int[] res = new int[nums.length];
            int k = 1;
            for(int i = 0; i < res.length; i++){
                res[i] = k;
                k = k * nums[i]; // 此时数组存储的是除去当前元素左边的元素乘积
            }
            k = 1;
            for(int i = res.length - 1; i >= 0; i--){
                res[i] *= k; // k为该数右边的乘积。
                k *= nums[i]; // 此时数组等于左边的 * 该数右边的。
            }
            return res;
        }
    }
    

239. 滑动窗口最大值 - 力扣(LeetCode) (leetcode-cn.com)

  • 题解:【动画模拟】一下就能读懂(单调双端队列) - 滑动窗口最大值 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:

  1. 维护一个单调递减的双端队列。
  2. 递归循环指定窗口大小次数,只要当前队列不为空,那么队尾的元素比当前元素小,那么移除队尾的元素,这样,就可以找到第一个元素,将该元素即对头元素,送至结果数组idx=1的位置。
  3. 继续移动窗口,判断当前窗口前的元素是否和对头元素相等,如果相等就出队。这一步就是保证窗口大小,也确保了维护的双端队列大小最多为k。
  4. 继续按照之前的规则进行入队,依旧是维护单调递减的队列。
  5. 每次将对头元素存放在数组里面。
  6. 返回数组。
  • 执行过程,以示例1为例

    nums:[1,3,-1,-3,5,3,6,7], k = 3
    开始创建一个双端队列
    [1]->[3]->[3,-1]
    然后将此时队列的对头元素加入到结果数组中
    然后从nums[k]开始扫描,每次首先要判断当前窗口元素是否和对头元素相等,相等就出队。
    
    进行扫描
    [3,-1]->[3,-1,-3]
    再扫描的时候,发现当前窗口的元素和对头元素相等(3 == 3),移除对头元素
    [3,-1,-3]->[-1,-3]
    继续扫描,此时5都比对中元素大,从队尾移除
    [-1,-3]->[5]
    [5]->[5,3]->[6]->[7]
    
    其实解释保持双端队列的单调递减,以及保证双端队列的大小最大为k,也就是为什么要进行当前窗口元素和对头元素是否相等。此时移除从对头移除。
    当前元素值比队尾元素值小,移除从队尾进行移除
    每次想数组添加元素是添加对头元素。
    
  • 代码

    class Solution {
        public int[] maxSlidingWindow(int[] nums, int k) {
            int len = nums.length;
            if(len == 0){
                return nums;
            }
            int[] res = new int[len - k + 1];
            int idx = 0;
            Deque<Integer> deque = new LinkedList<>();
            for(int i = 0; i < k; i++){
                while(!deque.isEmpty() && deque.peekLast() < nums[i]){
                    deque.removeLast();
                }
                deque.offerLast(nums[i]);
            }
            res[idx++] = deque.peekFirst();
            for(int j = k; j < len; j++){
                //因为是从k开始的,所以在每次判断时候要减去k
                if(nums[j - k] == deque.peekFirst()){
                    deque.removeFirst();
                }
                while(!deque.isEmpty() && deque.peekLast() < nums[j]){
                    deque.removeLast();
                }
                deque.offerLast(nums[j]);
                res[idx++] = deque.peekFirst();
            }
            return res;
        }
    }
    

推荐题解

  1. 遍历数组,将数存放在双向队列中,并用 L,R 来标记窗口的左边界和右边界。队列中保存的是该数值对应的数组下标位置,并且数组中的数要从大到小排序
  2. 如果当前遍历的数比队尾的值大,则需要弹出队尾值,直到队列重新满足从大到小的要求。(保证一定从大到小,后面的数字一定要比前面的大)
  3. 刚开始遍历时,L 和 R 都为 0,有一个形成窗口的过程,此过程没有最大值,L 不动,R 向右移。
  4. 当窗口大小形成时,L 和 R 一起向右移,每次移动时,判断队首的值的数组下标是否在 [L,R] 中,如果不在则需要弹出队首的值,当前窗口的最大值即为队首的数。
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums == null || nums.length < 2) return nums;
        // 双向队列 保存当前窗口最大值的数组位置 保证队列中数组位置的数值按从大到小排序
        LinkedList<Integer> queue = new LinkedList();
        // 结果数组
        int[] result = new int[nums.length-k+1];
        // 遍历nums数组
        for(int i = 0;i < nums.length;i++){
            // 保证从大到小 如果前面数小则需要依次弹出,直至满足要求
            while(!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]){
                queue.pollLast();
            }
            // 添加当前值对应的数组下标
            queue.addLast(i);
            // 判断存储的下标是否在队列中
            if(queue.peek() <= i-k){
                queue.poll();   
            } 
            // 当窗口长度为k时 保存当前窗口中最大值
            //从现在开始往数组里添加答案了,因为 i 至少已经走了一个窗口的大小
            if(i+1 >= k){
                result[i+1-k] = nums[queue.peek()];
            }
        }
        return result;
    }
}

56. 合并区间 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:
  1. 先对每个区间的左端点进行一个升序排序。
  2. 然后依次获取每个区间的左右端点,并使用一个列表进行一个存储。
  3. 当首次添加,即:列表为空,或者前一个右端点比当前的左端点的值小,那么添加当前的区间。
  4. 否则,即:前一个的右端点比当前的左端点大
  • 代码:

    class Solution {
        public int[][] merge(int[][] intervals) {
            if(intervals.length == 0){
                return new int[0][2];
            }
            //按每个区间的左端点升序排列。
            Arrays.sort(intervals, new Comparator<int[]>() {
                public int compare(int[] v1, int[] v2){
                    return v1[0] - v2[0];
                }
            });
            List<int[]> res = new ArrayList<>();
            for(int i = 0; i < intervals.length; i++){
                int L = intervals[i][0], R = intervals[i][1];
                //结果列表为空,就添加第一个,或者,前一个数组的右侧小于现在的左侧
                if(res.size() == 0 || res.get(res.size() - 1)[1] < L){
                    res.add(new int[]{L, R});
                }else {
                    //否则说明列表不为空,并且前一个数组的右侧大于或者等于现在的左侧,
                    //那么进行更新,原则是将前一个数组的右侧更新为现在右侧和前一个数组右侧的最大值
                    res.get(res.size() - 1)[1] = Math.max(res.get(res.size() - 1)[1], R);
                } 
            }
            return res.toArray(new int[res.size()][]);
    
        }
    }
    

15. 三数之和 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:

    1. 首先对数组进行排序,排序后固定一个数nums[i],再使用左右指针指向nums[i]后面的两端,数字分别为nums[L]和nums[R],计算三个数的和sum是否满足为0,满足就添加进结果集。
    2. 如果nums[i]>0,那么三数之和必然不等为0,结束循环。(因为已经拍好序了,当前大于0,后面的也必然大于0)。
    3. 如果nums[i] == nums[i - 1],那么该数字重复,跳过。
    4. 当sum==0的时候,nums[L] == nums[L+1],就会导致结果重复,跳过,即:L++
    5. 当sum==0的时候,nums[R] == nums[R+1],就会导致结果重复,跳过,即:R–
  • 代码:

    class Solution {
        public static List<List<Integer>> threeSum(int[] nums) {
            List<List<Integer>> ans = new ArrayList();
            int len = nums.length;
            if(nums == null || len < 3) return ans;
            Arrays.sort(nums); // 排序
            for (int i = 0; i < len ; i++) {
               	//先固定住一个数,然后再进行其他两个数的寻找
                if(nums[i] > 0) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
                if(i > 0 && nums[i] == nums[i-1]) continue; // 去重
                //其他的两个数分别在固定住数的左右找
                int L = i+1;
                int R = len-1;
                while(L < R){
                    int sum = nums[i] + nums[L] + nums[R];
                    if(sum == 0){
                        //添加之后不管去不去重,双指针都要移动
                        ans.add(Arrays.asList(nums[i],nums[L],nums[R]));
                        while (L<R && nums[L] == nums[L+1]) L++; // 去重
                        while (L<R && nums[R] == nums[R-1]) R--; // 去重
                        L++;
                        R--;
                    }
                    else if (sum < 0) L++;
                    else if (sum > 0) R--;
                }
            }        
            return ans;
        }
    }
    

283. 移动零 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:
  1. 使用双指针的方法,right为工作指针,left指向0的数字。
  2. 当right指向的数字非0的时候,那么就与left进行交换,交换完成之后,left就需要前移。
  3. 不管是否发生交换,工作指针一定会向下移动的。
  • 代码:

    class Solution {
        public void moveZeroes(int[] nums) {
            int n = nums.length, left = 0, right = 0;
            while(right < n){
                if(nums[right] != 0){
                    swap(nums, left, right);
                    left++;
                }
                right++;
            }
        }
        public void swap(int[] nums, int left, int right){
            int temp = nums[left];
            nums[left] = nums[right];
            nums[right] = temp;
        }
    }
    

88. 合并两个有序数组 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:

    1. 使用双指针的思想。因为没有返回值,所以最后的返回值要设置到nums1(题目要求)。
    2. 初始化一个大小为m+n的数组,用来记录合并后的结果。
    3. 初始化一个临时变量用来记录值。
    4. 设置i,j分别指向两个数组,只要二者小于给定的m或者n就进入循环。
      1. 如果i值已经达到了给定的m,那么临时变量就是nums2[j++],因为此时nums1已经达到给定的最末端了。
      2. 如果j值已经达到了给定的n,那么临时变量就是nums1[i++],因为此时nums2已经达到给定的最末端了。
      3. 如果nums1[i] <= nums2[j],那么临时变量值就是为nums1[i++],否则就是nums2[j++]。
      4. 记住一点,每次在值的比较或者末端比较的时候,两个指针某一个都要进行相应移动。
      5. 每次循环的时候,将temp放入到res合并数组中。
    5. 最后将res数组的值赋值给nums1。
  • 代码:

    class Solution {
        public void merge(int[] nums1, int m, int[] nums2, int n) {
            int[] res = new int[m + n];
            int i = 0, j = 0;
            int temp = 0;
            while(i < m || j < n){
                if(i == m){
                    temp = nums2[j++];
                }else if(j == n){
                    temp = nums1[i++];
                }else if(nums1[i] <= nums2[j]){
                    temp = nums1[i++];
                }else if(nums1[i] > nums2[j]){
                    temp = nums2[j++];
                }
                res[i + j - 1] = temp;
            }
            int count = 0;
            while(count != m + n){
                nums1[count] = res[count];
                count++;
            }
        }
    }
    

54. 螺旋矩阵 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:

    1. 题目的意思就是按照顺时针螺旋的方式添加进结果集。
    2. 得到row、col。
    3. 设置四个方向int[][] dirs = new int[][]{{0,1},{1,0},{0,-1},{-1,0}};分别表示向右,向下,向左,向上。这个顺序也正好符合了顺时针螺旋式的添加。
    4. 设置四个初始值x(当前行)、y(当前列)、d(当前方向)、i(控制循环次数),循环次数控制在row * col次。
    5. 每次就添加当前x、y所指向的元素,并将原数组中该值设置为Integer.MAX_VALUE;
    6. 计算下一次移动的方向int nx = x + dirs[d][0] ,ny = y + dirs[d][1];因为第一次是向右的,且d开始为0,那么最开始的时候就是向右走。
    7. 但是要判断此时的方向,如果有一个方向不符合要求,或者值为Integer.MAX_VALUE,那么说明此时要改变方向了。
      1. 改变方向:d = (d + 1) % 4,因为存在四个方向。
      2. 然后再次计算nx、ny。
      3. 不管方向是否满足要求,每次进行了更新就要将nx赋值给x,ny赋值给y。
    8. 返回res;
  • 代码:

    class Solution {
        public List<Integer> spiralOrder(int[][] matrix) {
            List<Integer> res = new ArrayList<>();
            int row = matrix.length, col = matrix[0].length;
            int[][] dirs = new int[][]{{0,1},{1,0},{0,-1},{-1,0}};
            for(int x = 0, y = 0, d = 0, i = 0; i< row * col; i++){
                res.add(matrix[x][y]);
                matrix[x][y] = Integer.MAX_VALUE;
    
                int nx = x + dirs[d][0] ,ny = y + dirs[d][1];
                if(nx < 0 || nx >= row || ny < 0 || ny >= col || matrix[nx][ny] == Integer.MAX_VALUE){
                    d = (d + 1) % 4;
                    nx = x + dirs[d][0];
                    ny = y + dirs[d][1];
                }
                x = nx;
                y = ny;
            }
            return res;
        }
    }
    
  • 重点是记住上下左右四个方向怎么描述。

912. 排序数组 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:

    1. 使用快速排序的思想,整体和215一致。
    2. 在快速排序内部,每次找到中点,然后不断的缩减问题的规模即可。
    3. 其次在找的时候,本题升序的结果。
  • 代码

    • 版本1:原生

    • 原生的思想:

      使用两个指针low,high,在大前提low小于high的情况下,进行交换,pivot的值选择为nums[low],在循环的时候high去找比pivot小的值,没找到就--,找到就停,然后交换low和high的值,low去找比pivot大的值,找到就停,没找到就++。然后交换low和high的值,最后low停的位置pivot值,最后再递归即可。在(low,mid - 1)和(mid + 1, high)中进行。
      
      class Solution {
          public int[] sortArray(int[] nums){
              QuickSort(nums,0,nums.length - 1);
              return nums;
          }
          public void quickSort(int[] nums, int low, int high){
              if(low < high){
                  int mid= partition(nums, low, high);
                  quickSort(nums, low, mid - 1);
                  quickSort(nums, mid + 1, high);
              }
          }
          public int partition(int[] nums, int low, int high){
              int pivot = nums[low];
              while(low < high){
                  while(low < high && nums[high] >= pivot){
                      high--;
                  }
                  if(low < high){
                      nums[low] = nums[high];
                  }
                  while(low < high && nums[low] <= pivot){
                      low++;
                  }
                  if(low < high){
                      nums[high] = nums[low];
                  }
              }
              nums[low] = pivot;
              return low;
          }
          public void swap(int[] nums, int i, int j){
              int temp = nums[i];
              nums[i] = nums[j];
              nums[j] = temp;
          }
      }
      
    • 版本2:改进

    class Solution {
        public int[] sortArray(int[] nums) {
            quickSort(nums, 0, nums.length - 1);
            return nums;
        }
        public void quickSort(int[] nums, int low, int high){
            if(low < high){
                int mid = partition(nums, low, high);
                quickSort(nums, low, mid - 1);
                quickSort(nums, mid + 1, high);
            }
        }
        
        private int partition(int[] nums, int low, int high){
            int pivot = low + (int)(Math.random() * (high - low + 1));
            swap(nums, low, pivot);
            int i = low, j = low + 1;
            int base = nums[low];
            while(j <= high){
                if(nums[j] < base){
                    i++;
                    swap(nums, i, j);
                }
                j++;
            }
            swap(nums, i, low);
            return i;
        }
    
        private void swap(int[] nums, int a, int b){
            int temp = nums[a];
            nums[a] = nums[b];
            nums[b] = temp;
        }
    }
    
  • 模拟过程同215一致,只是每次是先交换的小的,而215每次交换的是大的。

  • 思路二:

    思考:为什么在215的时候,交换是(i + 1, j),但是本题却是(++i, j),有何不同?

    1. 以[3,2,1,5,6,4]说明,215和本题呢,交换之后是[5,2,1,3,6,4],i = low, j = low + 1,但是问题却是在后面swap(low, i)的时候,使用i + 1,没有改变i的值,只是在每次交换的时候,让i的下一个位置指向要交换的位置,虽然每次交换完成之后,两题的i都会进行++,移动交换完成之后都会让i移动到下一个待交换的位置。
    2. 真正意义上就是升序的时候在交换使用++i,根本的改变了i的值,不仅指向了下一个位置要改变的数,也为最后进行swap(low, i)决定了i的位置。
    3. 215题使用i+1,那么i指向的是最后一次比pivot大的数的位置。那么在第一次的时候i就指向4,值为2。
    4. 本题使用++i,那么i指向6,值为4,在最后进行交换的时候就发生了根本性的变换。本题的这样设计将第一轮最大的数设置到最后一位。
  • 归并排序:

    • 归并排序的思想就是分治,将一个大问题拆分为若干个小的子问题解决。
  • 归并过程:

leetcode-hot100-数组_第2张图片

  • 重点在mergeTwo代码,好好背背。

    1. 首先传入的参数,数组,low,mid,high。
    2. 设计一个临时的数组。大小为high - low + 1。
    3. i设置为low,j设置为mid+1,k设置为0(索引)。
    4. 在循环里面比较值。
    5. 最后放值给nums数组。
  • 归并代码:

    class Solution {
        public int[] sortArray(int[] nums) {
            mergeSort(nums, 0, nums.length - 1);    
            return nums;
        }
    
        private void mergeSort(int[] nums, int low, int high){
            if(low < high){
                int mid = low + (high - low) / 2;
                mergeSort(nums, low, mid);
                mergeSort(nums, mid + 1, high);
                mergeTwoArrays(nums, low, mid, high);
            }
        }
    
        private void mergeTwoArrays(int[] nums, int low, int mid, int high){
            int[] tmp = new int[high - low + 1];
            int i = low, j = mid + 1, k = 0;
            while(i <= mid && j <= high){
                if(nums[i] < nums[j]){
                    tmp[k++] = nums[i++];
                }else{
                    tmp[k++] = nums[j++];
                }
            }
            while(i <= mid) tmp[k++] = nums[i++];
            while(j <= high) tmp[k++] = nums[j++];
            for(int index = 0; index < k; index++){
                nums[low + index] = tmp[index];
            }
        }
    }
    
  • 堆排序(大顶堆):建议嗯背

  • 堆排序代码:

    public class Sort{
        public int[] HeapSort(int[] nums){
            //构建大根堆
            buildMaxHeap(nums);
            for(int i = nums.length - 1; i > 0; i--){
                //将大根堆的堆顶和最后一个叶子结点交换,最大值在尾部。
                swap(nums, 0, i);
                //对前i个元素构建新的大顶堆
                heapify(nums, 0, i);
            }
        }
        //构建大顶堆(从第一个非叶子结点从右到左,从下到上)
        public void buildMaxheap(int[] nums){
            for(int i = nums.length / 2 - 1; i >= 0; i--){
                heapify(nums, i, nums.length);
            }
        }
        public void heapify(int[] nums, int i, int length){
            int left = 2 * i + 1;
            int right = 2 * i + 2;
            //默认最大值是当前i根节点
            int largest = i;
            //左子结点存在并且大于根节点
            if(left < length && nums[left] > nums[largest]){
                largest = left;
            }
            //右子结点存在并且小于根节点
            if(right < length && nums[right] < nums[largest]){
                largest = right;
            }
            //最大值是子结点
            if(largest != i){
                swap(nums, largest, i);
                //交换完毕之后再调整其子结点为根的堆
                heapify(nums, largest, length);
            }
        }
        public void swap(int[] nums, int i, int j){
            int temp = nums[i];
            nums[i] = nums[j];
            nums[j] = temp;
        }
    }
    

448. 找到所有数组中消失的数字 - 力扣(LeetCode) (leetcode-cn.com)

  • 注:本题与41是没有区别的,用41的做法一样可以做
  • 代码:
class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        List<Integer> res = new LinkedList<>();
        int n = nums.length;
        for(int i = 0; i < n; i++){
            if(nums[i] <= 0){
                nums[i] = n + 1;
            }
        }
        for(int i = 0; i < n; i++){
            int num = Math.abs(nums[i]);
            if(num <= n){
                nums[num - 1] = -Math.abs(nums[num - 1]);
            }
        }
        for(int i = 0; i < n; i++){
            if(nums[i] > 0){
                res.add(i + 1);
            }
        }
        return res;
    }
}
  • 因为要求在不使用额外空间且时间复杂度为O(n)情况下去解决,那么就不能使用暴力解法。那么就要考虑下标与数字的关系。(标记法)

    • 例如:

      index 0 1 2 3 4 5 6 7
      nums 4 3 2 7 8 2 3 1
    • 可以看出缺少[5,6]

    • 如果是正常排列的话,那么就是

      index 0 1 2 3 4 5 6 7
      nums 1 2 3 4 5 6 7 8
      • 即:下标和数组的关系是:index = num - 1
    • 所以:首先对数组进行扫描,先得到每次数字对应的下标,即:

      • index 3 2 1 6 7 1 2 0
        num 4 3 2 7 8 2 3 1
    • 然后再将对应位置的数变为其对应的负数

      • index 0 1 2 3 4 5 6 7
        nums -4 -3 -2 -7 8 2 -3 -1
      • 开始的时候[4....8]将数组对应的元素修改成了负数,之后[2,3]数字先是得到下标,然后再获取到数组中的数,对其进行abs,再在前面加一个符号,这样就能保证出现的数永远都是负的。

  • 代码

    class Solution {
        public List<Integer> findDisappearedNumbers(int[] nums) {
            List<Integer> res = new ArrayList<>();
            for(int i = 0; i < nums.length; i++){
                //获取每个数字应该放入的下标
                int idx = Math.abs(nums[i]) - 1;
                //将对应位置的数字变为负数
                nums[idx] = -Math.abs(nums[idx]);
            }
            for(int i = 0; i < nums.length; i++){
                if(nums[i] > 0){
                    res.add(i + 1);
                }
            }
            return res;
        }
    }
    

128. 最长连续序列

  • 思路:
  1. 对数组进行一个排序。
  2. 设置两个变量,一个最大长度值,一个长度值(设置这个最大的原因是可能这个长度一直在涨)。最后返回的最大长度值是最大值和长度值的最大的一个。
  3. 每次进行一个比较,相等就跳过,如果正好比前一个数大1,那么当前长度值就+1。否则,出现了断层,这个时候算最大长度值,并且置当前长度值为1。
  • 代码:
public int longestConsecutive(int[] nums) {
        int n = nums.length;
        if(n == 0){
            return 0;
        }
        Arrays.sort(nums);
        int maxLen = 1;
        int len = 1;
        for(int i = 1; i < n; i++){
            if(nums[i] == nums[i - 1]){
                //相等的
                continue;
            }else if(nums[i] == nums[i - 1] + 1){
                //正好大1
                len++;
            }else {
                maxLen = Math.max(maxLen, len);
                len = 1;
            }
        }
        return Math.max(maxLen, len);
    }

你可能感兴趣的:(leetcode,leetcode,算法,数据结构)