每列大于0的个数_题目1342——把一个数字减少到0的步骤数
封面來自leetcode.com
題目描述
給定一個非負整數,返回把這個數減少到0的操作步驟數。當給定的數是偶數時,把它除以2;當給定的數是奇數時,把它減去1。
示例
例1
輸入:num = 14
輸出:6
解釋:
結束
約束條件
num大于等于0,并且小于等于10的6次方
題解
初看這道題,示例給出的邏輯就已經形成代碼邏輯,所以只需要用代碼把文字描述實現出來,便得出解法
/*** @param {number} num* @return {number}*/ var numberOfSteps = function(num) {var ans = 0while (num != 0) {if (num % 2 === 1) { num--} else {num = num / 2}ans++}return ans };怎樣解不管時間復雜度和空間度其實都不大,因為題目本身有點像在做二分法,時間復雜度是O(log n),空間復雜度只用到了一個計數變量,所以是O(1)。
所以題目解法本身看上去的確已經沒有什么優化的空間了。
但是看了官方解法(我是會員所以能看到)中提到一句很好的話,在這里分享給大家“解法2和解法3并不會改變時間復雜度,但它們對于問題提供了一種不同的思考方式,希望大家能夠擴展自己解決問題的思路”。原話如下:
Approach 2 and 3 don't change the time complexity, but they offer a different way of thinking about the problem that studying will hopefully help you expand your problem solving skills!話已至此,那我們不妨看看另外兩種“不會降低時間復雜度”的解法。看完發現,果然。其實leetcode題做個幾道,如果有悟性的同學,都會發現萬事萬物都有一定的套路(即規律)。這里指的就是“位運算”。
位運算這東西對于不愿意關注的人來說,可能一直是一個很抵觸、很陌生的東西,感覺很難,而且業務中也不會用到。但對于研究過的同學都知道,位運算是最接近計算機運作原理的計算方式,自然效率也因為更接近“底層”而變高一些。
這就像是你本來可以用軟件封裝給你的一些工具方法實現你想做的事,但軟件本身給你預留了一個直接訪問底層的API,你可以自己去實現軟件提供給你的這些方法,并且能玩出更多的花樣,順帶收益更高的運算效率。
位運算就像這樣的一個東西。
解法二
仔細觀察發現我們的每次操作不是“除以2”就是“減去1”。
在二進制中,當一個數是奇數時,其末尾一定是1,而偶數時,其末尾一定是0。所以按照題目要求:
當末尾是1,即奇數時,我們對這個二進制進行減1,隨之其末尾變成0,即偶數,需要除以2,在二進制運算中除以2就是向右移動1位,這樣這個二進制數則會少一位,當重復如此操作直至所有位都移完,這個數就變成0了。
那么對于本題結果的計算便轉換成了二進制操作移位的步數,即遇到1,需要操作2步,即減1+右移;遇到0,只需操作1步,即右移。
所以題目的解法變成目標數轉換成二進制格式后中“1的個數*2+0的個數*1”,即本文的封面圖。
但這里有個需要注意的邊界問題,即當二進制數移動到只剩“1”時,只需要減1,即得到結果,需要停止計算。也就是對于最后一個1的操作數不需要乘2,所以要在總的結果數中減1即可。
在這一點上,官方題解中也有一句很好的告誡“我們都知道,因為一個錯誤導致滿盤皆屬的情況經常發生,我們應該對情況的邊界小心留意“。原話如下:
as we know this "off-by-one-error" will always happen (except when the initialnumis0, we need to be careful of that edge case too!).代碼如下:
/*** @param {number} num* @return {number}*/ var numberOfSteps = function(num) {if (num === 0) return numvar ans = 0num = num.toString(2)for (const item of num) {if (+item === 1) {ans+=2} else {ans+=1}}return ans-1 };解法三
解法二的弊端在于,它相比解法一反而增加了空間復雜度,因為我們把原本的整數存儲為了字符串,字符串占用的空間會比整數多。
于是解法三中的優化是用“逐位比較”取代“將整數轉換為二進制字符串”。
我們知道“按位與”操作(&)是可以比較出某一位是否是1的,因為當某位是1時,與1操作會得到1,而當某位是0時,與1操作會得到0。
所以我們完全無需像解法中把數字轉換為二進制字符串,直接將原整數依次和一個只有固定位上是1的數進行與操作,這個固定位我們可以控制為從末尾到首位,而這個操作則只需將1依次進行乘2操作(或左移)即可。
至于統計操作步驟的規則依然如解法二,即遇到位值為1時,計2步,遇到位值為0時,計1步,最終結果減1。
因為與操作的特點是,兩個操作數都為1,結果才為1,只要有一個操作步為1,結果都會0。
而我們提供的那個動態的數是確定的依次逐位為1,所以如果某一次計算的結果是0,則證明所有位都不相同,即與我們的動態數當前為1對應位置上的數為0,則步數計算應該+1,否則+2。
代碼如下
/*** @param {number} num* @return {number}*/ var numberOfSteps = function(num) {if (num === 0) return numvar ans = 0;var flag = 1;while (flag <= num) {const temp = flag & numif (temp !== 0) {ans = ans + 2;} else {ans = ans + 1;}flag = flag * 2}return ans - 1 };由于解法三是直接拿整數本身進行與操作計算的,所以沒有像解法二額外將整數轉換為字符串,增加了空間復雜度,所以解法三同解法一,空間復雜度為O(log n),空間復雜度為O(1)。
總結
于我個人而言,做這道題最大的收獲不是這道簡簡單單的題目本身,而是文中引用的兩句話。因為遵囑這樣的告示,可能會對今后做更多的題乃至工作中會有莫大的幫助,共勉。
總結
以上是生活随笔為你收集整理的每列大于0的个数_题目1342——把一个数字减少到0的步骤数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 上升沿_输入输出的上升沿和下降沿是怎么来
- 下一篇: python小结_python简单小结