数组相关的算法题

内容包括:

  • 子数组和最大的最小子集、子数组和>=定值的最小子集、数组的主元素、连续子数组的最大和、
  • 一个无序数组的中位数、两个有序数组的中位数、
  • 数组的平衡点(两边的和相等)、数组的分裂点(左边的数都<=它,右边的数都>=它)、
  • 两个数组(有序或无序两种)的对称差集、两个有序数组的交集;
  • 最长上升子序列,三种元素排序;
  • 待补充的其他问题。

C++语言描述。


// 快速排序
void quickSort(int a[], int left, int right)
{
  if(left < right)
  {
    int i = left, j = right;
    int x = a[left];
    while(i < j)
    {
      while(i < j && a[j] >= x)
        --j;
      if(i < j) a[i++] = a[j];
      while(i < j && a[i] < x)
        ++i;
      if(i < j) a[j--] = a[i];
    }
    a[i] = x;
    quickSort(a, left, i - 1);
    quickSort(a, i + 1, right);
  }
}

/* LintCode-761: 数组和的最小子集
 * 问题:非负整数数组. 取数组中的一部分元素, 使得它们的和大于数组中其余元素的和, 
 * 求出满足条件的元素数量最小值。
 * 给出 nums = [3, 1, 7, 1], 返回 1
 * 给出 nums = [2, 1, 2], 返回 2
 */
int minElement(vector<int>& arr){
  sort(arr.begin(), arr.end());
  int sum = 0;
  for (int i = 0; i < arr.size(); i++)
  {
    sum += arr[i];
  }
  sum = sum/2 + 1;
  int cnt = 0;
  for (int i = arr.size()-1; i >= 0 ; --i)
  {
    sum -= arr[i];
    ++cnt;
    if(sum <= 0) return cnt;
  }
}

/*
 * 求最短子数组的大小,要求子数组的和>=s。
 * Minimum Size Subarray sum
 * For example, given the array [2,3,1,2,4,3,2,2] and s = 7, Output: 2,
 * the subarray [4,3] has the minimal length, [3,2,2] dose Not.
*/
int minSumSize(vector<int>& nums, int s)
{
  // sum是滑动窗口的和,start是窗口的起始位置
  int ret = INT_MAX, sum = 0, start = 0;
  for(int i = 0; i < nums.size(); ++i)
  {
    sum += nums[i];
    while(start <= i && sum >=s)
    {
      // 记录最短长度
      ret = min(ret, i - start + 1);
      // 窗口右移
      sum -= nums[start++];
    }
  }
  return ret == INT_MAX ? -1 : ret;
}

/* LeeCode-168 Majority Element
 * 给定一个大小为n的数组,找出主元素。主元素就是出现次数大于floor(n/2)次的元素。
 * 你可以假设数组是非空的,并且主元素始终存在于数组中。
 * Input: [2,2,1,1,1,2,2] 返回:2
*/
int majorityElement(vector<int>& arr) {
  int ret = 0, cnt = 0;
  for (int num : arr) {
    if (0 == cnt) { //记录当前num
      ret = num;
      ++cnt;
    } else if( num == ret) { //遇到同一个数
      ++cnt;
    } else {
      --cnt;
    }
  }
  return ret;
}
//进阶问题,出现次数大于floor(n/3)的所有元素。
// Input: [1,1,1,3,3,2,2,2] Output: [1,2]
// 摩尔投票法 Moore Voting
vector<int> majorElement(vector<int>& nums)
{
  vector<int> ret;
  int a = 0, b = 0, cnt1 = 0, cnt2 = 0;
  for(int num : nums) {
    if(num == a) {
      ++cnt1;
    } else if(num == b) {
      ++cnt2;
    } else if(cnt1 == 0) {
      a = num; cnt1 = 1;
    } else if(cnt2 == 0) {
      b = num; cnt2 = 1;
    } else {
      --cnt1; --cnt2;
    }
  }
  cnt1 = cnt2 = 0;
  for (int num : nums) {
    if(num == a) ++cnt1;
    else if(num == b) ++cnt2;
  }
  if(cnt1 > nums.size()/3) ret.push_back(a);
  if(cnt2 > nums.size()/3) ret.push_back(b);
  return ret;
}

/*
 * 连续子数组的最大和,返回和。
 * Input: [-2,1,-3,4,-1,2,1,-5,4] Output: 6 
 * 最大和的子数组是[4,-1,2,1]
*/
int maxSubSum(vector<int>& nums)
{
  int retSum = INT_MIN, curSum = 0;
  for(int i = 0; i < nums.size(); ++i) {
    if(curSum <= 0) {
      curSum = nums[i];
    } else {
      curSum += nums[i];
    }
    if(curSum > retSum) {
      retSum = curSum;
    }
  }
  return retSum;
}

/*
 * 求无序数组的中位数,复杂度要求<=O(N*logN)
*/
int partSort(int a[], int start, int end)
{
  int left = start;
  int right = end;
  int key = a[end];
  while(left < right)
  {
    while(a[left] <= key && left < right) ++left;
    while(a[right] >= key && left < right) --right;
    if(left < right)
      swap(a[left], a[right]);
  }
  swap(a[right], a[end]);
  return left;
}
int getMedian(int a[], int len)
{
  int start = 0;
  int end = len - 1;
  // 不论len是奇偶数,mid都用 len/2 ;
  int mid = len / 2; //注意不是 (len-1)/2
  int part = partSort(a, start, end);
  while(part != mid)
  {
    if(part > mid)
      part = partSort(a, start, part - 1);
    else
      part = partSort(a, part + 1, end);
  }
  return a[mid];//注意不是mid+1
}

/*
 * 求两个有序数组的中位数,复杂度要求O(log(m+n))。
 * nums1 = [1, 2], nums2 = [3, 4]; The median is (2 + 3)/2 = 2.5
 * nums1 = [1, 3], nums2 = [2]; The median is 2.0
*/
// k in {(m+n+1)/2, (m+n+2)/2}
int findKth(vector<int>& nums1, int i, vector<int>& nums2, int j, int k)
{
  if(i > nums1.size()) {
    //在nums2中找到从j开始的第k个数
    return nums2[j+k-1];
  }
  if(j > nums2.size()) {
    return nums1[i+k-1];
  }
  if(1 == k) {
    return min(nums1[i], num2[j]);
  }
  // 若剩余大小不足k/2,则从另一个数组中取第k/2个数
  int mid_a = (i + k/2 -1 < nums1.size()) ? nums1[i + k/2 -1] : INT_MAX;
  int mid_b = (j + k/2 -1 < nums2.size()) ? nums2[j + k/2 -1] : INT_MAX;
  if(mid_a < mid_b)
  {
    return findKth(nums1, i + k/2, num2, j, k - k/2);
  } else {
    return findKth(nums1, i, num2, j + k/2, k - k/2);
  }
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
    int m = nums1.size(), n = nums2.size(), left = (m + n + 1) / 2, right = (m + n + 2) / 2;
    return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
}
/*
[解释如下:](https://www.cnblogs.com/grandyang/p/4465932.html)

假设两个有序数组的长度分别为m和n,由于两个数组长度之和 m+n 的奇偶不确定,因此需要分情况来讨论,对于奇数的情况,直接找到最中间的数即可,偶数的话需要求最中间两个数的平均值。使用一个小trick:
分别找第 (m+n+1) / 2 个,和 (m+n+2) / 2 个,然后求其平均值即可,这对奇偶数均适用。若 m+n 为偶数的话,取这两个数的平均值;若 m+n 为奇数的话,那么其实 (m+n+1) / 2 和 (m+n+2) / 2 的值相等,相当于两个相同的数字相加再除以2,还是其本身。

这里需要定义一个函数来在两个有序数组中找到第K个元素。K 就是(m+n+1) / 2 或者(m+n+2) / 2。

使用两个变量i和j分别来标记数组 nums1 和 nums2 的起始位置。先考虑边界情况:
1,当某一个数组的起始位置大于等于其数组长度时,说明其所有数组均已经查找过了,只需要直接在另一个数组中找从i或j开始的第k个数就可以了。
2,当K=1的时候,变成分别查找第1个数,只要比较 nums1 和 nums2 的起始位置i和j上的数字,然后直接返回。

难点就在于一般的情况怎么处理,因为需要在两个有序数组中找到第K个元素。
为了加快搜索的速度,可以使用二分法,分别在 nums1 和 nums2 中查找第 K/2 个元素。
先 check 一下,数组中到底存不存在第 K/2 个数字,如果存在就取出来,否则就赋值上一个整型最大值,因为目的是要在 nums1 或者 nums2 中先淘汰 K/2 个较小的数字,判断的依据就是看 midVal1 和 midVal2 谁更小,但如果某个数组的个数都不到 K/2 个,就将其对应的 midVal 值设为整型最大值,表示不从这个数组淘汰出去。
*/

//---------------------------------

/*
 * 寻找数组的平衡点问题
 * 平衡点:比如int numbers[]={1,3,5,7,8,25,4,20}; 
 * 25前面的总和为24,25后面的总和也是24,25这个点就是平衡点。
 * O(2n)算法如下:
*/
int findBalance(int arr[], int len)
{
  long sumLeft = 0;
  long sumRight = 0;
  for(int i=1; i < len; ++i)
  {
    sumRight += arr[i];
  }
  for(int j = 1; j < len; ++j)
  {
    sumLeft += arr[j-1];
    sumRight -= arr[j];
    if(sumLeft == sumRight)
      return j;
  }
  return -1;
}

/*
 * 在数组里查找这样的数,其左边的数都小于等于它,右边的数都大于等于它。
 * 使用额外数组,比如rightMin[],来帮我们记录原始数组array[i]右边(包括自己)的最小值。
 * 假如原始数组为: array[] = {7, 10, 2, 6, 19, 22, 32}, 那么rightMin[] = {2, 2, 2, 6, 19, 22, 32}. 
 * 从头开始遍历原始数组时,我们保存一个当前最大值 max, 如果当前最大值刚好等于rightMin[i],
 * 那么这个最大值即为所求。
 * 例如数组 {7, 10, 2, 6, 19, 22, 32} 返回19;
 *     数组 {1,3,5,7,8,25,4,20},返回第二个数3.
*/
int smallLarge(int data[], int len)
{
  if(data == NULL || len < 1) return -1;
  
  std::vector<int> rightMin(len);
  
  rightMin[len-1] = data[len-1];
  for(int i = len - 2; i >= 0; --i)
  {
    rightMin[i] = min(data[i], rightMin[i+1]);
  }
  
  int leftMax = data[0];
  for(int i = 1; i < len; ++i)
  {
    // [0, i]的最小值
    leftMax = max(data[i], leftMax);
    if(leftMax == rightMin[i])
    {
        return data[i];
    }
  }
  
  return -1;
}

// 求两个数组的对称差集,即只在其中一个数组中出现过的数字组成的集合。
// C++ STL库函数:std::set_symmetric_difference()
// 例如 {1, 3, 5, 7, 9, 11} 和 {1, 1, 2, 3, 5, 8, 13}
// 它们的差集是:{2, 7, 8, 9, 11, 13}
// 两个无序数组的差集:互相二分查找,复杂度为 m*log(n) + n*log(m)。
vector<int> vec_diff_1(vector<int>& vm, vector<int>& vn)
{
    vector<int> vec_ret;
    for(int i = 0; i < vm.size(); i++)
    {
        if(0 == std::binary_search(vn.begin(), vn.begin()+vn.size(), vm[i]))
        {
            vec_ret.push_back(vm[i]);
        }
    }
    for(int i = 0; i < vn.size(); i++)
    {
        if(0 == std::binary_search(vm.begin(), vm.begin()+vm.size(), vn[i]))
        {
            vec_ret.push_back(vn[i]);
        }
    }
    // 对结果排序,可以省略
    sort(vec_ret.begin(), vec_ret.end());
    
    return vec_ret;
}
// 两个有序数组的差集:复杂度O(m+n)
// 事先排序两个数组复杂度:mlog(m)+nlog(n)
vector<int> vec_diff_2(vector<int>& vm, vector<int>& vn)
{
    vector<int> result;
    int first_m=0, first_n=0;
    int last_m = vm.size();
    int last_n = vn.size();
    while(first_m != last_m && first_n != last_n)
    {
        if(vm[first_m] < vn[first_n])
        {
            result.push_back(vm[first_m]);
            ++first_m;
        }
        else if(vn[first_n] < vm[first_m])
        {
            result.push_back(vn[first_n]);
            ++first_n;
        }
        else {
            ++first_m;
            ++first_n;
        }
    }
    
    copy(vm.begin()+first_m, vm.end(), std::inserter(result, result.begin()+result.size()));
    
    copy(vn.begin()+first_n, vn.end(), std::inserter(result, result.begin()+result.size()));
    
    return result;
}

// 求两个数组的交集
// 例如 {1, 3, 5, 7, 9, 11} 和 {1, 1, 2, 3, 5, 8, 13}
// 它们的交集是:{1,3,5}
vector<int> vec_intersection(vector<int>& vm, vector<int>& vn)
{
    vector<int> result;
    int first_m=0, first_n=0;
    int last_m = vm.size();
    int last_n = vn.size();
    while(first_m != last_m && first_n != last_n)
    {
        if(vm[first_m] < vn[first_n])
        {
            ++first_m;
        }
        else if(vn[first_n] < vm[first_m])
        {
            ++first_n;
        }
        else {    //  vm[first_m] == vn[first_n]
            result.push_back(vm[first_m]);
            ++first_m;
            ++first_n;
        }
    }
    return result;
}

/*
 * 最长上升子序列
 * 例如[10,9,2,5,3,7,20,18] 返回4,[2,3,7,20]或者[2,3,7,18]
*/
int maxIncSeq(vector<int>& nums)
{
  vector<int> vbuf;
  for(int i = 0; i < nums.size(); ++i)
  {
    //找到第一个>=当前值的位置
    vector<int>::iterator it = lower_bound(vbuf.begin(), vbuf.end(), nums[i]);
    if(it == vbuf.end())
      vbuf.push_back(nums[i]);
    else
      *it = nums[i];
  }
  return vbuf.size();
}
// 下面这种写法也行:
int maxIncSub(int arr[], int n)
{
  int len = 0;
  int* buf = new int[n+1];
  for(int i = 0; i < n; i++)
  {
    // 二分查找第一个>=当前值的位置
    int pos = lower_bound(buf, buf+len, arr[i]) - buf;
    if(pos == len) {
      buf[len++] = arr[i];
    }
    else {
      buf[pos] = arr[i];
    }
  }
  delete[] buf;
  
  return len;
}

//三种颜色排序问题
void sort_colors(int[] colors, int len)
{
  int left = 0, right = len - 1;
  int cur = 0;
  while(cur <= right)
  {
    if(colors[cur] == 0)
    {
      swap(colors[cur],colors[left]);
      left++;
      cur++;
    }
    else if(colors[cur] == 2)
    {
      swap(colors[cur], colors[right]);
      right--;
    }
    else // if(colors[cur] == 1)
    {
      cur++;
    }
  }
}






未完待续。

你可能感兴趣的:(编程基础)