删数问题【贪心】

题目

给定n位整数d,去掉k(k<n)个数字后剩下的数字按原次序排列组成一个新的正整数。
求最小正整数。

贪心法一般有两种思路。

思路1

一种是 缩小问题规模
比如说本题就可以把原题改为

给定n位整数d,去掉1个数字后剩下的数字按原次序排列组成一个新的正整数。
求最小正整数。

进一步说,可以转化成:找一个数x,无论删除x前的数还是x后的数,结果都不会比删除x结果好
下面分类讨论

删除的数在x前

设删除的数为 a
在这里插入图片描述
如图
只要对任意a, 满足 a ≤ b a \leq b ab就行了。换句话说,x为非递减序列的末尾。

删除的数在x后

设删除的数为 a
在这里插入图片描述
利用前面的结果,x前为非递减序列的末尾。换句话说就是x>y。直接满足删除x最优。

代码

算法过程

为了更好理解。这里用1238234657作具体的例子。
删除4个数字。
中间过程如下

1238234657
123234657
12234657
1223457
122345

优化

每轮都要删除非递减序列末尾的数。由于删除的是末尾,删除后不用再从头遍历(已经判断过是非递减序列了),这样可降低时间复杂度。但是删除以后要移动元素,总体的时间复杂度还是 O ( k n ) O(kn) O(kn)。这里我们选择链表作为数据载体进一步把时间复杂度降到 O ( n ) O(n) O(n)

#include "study.h"

int main(int argc, char const *argv[]) {
    
    
  freopen("7.10.2.txt", "r", stdin);
  int k, n;
  cin >> k;
  string d;
  cin >> d;
  list<char> li(d.begin(), d.end());
  list<char>::iterator it1 = li.begin(), it2 = li.begin();
  for (int i = 0; i < k; i++) {
    
    
    for (++it2; it2 != li.end(); ++it1, ++it2) {
    
    
      if (*it1 <= *it2) continue;
      it2 = it1 = --li.erase(it1);
      break;
    }
  };
  for (it1 = li.begin(); it1 != li.end(); ++it1) cout << *it1;
  cout << '\n';
  return 0;
}

思路2

另外一种是 分解问题步骤
本题就可以把原题改为

从 整数d 中挑选数字,依次填入长度为 n-k 的数组中。
求数组组成的最小数字

这个思路更简单
由于是先填高位的而且位数是确定的,只要尽可能填小的数就行了
比如1XXX肯定比2XXX小。
但是有个限制就是不能选太后面的数,否者位数不够。
比如5412你选1的话长度最长也就2了。
另外还有个限制就是,如果选择1,则1前面的数字都不能选了。
这里我选择优先队列,也就是堆作为数据载体。
Node 里的val存的是实际字符,len表示最大可能长度

代码

#include "study.h"
// 删数问题
struct Node {
    
    
  char val;
  int len;
  bool operator<(const Node &r) const {
    
    
    if (val == r.val) {
    
    
      // 长度优先
      return len < r.len;
    }
    // val 小优先
    return r.val < val;
  }
};

int main(int argc, char const *argv[]) {
    
    
  freopen("7.10.2.txt", "r", stdin);
  string d;
  int k, n;
  cin >> k >> d;
  n = d.size();
  priority_queue<Node> pq, pq2;
  for (int i = 0; i < n; i++) pq.push({
    
    d[i], n - i});
  // need需要的数字个数,lastlen上一次选择的长度
  for (int need = n - k, lastlen = n; !pq.empty() && need;) {
    
    
    Node cur = pq.top();
    pq.pop();
    // 这次选择的长度不能比上一次的小
    if (cur.len > lastlen) continue;
    if (need > cur.len) {
    
    
      // 长度不够,放到下一轮
      pq2.push(cur);
    } else {
    
    
      // 存入答案,并更新需要的数字个数,和选择的长度
      d[n - k - (need--)] = cur.val;
      lastlen = cur.len;
      while (!pq2.empty()) {
    
    
        // 把没选的加回来
        pq.push(pq2.top());
        pq2.pop();
      }
    }
  }
  d[n - k] = '\0';
  cout << d.c_str() << '\n';
  return 0;
}

附件

study.h 头文件

#ifndef STUDY
#define STUDY

#include <bits/stdc++.h>
using namespace std;

#define MAX_INT 0x3f3f3f3f
#define MIN_INT 0xc0c0c0c1

istream &operator>>(istream &in, vector<int> &v) {
    
    
  v.clear();
  int x;
  if (in >> x) {
    
    
    v.push_back(x);
    string s;
    getline(in, s);
    stringstream ss(s);
    while (ss >> x) v.push_back(x);
  }
  return in;
}

istream &operator>>(istream &in, vector<vector<int>> &v) {
    
    
  for (int i = 0; i < v.size(); i++) cin >> v[i];
  return in;
}

ostream &operator<<(ostream &out, vector<int> arr) {
    
    
  for (int i = 0; i < arr.size(); i++) out << arr[i] << ' ';
  return out;
}

ostream &operator<<(ostream &out, vector<vector<int>> arr) {
    
    
  for (int i = 0; i < arr.size(); i++) out << arr[i] << '\n';
  return out;
}
#endif

猜你喜欢

转载自blog.csdn.net/qq_45256489/article/details/122010600