LeetCode 2 Keys Keyboard

LeetCode 2 Keys Keyboard

Initially on a notepad only one character 'A' is present. You can perform two operations on this notepad for each step:

  1. Copy All: You can copy all the characters present on the notepad (partial copy is not allowed).
  2. Paste: You can paste the characters which are copied last time.

Given a number n. You have to get exactly n 'A' on the notepad by performing the minimum number of steps permitted. Output the minimum number of steps to get n 'A'.

Example 1:

Input: 3
Output: 3
Explanation:
Intitally, we have one character 'A'.
In step 1, we use Copy All operation.
In step 2, we use Paste operation to get 'AA'.
In step 3, we use Paste operation to get 'AAA'.

Note:

  1. The n will be in the range [1, 1000].

一个记事本中原本只有一个字符A,每次可以做两种操作中的一种,分别是全选和粘贴。问要得到n个字符A,至少需要经过多少次操作。

这一题我一开始想到用BFS来做,两种操作相当于两条搜索路径,所以很容易能写出BFS的代码:

class Solution {
private:
	struct Node {
		int presented_cnt_; // 现在有多少个字符
		int copied_cnt_; // 剪切板中有多少个字符
		int step_; // 走过多少步了
		Node(int p, int c, int s) :presented_cnt_(p), copied_cnt_(c), step_(s) {};
	};
public:
	int minSteps(int n) {
		queue<Node> q;
		Node node(1, 0, 0);
		q.push(node);
		while (!q.empty()) {
			Node cur = q.front();
			q.pop();
			if (cur.presented_cnt_ == n)
				return cur.step_;
			if (cur.presented_cnt_ > n)continue;
			Node copy_node(cur.presented_cnt_, cur.presented_cnt_, cur.step_ + 1); // 全选
			q.push(copy_node);
			if (cur.copied_cnt_ != 0) { // 粘贴
				Node paste_node(cur.presented_cnt_ + cur.copied_cnt_, cur.copied_cnt_, cur.step_ + 1);
				q.push(paste_node);
			}
		}
	}
};

预料到了,本代码提交TLE。

搜索会超时的,用DP一般都不会超时。设dp[i]表示要得到i个字符A,至少需要的操作数。显然,dp[1]=0。

假设已经求到了dp[1,...,i-1],现在要求dp[i]。要得到i个字符,最多的操作数是i,即先复制一个A,然后粘贴i-1次,总共需要i次操作。比较快的方法是,如果i是偶数,则可以从i/2个字符开始,全选粘贴就能得到i个字符。

所以我们遍历i前面的所有j,如果i能整除j,则可以全选j,然后粘贴i/j-1次得到i个字符,即操作数是1+(i/j-1)=i/j,当然还需要加上到达i的操作数,所以dp[j]=dp[i]+i/j。

完整代码如下:

class Solution {
public:
	int minSteps(int n) {
		vector<int> dp(n + 1, INT_MAX);
		dp[1] = 0;
		for (int i = 2; i <= n; ++i) {
			dp[i] = i;
			for (int j = i - 1; j >= 1; --j) {
				if (i%j == 0) {
					dp[i] = min(dp[i], dp[j] + i / j);
				}
			}
		}
		return dp[n];
	}
};

本代码提交AC,用时79MS。

还有一种解法类似于素数筛法,我们首先知道dp[1]=0,如果我们对1个字符先全选,然后不停粘贴,则能得到2,3,4,...个字符,且操作数是2,3,4,...;下一次,我们知道dp[2]=2,如果我们对2个字符全选,然后不停粘贴,则能得到4,6,8,...个字符,且操作数是4,5,6,...。每一轮我们都只保留最小操作数。最终筛完之后,就是全局最优结果了。

完整代码如下:

class Solution {
public:
	int minSteps(int n) {
		vector<int> dp(n + 1, INT_MAX);
		dp[1] = 0;
		for (int i = 1; i <= n; ++i) {
			for (int j = 2 * i, k = 2; j <= n; j += i, ++k) {
				dp[j] = min(dp[j], dp[i] + k);
			}
		}
		return dp[n];
	}
};

本代码提交AC,用时3MS,速度飞快,因为越到后面,循环倍数越少。

Leave a Reply

Your email address will not be published. Required fields are marked *