给你一个整数 n,你需要重复执行多次下述操作将其转换为 0 :
翻转 n 的二进制表示中最右侧位(第 0 位)。如果第 (i-1) 位为 1 且从第 (i-2) 位到第 0 位都为 0,则翻转 n 的二进制表示中的第 i 位。返回将 n 转换为 0 的最小操作次数。
示例 1:
输入:n = 0 输出:0示例 2:
输入:n = 3 输出:2 解释:3 的二进制表示为 "11" "11" -> "01" ,执行的是第 2 种操作,因为第 0 位为 1 。 "01" -> "00" ,执行的是第 1 种操作。示例 3:
输入:n = 6 输出:4 解释:6 的二进制表示为 "110". "110" -> "010" ,执行的是第 2 种操作,因为第 1 位为 1 ,第 0 到 0 位为 0 。 "010" -> "011" ,执行的是第 1 种操作。 "011" -> "001" ,执行的是第 2 种操作,因为第 0 位为 1 。 "001" -> "000" ,执行的是第 1 种操作。示例 4:
输入:n = 9 输出:14示例 5:
输入:n = 333 输出:393提示:
0 ≤ n ≤ 1 0 9 0 \leq n \leq 10^9 0≤n≤109这是周赛的最后一题。做完第三题的时候我还有一个小时做这道题,而且已经感觉自己已经无限接近于答案了,但就是一直超时。题目的意思是,对n可以进行如上的两种操作。我先说说比赛时候的想法吧,当时看出来了如下几个结论:
n的范围很大,肯定不能广搜,或者说不能很基础的搜索,应该是存在着某种贪心策略发现操作的本质就是逐个消去最高位的1,让最高位的1之后变成1000…,这样才可以消去最高位的1。当时发现似乎最重要的结论是,第一个操作和第二个操作一定是交替进行的。因为重复两次的翻转没有任何意义。所以要么是1,2,1,2这样操作下去,要么是2,1,2,1操作下去。。一定可以得到0。于是我就这样遍历下去,发现必定会超时,说明遍历还是不可能过的。然后又发现二的指数幂变成0的操作次数是 f ( 2 k ) = 2 k + 1 − 1 f(2^k)=2^{k+1}-1 f(2k)=2k+1−1,会把到最高位1为止这么多位,所有数都遍历一遍。但是就是没能把几个特性给联系在一起构造出算法。看了lucifer1004的题解,豁然开朗。我们的操作目的分为两种,一种是把n变为0,用f(n)表示。刚刚分析出来,f(n)首先要把最高位的1右侧变成1000…,我们把这个操作称为g。接下来为了完成g,如果最高位1的右侧本来就是1,那么接下来的目的就是把这个1右侧全部变成0,可以调用f实现。否则,为了构造1000…,我们要先构造出0100…,然后翻转最高位。这个过程可以递归调用g实现。代码如下:
class Solution { int f(int n) { if (n <= 1) return n; int t = 32 - __builtin_clz(n) - 1; return (1 << t) + g(n ^ (1 << t), t - 1); } int g(int n, int t) { if (t == 0) return 1 - n; if (n & (1 << t)) return f(n ^ (1 << t)); return (1 << t) + g(n, t - 1); } public: int minimumOneBitOperations(int n) { return f(n); } };当然大佬眼里这道题就是一道送分题。。可以发现每次的操作就是格雷编码从G(n)变成G(n-1)的逆过程,所以就变成了:
class Solution { public: int minimumOneBitOperations(int n) { int ans = 0; while (n) { ans ^= n; n >>= 1; } return ans; } };