zoukankan      html  css  js  c++  java
  • 数组经典题目

    循环不变式:如果某命题初始为真,且每次更改后仍然保持该命题为真,则若干次更改后该命题仍然为真。 


    1. 求局部最大值

    问题描述:给定一个无重复元素的数组A[0...N-1],求找到一个该数组的局部最大值。

    分析思路:

    • 如果遍历数组一遍,用一个变量记录数组中的最大值,这样得到的最大值一定也是局部最大值。这种方案的时间复杂度为O(N),空间复杂度为O(1);
    •  考虑另一种方案。用空间换时间。用二分查找的思想,使得时间复杂度为O(logN).
      • 定义一个辅助数组“高原数组”:Array[from...to]满足
        • Array[from] > Array[from-1]
        • Array[to] > Array[to+1]
        • 当该数组的长度为1时,该数组的值就是局部最大值。
        • 为保持一致性,我们定义数组外边界的值为无穷小。即A[0]>A[-1], A[N]>A[N+1].
      • 用两个指针left,right分别指向数组的首和尾,求中点mid=(left+right)/2:
        • A[mid]>A[mid+1],则子数组A[left...mid]为高原数组——right=mid;
        • A[mid]<A[mid+1],则子数组A[mid+1...right]为高原数组——left=mid+1;
        • 递归直至left=right
      • /**
             * Get the local maximum from an array
             * @param nums
             * @return
             */
            public int getLocalMax(int[] nums) {
                int left=0; 
                int right = nums.length-1;
                int mid = 0;
                while(left < right) {
                    mid = (left + right) / 2;
                    if(nums[mid] > nums[mid+1])
                        right = mid;
                    else if(nums[mid] < nums[mid+1])
                        left = mid+1;
                }
                return nums[left];
            }

     2. 第一个缺失的整数

    问题描述:给定一个数组A[0...N-1],找到从1开始,第一个不在数组中的正整数。

    如:3,5,1,2,-3,7,14,8输出4.

    分析思路:

    • 思路一:从1开始遍历,看每个数字是否在数组中出现。这样时间复杂度为O(N2);
    • 思路二:申请一个额外的数组,从1开始,将数组遍历一遍,如果该数字在数组中出现则标为true。时间复杂的为O(N),空间复杂度为O(1);
    • 思路三:将一个整数直接放到他应该放的位置。如果是负数或者大于了数组的范围则直接丢弃:时间复杂度为O(N),空间复杂度为O(1).
      • 假定前i-1个数已经找到,并且依次存放在A[1,2,...i-1]中,继续考察A[i]:
        • 若A[i] <= 0 或A[i] >N,则可以直接丢弃;
        • 若A[i] < i且A[i] >= 1,则说明A[i]在前面i-1个位置中已经出现过了,可以直接丢弃;
        • 若A[i] > i且A[i] <= N,则说明应该将该元素放到后面的某个位置上:
          • A[i] 与 A[A[i]]的元素直接交换,i不变;
        • 若A[i] = i,则说明A[i]处于正确的位置上,i++即可,循环不变式扩大。
      • 如果丢弃A[i]呢?
        • 将A[N]赋值给A[i],然后N--。
    •     /**
           * Find the fist positive integer not presented in the array count from 1.
           * @param nums
           * @return
           */
          public int getMinPosNotExist(int[] nums) {
              int i = 0;
              int len = nums.length;
              while(i < len) {
                  
                  if(nums[i] == i+1)
                      i++;
                  else if(nums[i] <= 0 || nums[i] >= len || nums[i] < i-1 ) {
                      nums[i] = nums[len-1];
                      len--;
                  }
                  else if(nums[i] > i+1 ) {
                      int temp = nums[i];
                      nums[i] = nums[nums[i]-1];
                      nums[temp-1] = temp;
                  }
              } 
              return i+1;
              
          }

     3. 查找旋转数组的最小值

    问题描述:假定一个排序数组一某个未知元素为支点做了旋转,如原数组0,1,2,3,4,5,6,7旋转后得到了4,5,6,7,0,1,2。找出旋转后的数组的最小值。假定数组中没有重复数字。

    分析思路:

    • 仍然按照循环不变式+二分查找的思路。
      • 数组由普通升序数组和旋转数组构成;
      • 用left,right分别指向首尾元素,元素不重复。
        • 若该数组是普通的升序数组,则a[left] < a[right];
        • 若该数组是旋转数组,则肯定是数组的前半段的所有元素大于后半段的所有元素,则有a[left]>a[right];
      • 令mid = (left + right) / 2.
        • 若旋转数组在左边,则a[mid] < a[right],令left = mid+1;
        • 若旋转数组在右边,则a[mid] > a[right],令right = mid;
      • /**
             * Get the minimum element of a rotate array.
             * @param nums
             * @return
             */
            public int getMiniOfRotateArray(int[] nums) {
                int left = 0;
                int right = nums.length-1;
                int mid;
                while(left < right) {
                    mid = (left + right) / 2;
                    if(nums[mid] > nums[right])
                        left = mid+1;
                    else if(nums[mid] < nums[right])
                        right = mid;
                }
                return nums[left];
            }

     4. 求一个子数组的值最接近0的问题。

    问题描述:求对于长度为N的数组A,求连续子数组的和最接近0的值。如数组A:1,-2,3,10,-4,7,2,-5.它的所有子数组中,和最接近0的是[-4,7,2,-5],和为0.

    分析思路:这种求子数组和的问题,一般需要有一个辅助数组,存储中间计算的过程。

    用一个sum数组,第i个元素表示:该数组中从第1个元素到第i-1个元素的和,则从i到j这一段子数组的和为:sum[j]-sum[i-1]。

    因为我们的数组中保存了每段子数组的和,则在所有子数组的和中差别最少的那部分就是要求的子数组的和。

    也就是我们可以将所求的sun数组进行排序,然后返回相邻元素的差的绝对值最小的那个。

    • /**
           * Get the minimum sum of continues subArray which is closest to 0.
           * @param nums
           * @return
           */
          public int getClosestToZero(int[] nums) {
              int[] sum = new int[nums.length];
              sum[0] = nums[0];
              for(int i=1; i<nums.length; i++) {
                  sum[i] = sum[i-1] + nums[i];
              }
              Arrays.sort(sum);
              int res = Integer.MAX_VALUE;
              int temp = 0;
              for(int i=1; i<sum.length; i++) {
                  temp = Math.abs(sum[i] - sum[i-1]);
                  res = Math.min(temp, res);
              }
              return res;
          }

     5. 最大子数组的和。

    问题描述:给定一个数组A[0,...,n-1],求A的连续子数组,是的该子数组的和最大。

    例如:数组:1,-2,3,10,-4,7,2,-5,最大子数组为:3,10,-4,7,2

    思路分析:

    这个题目与上面的题目不同,可以使用DP的思想来解决了。

    • 用sum[i]表示以a[i]为结尾的子数组中最大的和;
    • 因为和一定是以a[i]为结尾,所以它一定会有,关键看前面的和要不要。
    • 状态转移方程为:sum[i] = max(sum[i-1]+a[i], a[i])。
    • 时间复杂度为:O(n)。
    • /**
           * Get the max sum of subArray.
           * @param nums
           * @return
           */
          public int getMaxSumOfSubArray(int[] nums) {
              int[] sum = new int[nums.length];
              sum[0] = nums[0];
              int res = sum[0];
              for(int i=1; i<nums.length; i++) {
                  sum[i] = Math.max(sum[i-1]+nums[i], nums[i]);
                  res = Math.max(res, sum[i]);
              }
              return res;
          }
    • 如果是返回和最大数组本身,则需要使用两个指针,分别记录所得数组的起始位置和结束位置。
    • /**
           * Get the max sum itself of subArray.
           * @param nums
           * @return
           */
          public int getMaxSumOfSubArraySelf(int[] nums) {
              int[] sum = new int[nums.length];
              sum[0] = nums[0];
              int res = sum[0];
              int start = 0, end = 0;
              for(int i=1; i<nums.length; i++) {
                  if(nums[i] > sum[i-1] + nums[i]) {
                      start = i;
                      sum[i] = nums[i];
                  }
                  else 
                      sum[i] = sum[i-1]+nums[i];
                  //sum[i] = Math.max(sum[i-1]+nums[i], nums[i]);
                  if(sum[i] > res) {
                      end = i;
                      res = sum[i];
                  }
                  
              }
              for(int i=start; i<=end; i++) {
                  System.out.print(nums[i] + "  ");
              }
              System.out.println();
              return res;
          }
      View Code

    6. 荷兰国旗问题

    问题描述:现有红、白、蓝三个不同颜色的小球,乱序排列在一起,请重新排列这些小球,使得红白蓝三色的同颜色的球在一起。

    分析思路:

    • 假设有三个指针front,current,end,分别表示:front表示白球开始的位置,其前面的都是红球;current表示当前元素;end表示蓝球开始的位置;
    • 开始时可以让a[front] = a[current] = a[0], a[end] = a[n-1]
      • 如果a[current]指向红球,则
        • 如果front == current, 则front++, current++;
        • 如果front != current, 则a[front]与a[current]交换,front++。
      • 如果a[current]指向白球,则current++;
      • 如果a[current]指向蓝球,则a[current]与a[end]交换,end--;
      • 以上也是循环不变式的应用:三个变量front, curretn, end,将数组分成4个区域:
        • [0, front):所有的数据都是红球;
        • [front, cur):所有的数据都是白球;
        • [cur, end):未知;
        • [end, n):所有的数据都是蓝球。
        • 初始时,before=cur=0,end=n-1,所以第1,2,4个区间都是空集,满足循环不变式;
        • 遍历cur,根据a[cur]的值做出相应的处理,知道区间[cur, end)为空,在计算的过程中也保持循环不变式,所以最终的到的也是循环不变式。且都保持真值。
      • Version1:
        • /**
               * Dutch National Flag Problem.
               * @param nums
               * @return
               */
              public int[] DutchNationFlag(int[] nums) {
                  int front = 0, cur = 0, end = nums.length-1;
                  while(cur < end) {
                      if(nums[cur] == 2) {
                          nums[cur] = nums[end];
                          nums[end] = 2;
                          end--;
                      }
                      else if(nums[cur] == 1) {
                          cur++;
                      }
                      else { //nums[cur] == 0
                          if(cur != front) {
                              nums[cur] = nums[front];
                              nums[front] = 0;
                              front++;
                          }
                          else {
                              cur++;
                              front++;
                          }
                      }
                  }
                  return nums;
              }
          View Code
      • Version2:

        • public int[] DutchNationFlagVersion2(int[] nums) {
                  int front = 0, cur = 0, end = nums.length-1;
                  while(cur < end) {
                      if(nums[cur] == 2) {
                          nums[cur] = nums[end];
                          nums[end] = 2;
                          end--;
                      }
                      else if(nums[cur] == 1) {
                          cur++;
                      }
                      else { //nums[cur] == 0
                          if(cur != front) {
                              nums[cur] = nums[front];
                              nums[front] = 0;
                          }
                          cur++; //为保持代码的整洁性,因为front指向的要么是1要么是0,因此即使不相等都可以让current放心的自加
                          front++;
                      }
                  }
                  return nums;
              }
          View Code
            

    7. 数组的最大间隔

    问题描述:给定整数数组a[0,...,n-1],求这n个数排序后最大间隔。如:1,7,14,9,4,13的最大间隔是4。

    思路分析:

    • 借鉴桶排序的思想。
      • 假定N个数的最大值最小值分别为:max,min. 则这N个数形成N-1个间隔,每个间隔的大小为:(max - min)/N-1;  
        • 如果这个N个数均匀的分散在N-1个区间中,则间距都是上面的间隔且最小;
        • 如果N个数不是均匀分散的,则最大间距一定存在。
        • 因此我们可以看相邻两个区间的最小值与最大值,然后相减,最大的插值 即为最大的间隔。
        • 因为在寻找的过程中我们只是遍历数组,所以时间复杂度为:O(N),空间复杂度为O(N)
        • 如果使用先排序的思想,则时间复杂度最好为:O(NlogN)
        • //The struct of Bucket    
          class Bucket {
              int min = Integer.MAX_VALUE;
              int max = Integer.MIN_VALUE;
              boolean valid = false;
              public void add(int x) {
                  if(valid == false) 
                      valid = true;
                  if(max < x)
                      max = x;
                  if(min > x)
                      min = x;
              }
          }
          
          /**
               * Get the max gap from the sorted nums.(The array is not sorted)
               * @param nums
               * @return
               */
              public int getMaxGap(int[] nums) {
                  Bucket[] buckets = new Bucket[nums.length];
                  for(int i=0; i<nums.length; i++)
                      buckets[i] = new Bucket();
                  int max = Integer.MIN_VALUE;
                  int min = Integer.MAX_VALUE;
                  for(int i=0; i<nums.length; i++) {
                      if(max < nums[i])
                          max = nums[i];
                      if(min > nums[i])
                          min = nums[i];
                  }
                  
                  //put the data to the buckets.
                  int gap = max - min;
                  int n = nums.length;
                  for(int i=0; i<nums.length; i++) {
                      int num = ((nums[i] - min) * n) / gap;
                      if(num >= n) {
                          buckets[n-1].add(nums[i]);
                      }
                      else 
                          buckets[num].add(nums[i]);
                  }
                  
                  //find the max gap.
                  int maxgap = -1;
                  int i = 0;
                  for(int j=1; j<n; j++) { 
                      if(buckets[j].valid) {
                          maxgap = Math.max(maxgap, buckets[j].min - buckets[i].max);
                          i = j;
                      }
                  }
                  return maxgap;
              }
  • 相关阅读:
    使用Astah画UML类图经验总结
    Qt的四个常见的图像叠加模式
    获取Linux时间函数
    DBus学习网站
    线程属性pthread_attr_t简介
    Secure CRT 自动记录日志log配置
    MySQL的group_concat()函数合并多行数据
    MySQL的Limit详解
    异步查询json传日期格式到前台,变成了时间戳的格式
    启动studio报错Gradle error
  • 原文地址:https://www.cnblogs.com/little-YTMM/p/5456780.html
Copyright © 2011-2022 走看看