【算法訓練】DAY1:整數反轉

1 前言

題目來源于Leetcode。
在這里插入圖片描述

重點:理清邏輯,忽略細節,模仿高手,五毒神掌

2 題目分析

題目很容易理解,先分成兩個部分

  • 正數
  • 負數

先解決正數

最開始想到的是

int
char數組
long

唯一增加的就是,先判斷整數是多少位

之后再判斷溢出

然后解決負數
先使用一個bool變量保存符號,如果是負數,則取絕對值,再按正數進行運算,之后再加上符號,再判斷溢出。

整體思維非常容易想到,分治思想,下面是代碼。

3 自己想

int reverse(int x) {// 不管正負數,全變正數bool isNegativeNumber = false;int  xAbsoluteValue = 0;if (x < 0) {isNegativeNumber = true;if (x != INT_MIN)xAbsoluteValue = -x;elsereturn 0;}else {isNegativeNumber = false;xAbsoluteValue = x;}// 判斷整數多少位【動態的】int xTemporary = xAbsoluteValue;int count = 0;for (int i = 0; i < sizeof(int)*8; i++) { // 【注意】字節數*8if (xTemporary == 0) {break;}else {count++;}xTemporary /= 10;}// 反轉long long xNew = 0; // 不要用long,它在32位下也是4字節xTemporary = xAbsoluteValue;for (int i = 0; i < count; i++) {xNew = xNew * 10 + xTemporary % 10;xTemporary /= 10;}// 符號回歸if (isNegativeNumber) {xNew = -xNew;}// 判斷溢出if (xNew < INT_MIN || xNew > INT_MAX) {return 0;}else{return xNew;}}

在這里插入圖片描述
運行結果還可以,就是系統本身不穩定,有時候是4ms,這不重要,重要的是,這種做法太啰嗦了,我先嘗試按照這個思路優化一下。

去掉符號轉換,這部分沒有也一樣

注意使用long long,而不是long,32位下前者8字節,后者和int一樣4個字節

判斷溢出,使用一行代碼搞定,取締if else,使用三元運算符

// 判斷整數多少位【動態的】int xTemporary = x;int count = 0;for (int i = 0; i < sizeof(int) * 8; i++) { // 【注意】字節數*8if (xTemporary == 0) {break;}else {count++;}xTemporary /= 10;}// 反轉long long xNew = 0; xTemporary = x;for (int i = 0; i < count; i++) {xNew = xNew * 10 + xTemporary % 10;xTemporary /= 10;}return (xNew < INT_MIN || xNew > INT_MAX) ? 0 : xNew;

好了,這個程序已經優化了很多了,還有沒有空間繼續優化呢?

再優化,就只能在獲取整數位數下手,將其直接變成反轉的條件使用。

好吧……我做不下去了,看大神解法好了。

談一談收獲

  1. 對待算法題,重點關注邏輯,對于防御性編程等細節可以不用深究
  2. 先把邏輯在紙面上搞清楚,再寫代碼!
  3. int、long等數據類型的大小,根據系統位數以及編譯器決定,需要實際測試一下,盡量使用sizeof等通用的東西
  4. 計算位數部分,有一點動態規劃的意思,很有趣。

毫無移位,按照我這么寫算法題,可以涼涼了~~~~接下來,我來學習一下大神的解法吧。

4 看大神做法,直接模仿學會

4.1 大神一

long long res = 0;
while (x) {res = res * 10 + x % 10;x /= 10;
}
return (res < INT_MIN || res > INT_MAX) ? 0 : res;

這個大神與我的思路類似,只不過比我的又進一步優化,我們學習一下。

這里最重要的一點,不需要判斷多少位,也不需要暫存,不用管循環次數,循環結束的條件,就是x = 0

這也不是本質,這題的本質是數學問題

  • 1234變成4321的問題
  • 1234提取出每一個數字的問題

來看看我的算法中愚蠢的點

// 判斷整數多少位【動態的】int xTemporary = x;int count = 0;for (int i = 0; i < sizeof(int) * 8; i++) { // 【注意】字節數*8if (xTemporary == 0) {break;}else {count++;}xTemporary /= 10;}// 反轉long long xNew = 0; xTemporary = x;for (int i = 0; i < count; i++) {xNew = xNew * 10 + xTemporary % 10;xTemporary /= 10;}

關注一下兩個循環的條件

  • 循環32次,確定位數
  • 根據位數再反轉

事實上,我想的是,先確定好位數,這樣就不用每次都循環32次了,但是,我在確定位數的時候,還是循環了32次……蠢到家……

雖然不是每次循環32次,但是,這種程序結構無疑是垃圾的,盡管是雙重保險,但是沒有必要阿,我們警惕一下!

值得警惕的結構

拋開題目本身,我們看一看這個結構

for (int i = 0; i < sizeof(int) * 8; i++) { // 【注意】字節數*8if (xTemporary == 0) {break;}else {count++;}xTemporary /= 10;
}

for循環中,嵌套一個通過if判斷的跳出循環的裝置,我們來改進一下

while(xTemporary){ xTemporary /= 10;count++;}

嗯,這倆功能完全一樣,但是顯然后者更加簡潔

現在,我們是通過中介count來完成程序,那么,可以去掉中間商嗎?

當然可以!

在這里插入圖片描述
既然,xTemporary /= 10就可以作為終止條件,我們直接用就好了,沒必要再管中間商,忽略它!

看一下我們剛才優化的本質,x /= 10;while(x) 二者配合,作為循環終止條件,因此,我們進一步優化。

// 判斷整數多少位【動態的】int xTemporary = x;int count = 0;while(xTemporary){ xTemporary /= 10;count++;}// 反轉long long xNew = 0; while(xTemporary) {xNew = xNew * 10 + xTemporary % 10;xTemporary /= 10;}

這樣一來,你很容易發現,第一個循環完全沒有用,直接刪掉。

int reverse(int x) {long long xNew = 0; while(x) {xNew = xNew * 10 + x % 10;x /= 10;}return (xNew < INT_MIN || xNew > INT_MAX) ? 0 : xNew;
}

我們,成功將自己的爛程序一步步優化成了大神的程序。

為了程序的通用性,我們稍改一下

int reverse(int x) {long long xNew = 0; while(x != 0) {xNew = xNew * 10 + x % 10;x /= 10;}return (xNew < INT_MIN || xNew > INT_MAX) ? 0 : xNew;
}

因為只有C/C++使用0和false是一樣的,但是Java就不允許,只能使用布爾值。

4.2 大神二

int reverse(int x) {int d = 0;while (x){if (d > INT_MAX / 10 || d < INT_MIN / 10)return 0;d = d * 10 + x % 10;x = x / 10;}return d;
}

我們分析大神的思路,我先緩緩下跪了!

在后面五毒神掌第二掌會分析。

5 收獲

5.1 一個重要結構的優化

for循環內,通過if跳出的時候,可以優化。

for(int i = 0;i < sizeof(int)*8;i++){if(x){break;	}x /= 10
}
while(x){x /= 10
}

5.2 去掉“中間商”的方法

對于一些共性的東西,不再單獨列出中間結果,直接得到最終答案

5.3 算法的本質是數學問題

這個數學表達式其實是這么來的

  • 先分治,拆解為數字+權重的形式,本質是硬件思維
  • 再調換數字的權重
    在這里插入圖片描述

至于最終的表達式,需要一點點優化過來。

我們需要知道,對于int x;

  • 求最低位的數字:x % 10
  • 降維,降低數量級:x / 10(利用int直接抹掉小數點)

第一次的算法(使用偽代碼)

while(遍歷每一位的數字){number[i] = x % 10;x /= 10;
}

這是很容易想到的,那么,我們保存了每一位數字,怎么保存它的權重?真的有必要保存權重嗎?
顯然沒有必要,我們試一下就知道,可以直接一邊處理舊數字,一邊計算新數字

newX = 0;
while(遍歷每一位的數字){number[i] = x % 10;x /= 10;newX = newX*10 + number[i];
}

這已經是最小單元,沒法解釋,自己試一下吧。

然后你會發現number[i]是多余的,并且遍歷的條件就是x != 0

long long newX = 0;
while(x != 0){newX = newX*10 + x % 10;x /= 10;
}

至于為什么用long long,這叫先假想結果,因為結果會溢出,所以只能用long long了。

5.4 一些衍生的題目

5.4.1 求整數位數

所有整數均可。

int reverse(int x) {int count = 0;while (x){x /= 10;count++;}return count;
}

5.4.2 求整數的每一位

void reverse(int x) {int count = 0;int xTemporary = x;while (xTemporary){xTemporary /= 10;count++;}int *everyNumber = new int[count];for (int i = 0; i < count; i++) {everyNumber[i] = x % 10;x /= 10;}for (int i = 0; i < count; i++) {cout << everyNumber[i] << endl;}
}

注意

char與int轉換,記得差一個'0'

int i = 4;
char a = i + '0';
cout << a << endl;

6 五毒神掌

五毒神掌是什么?

關注代碼邏輯和結構層面的細節

目標導向,一天一個,完全搞定300題

6.1 第一掌

  1. 先正確理解題目
  2. 自己想,5分鐘想出來就寫
  3. 想不出來,就直接看世界大神答案,并且理解
  4. 然后大致理解背下來(理解代替記憶,如果不理解,就先記憶,多用用就理解了)
  5. 邊抄邊背的方式寫代碼

自己的思路不能只有一種,每種都要嘗試。

重點關注邏輯!畫圖+手算分析

6.1.1 自己思考的過程

題目很簡單,就是整數反轉,需要注意

  • 正負數問題
  • 反轉后溢出問題:用long long存儲

在這里插入圖片描述
之后用幾個數字試一試,研究一下數學公式,先寫正確,再不斷優化。

int reverse(int x) {long long xNew = 0;while (x != 0) {xNew = xNew * 10 + x % 10;x /= 10;}return (xNew < INT_MIN || xNew > INT_MAX) ? 0 : xNew;
}

6.1.2 大神的代碼

public int reverse(int x)
{int result = 0;while (x != 0){int tail = x % 10;int newResult = result * 10 + tail;if ((newResult - tail) / 10 != result){ return 0; }result = newResult;x = x / 10;}return result;
}

基于我的思路,如果可能溢出,就直接使用更大的容器取存儲數據,然后看看有沒有超過小容器的值,那么,如果沒有更大的容器,又該怎么辦?

沒有大容器,那就用2個小容器,比較新值和舊值。

對于重點公式x1新 = x1舊 * 10 + x % 10,我們知道,在數學公式中,進行等價變形,等式應該相等,也就是等式(x1新 - x%10) / 10 = x1舊成立。

但是對于計算機不同,如果第一個公式計算過程有溢出,就會丟失數據,那么第二個公式就不成立

這也就是我們判斷的重點:If overflow exists, the new result will not equal previous one.

如果溢出存在,那么,使用新值運算反過來得到的舊值,就不是原來的那個舊值。

代碼如下:

int reverse(int x) {int xNew1 = 0;  // 舊值int xNew2 = 0;	// 新值while (x) {xNew2 = xNew1 * 10 + x % 10;if ((xNew2 - x % 10) / 10 != xNew1)return 0;xNew1 = xNew2;x /= 10;}return xNew2;
}

事實上,在Leetcode編譯器,上面的寫法是錯誤的!

新的收獲:使用經典的測試用例

不得不說……任何的算法,在使用大量測試用例測試之前,都不一定完美,例如上面的算法,如果使用INT_MAX作為測試用例,對于能夠進行溢出檢測的嚴格編譯器來說,會出現報錯(不過C++編譯器一般不檢測……),那么,報錯的原因是什么

我們看一下xNew2 = xNew1 * 10 + x % 10;,試想一下,我們剛才假定這個過程中,編譯器是允許溢出后直接截斷,但不會報錯,那么現在,我們假定,編譯器不允許溢出的發生,我們又該怎么辦?

【思維修煉】“治未病”思想:在問題發生之前處理掉

對于xNew2 = xNew1 * 10 + x % 10;,我們需要在溢出發生前,就檢測出來,因此有以下程序

int reverse(int x) {int xNew = 0;while(x != 0){if(xNew > INT_MAX/10 || xNew < INT_MIN/10)	return 0;xNew = xNew * 10 + x % 10;x /= 10;}return xNew;}

更嚴格來說,是不是需要把x % 10也“治未病”呢?顯然不需要,因為不存在一個數字,乘10后沒有溢出,但是再+1就溢出了

思考:為什么不是>=

因為,對于極限數字214748364(也就是INT_MAX / 10),乘10之后,再加上x % 10是不可能溢出的(可以想象,如果溢出,那x % 10的結果需要 >7,那么,在這個數反轉之前,就已經溢出了,所以不可能)。

經典測試案例 + 嚴格編譯器 = 優秀算法

對于經典測試案例,例如本題,可以有

123
-123
INT_MAX
INT_MIN
1230000

這些提交前的測試案例,足夠描述各種情況了。

6.1.3 小結

  1. 1個大容器與2個小容器
  2. 算法與數學公式

6.2 第二掌

把大神的代碼完全不看的情況下寫出來。

  • 搞定

自己的代碼,多種寫法,不斷優化到極致。

6.3 第三掌

過了24 小時的時間以后,再次重復做題

不同解法的熟練程度 ——> 專項練習

  • 新的收獲

6.3.1 整數反轉圖解——安檢排隊模型

在這里插入圖片描述
如果你從動態的角度去看一下,是不是像一個U型排隊區人員流動的樣子?

想一想你過安檢排隊的情形。
在這里插入圖片描述
怎么樣,是不是瞬間記住了這個整數反轉模型

int reverse(int x) {int xNew = 0;while(x){if(xNew > INT_MAX/10 || xNew < INT_MIN/10)	return 0; // 預測xNew = xNew*10 + x%10;x /= 10;}return xNew;}

通過預測提高性能

這是偉大計算機思想之一,應用廣泛,例如指令操作的分支預測,在本題中,溢出的檢測就使用了預測思想

6.4 第四掌

過了一周之后: 反復回來練習相同的題目

6.5 第五掌

面試前一周恢復性的訓練,所有題目全都刷一遍

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/384712.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/384712.shtml
英文地址,請注明出處:http://en.pswp.cn/news/384712.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【匯編語言】(x86)test與跳轉指令(je jle jge jg jl……)組合的含義

在x86指令集中&#xff0c;經常遇到test指令與條件跳轉指令組合&#xff0c;這是什么含義呢&#xff1f; 博主表示&#xff0c;查了很多資料也沒人完全說清楚…… 這里只用最簡單的&#xff0c;抽象層次進行說明&#xff0c;不講原理。 舉例 test edx,edx jle 某地址含義是&…

【藍橋杯】BASIC-8 回文數(2020-06-08)

題目 試題 基礎練習 回文數 資源限制 時間限制&#xff1a;1.0s 內存限制&#xff1a;512.0MB 問題描述   1221是一個非常特殊的數&#xff0c;它從左邊讀和從右邊讀是一樣的&#xff0c;編程求所有這樣的四位十進制數。    輸出格式   按從小到大的順序輸出滿足條件的…

【算法訓練】Leetcode 1295. 統計位數為偶數的數字(2020.06.09 )

1 題目 1295. 統計位數為偶數的數字 給你一個整數數組 nums&#xff0c;請你返回其中位數為 偶數 的數字的個數。 示例 1&#xff1a; 輸入&#xff1a;nums [12,345,2,6,7896] 輸出&#xff1a;2 解釋&#xff1a; 12 是 2 位數字&#xff08;位數為偶數&#xff09; 345 …

Vivado設置指定源文件進行RTL優化

像VS編譯器設置啟動項一樣&#xff0c;Vivado中&#xff0c;也有類似設計&#xff0c;可以看到&#xff0c;當前選中的是ALU&#xff0c;那么進行RTL優化的時候&#xff0c;會優化RTL的結果&#xff0c;而不是別的&#xff0c;如何改成別的&#xff1f; 在某文件上右鍵單擊選擇…

【完整流程】用VSCode替換Vivado默認編輯器

本文樓主找了很多資料&#xff0c;選出了最有用的資料&#xff0c;按照教程走&#xff0c;就可以順利搞定&#xff0c;先給出畫面 很酷很方便&#xff0c;同時還有 自動補全檢測錯誤列選自動生成仿真測試文件 等重要功能 Vivado原來的編輯器是這樣的…… 關鍵是&#xff0c…

IEDA中JavaDoc的自動生成、手動生成,以及生成html文檔

1 自動生成類的注釋 JavaDoc就是java特有的一種注釋。 1.1 配置 首先&#xff0c;IDEA點擊File-->Settings 然后Editor-->File and Code Templates-->Class 之后在這地方&#xff0c;添加一些代碼 /** * ${description} * * <p> * 創建日期&#xff1a;$…

【java】父類與子類的引用賦值關系

理清楚4個目標 父類引用&#xff08;“名”&#xff09;父類對象&#xff08;“實”&#xff09;子類引用子類對象 理清楚幾個操作 // 父類 public class parent{}// 子類 public class sun{}父類引用指向父類對象 parent p1 new parent();子類引用指向子類對象 son s1 …

IDEA自動生成 構造方法 get set方法

對于一個類&#xff0c;創建好成員變量后 右鍵單擊&#xff0c;選中Generate 然后 這幾個依次是 構造方法getsetget和set 我們可以選中一個&#xff0c;然后選中要生成的變量&#xff0c;點擊OK 這樣就可以自動生成 構成方法get方法set方法

IDEA快速修改類名和文件名

在你要修改的類名上&#xff0c;選中類名&#xff0c;然后 右鍵單擊選中Refactor選中Rename 也可以使用快捷鍵 Win用戶是Shift F6

java中 靜態方法與成員方法何時使用

靜態方法 不操作成員變量&#xff0c;可以直接調用 是用來直接對傳入的數據進行操作的 成員方法 需要操作對象的成員變量的 區別 靜態方法&#xff0c;不能操作成員變量&#xff0c;只是一個操作成員方法&#xff0c;可以操作成員變量&#xff0c;不僅僅是操作&#xff0…

通過編程解決問題的正確思路

1. 先知道我們面對一個怎樣的問題 2. 考慮這個問題在現實生活中&#xff0c;我們要用怎樣的方式去解決 3. 從現實到計算機&#xff0c;如何用編程的思路解決 4. 實現&#xff0c;編碼和測試 5. 迭代 現實問題自然語言解決方案機器語言解決方案編碼實現測試迭代

數據庫設計的核心原則 外鍵的設計 提高插入數據速度

大道至簡&#xff1a;數據庫設計的核心原則 數據庫設計&#xff0c;不得不承認&#xff0c;有很多專業化的理論知識&#xff0c;但是對于初學者來說&#xff0c;只需要大道至簡的原則就可以了。 能不重復的就不重復&#xff0c;太重復的就拆開&#xff0c;使用指定數據做識別…

MySQL提高插入數據的效率(結合JDBC)

0 解決問題最佳途徑&#xff1a;直接找官方 先說明的是&#xff0c;有問題直接去找官方文檔&#xff0c;而不應該去百度搜索&#xff0c;您很容易體驗到&#xff0c;搜索引擎很難快速找到真正對您有價值的解決方案&#xff0c;而官方文檔是最快捷的途徑。 本篇也是基于官方文…

【計算機心理學】先設計再實現 在實現中完善設計

先設計再實現 在物理學中&#xff0c;通常都是先理論證明觀點&#xff0c;再進行實踐&#xff0c;然后&#xff0c;再有世界各地的科學家根據理論進行實驗&#xff0c;以證明觀點正確。 在計算機軟件開發&#xff0c;硬件開發等&#xff0c;都講求先邏輯抽象設計&#xff0c;…

【FPGA VerilogHDL】第一次嘗試:LED燈基礎實驗

0 實驗環境 0.1 軟件環境 ISE 14.7win10vivado 2017.4 0.2 硬件設備 ISE適用的FPGA開發板&#xff1a;ALINK AX309 1 需求 能夠靈活控制4個LED燈 2 Verilog實現 timescale 1ns / 1ps // // Create Date: 14:18:20 08/08/2020 // Module Name: led // Revision…

使用ISE一鍵生成bit文件

我們知道&#xff0c;這幾個&#xff0c;在第一次做好源文件之后&#xff0c;需要一個個進行右鍵單擊-->run&#xff0c;以發現錯誤。 但是之后的調試&#xff0c;只要一點點變化&#xff0c;哪怕是注釋變化&#xff0c;都需要重新run3次&#xff0c;太麻煩了。 不過經過實…

【FPGA Verilog】實驗二:key按鍵基礎實驗

只說一下經驗和教訓 1 必須按照設計流程走 不要因為實驗簡單&#xff0c;就直接進行綜合&#xff0c;比如按照 設計編碼RTL優化仿真綜合管腳分配&#xff0c;實現下載 一定要按照這個步驟來。 2 必須先查看開發板說明文檔 開始出了一個令人困惑的問題&#xff0c;后來發現…

【Java】字符串轉換為數字:Integer的parseInt方法

Java官方文檔[1]的解釋 public static int parseInt?(String s) throws NumberFormatException Parses the string argument as a signed decimal integer. The characters in the string must all be decimal digits, except that the first character may be an ASCII minus…

在win10上使用Vmware安裝Mac OS

安裝macOS 如何在Windows上VMware上安裝macOS Catalina 10.15 做一些提示&#xff1a; 如果您在第一次啟動mac的時候&#xff0c;在出現【語言選擇】之前&#xff0c;出現了連接藍牙內容。 您可以將教程中【修改為win10 x64】那一步跳過&#xff0c;請注意&#xff0c;如果您…

JDBC 防御性編程

防御性編程&#xff08;Defensive Programming&#xff09; 什么是Defensive Programming[1]&#xff1f; 原文&#xff1a;Defensive programming is a form of defensive design intended to ensure the continuing function of a piece of software under unforeseen circu…