《关于我横扫一线厂的那些面经》拼多多Java岗(附答案)
前言
去年年底面試了多多買菜,有圖為證,現(xiàn)整理面經(jīng),希望各位不要覺(jué)得太遲(這該死的拖延癥😂)。
周日晚上8點(diǎn)視頻面試的拼多多,結(jié)果人家全員加班中,辦公室中都是人,所以大家去多多前還是思考下把。🤣
?
問(wèn)題1.arraylist線程是否安全,具體體現(xiàn)在哪行?
答:線程不安全,具體表現(xiàn)新增元素的賦值操作elementData[size++]=e。
?
我們先來(lái)看下什么是線程的安全性:
線程安全就是多線程訪問(wèn)時(shí)對(duì)數(shù)據(jù)進(jìn)行了加鎖機(jī)制(樂(lè)觀鎖或悲觀鎖),只有一個(gè)線程能夠正常訪問(wèn),其他線程不能進(jìn)行訪問(wèn)直到該線程讀取完。不會(huì)出現(xiàn)數(shù)據(jù)不一致或者數(shù)據(jù)污染。
線程不安全就是多線程訪問(wèn)時(shí)不提供數(shù)據(jù)訪問(wèn)保護(hù),有可能出現(xiàn)多個(gè)線程先后更改數(shù)據(jù),所以得到的數(shù)據(jù)是臟數(shù)據(jù)。
?
如圖,AbstractList下面有兩個(gè)類,一個(gè)是線程不安全ArrayList,另外一個(gè)是線程安全vector。
?
從源碼的角度來(lái)看,因?yàn)閂ector的方法前加了synchronized 關(guān)鍵字,也就是同步的意思,所以其是線程安全的。
?
所以我們看到這兩個(gè)的區(qū)別:
Vector:線程安全,但是性能低。
ArrayList:線程不安全,但是性能高,高效。
?
所以自古魚和熊掌不可兼得。
?
我們來(lái)看下具體的代碼,從下圖我們可以得出其添加元素時(shí)候的兩步走:
1. 在 Items[Size] 的位置存放此元素;
2. 增大 Size 的值。
?
我們來(lái)思考下:
在單線程運(yùn)行的情況下,如果 Size = 0,添加一個(gè)元素后,此元素在位置 0,而且 Size=1,一切都是正常的;
而如果是在多線程情況下,比如有兩個(gè)線程,線程 A 先將元素存放在位置 0,此時(shí)size并沒(méi)有加一,但是此時(shí) CPU 調(diào)度線程A暫停,線程 B 得到運(yùn)行的機(jī)會(huì)。
線程B也向此 ArrayList 添加元素,因?yàn)榇藭r(shí) Size 仍然等于 0 (注意哦,我們假設(shè)的是添加一個(gè)元素是要兩個(gè)步驟哦,而線程A僅僅完成了步驟1),
所以線程B也將元素存放在位置0,此時(shí)size也沒(méi)有加1。然后線程A和線程B都繼續(xù)運(yùn)行,都增加 Size 的值,此時(shí)size等于2。
那好,現(xiàn)在我們發(fā)現(xiàn)問(wèn)題了,元素實(shí)際上只有一個(gè),存放在位置 0,而 Size 卻等于 2。這就是“線程不安全”了。
?
看下面的代碼,在多線程并發(fā)情況下,提示了報(bào)錯(cuò)信息,程序報(bào)了并發(fā)修改異常ConcurrentModificationException,我們也可以通過(guò)看下ArrayList底層中add()方法,是沒(méi)有加鎖的操作,當(dāng)多個(gè)線程共享一份資源時(shí),可能發(fā)生線程問(wèn)題;arrayList的add()方法是沒(méi)有加鎖的。
public static void main(String[] args) throws InterruptedException {List<Integer> list = new ArrayList<>();ExecutorService threadPool = Executors.newFixedThreadPool(30);for (int i = 1; i <= 30; i++) {int finalI = i;threadPool.execute(() -> {list.add(finalI);System.out.println(list);});}threadPool.shutdown();}?
如果我們想要不報(bào)錯(cuò),可以將list轉(zhuǎn)化為線程安全的集合,使用Collections工具類的synchronizedList方法,來(lái)將其轉(zhuǎn)化線程安全的。
public static void main(String[] args) throws InterruptedException {List<Integer> list = Collections.synchronizedList(new ArrayList<>());//此處只是案例demo,真實(shí)使用線程池不建議用這種方式創(chuàng)建ExecutorService threadPool = Executors.newFixedThreadPool(30);for (int i = 1; i <= 30; i++) {int finalI = i;threadPool.execute(() -> {list.add(finalI);System.out.println(list);});}threadPool.shutdown();}?
問(wèn)題2.hashmap為什么用紅黑樹(shù),不要AVL樹(shù),或B+樹(shù)?
答:因?yàn)锳VL樹(shù)比紅黑樹(shù)保持更加嚴(yán)格的平衡,是以更多旋轉(zhuǎn)操作導(dǎo)致更慢的插入和刪除為代價(jià)的樹(shù),B+樹(shù)所有的節(jié)點(diǎn)擠在一起,當(dāng)數(shù)據(jù)量不多的時(shí)候會(huì)退化成鏈表。
?
AVL樹(shù)
平衡二叉搜索樹(shù)(Self-balancing binary search tree)又被稱為AVL樹(shù),且具有以下性質(zhì):它是一 棵空樹(shù)或它的左右兩個(gè)子樹(shù)的高度差的絕對(duì)值不超過(guò)1,并且左右兩個(gè)子樹(shù)都是一棵平衡二叉樹(shù)。
某在AVL樹(shù)中查找通常更快,但這是以更多旋轉(zhuǎn)操作導(dǎo)致更慢的插入和刪除為代價(jià)的。因此,如果希望查找次數(shù)主導(dǎo)樹(shù)的更新次數(shù),請(qǐng)使用AVL樹(shù)。
下面兩張圖都是平衡二叉搜索樹(shù)。
? ? ? ? ? ? ? ? ? ? ? ? ? ?
?
那我們來(lái)看下不是平衡二叉搜索樹(shù)長(zhǎng)什么樣子?從下圖可以看到C節(jié)點(diǎn)的左子樹(shù)有F,L,M,即為兩層,但是C節(jié)點(diǎn)的右子樹(shù)并沒(méi)有,他們相差了2,所以并不是平衡二叉搜索樹(shù)。
左子樹(shù)的左子樹(shù)插入結(jié)點(diǎn) (左左)
?
右子樹(shù)的右子樹(shù)插入節(jié)點(diǎn) (右右)
?
左子樹(shù)的右子樹(shù)插入節(jié)點(diǎn) (左右)
?
右子樹(shù)的左子樹(shù)插入節(jié)點(diǎn) (右左)
B+樹(shù)
B+樹(shù)的非葉子結(jié)點(diǎn)不存儲(chǔ)數(shù)據(jù),所以每個(gè)結(jié)點(diǎn)能存儲(chǔ)的關(guān)鍵字更多。所以B+樹(shù)更能應(yīng)對(duì)大量數(shù)據(jù)的情況。jdk1.7中的HashMap本來(lái)是數(shù)組+鏈表的形式,鏈表由于其查找慢的特點(diǎn),所以需要被查找效率更高的樹(shù)結(jié)構(gòu)來(lái)替換。
如果用B+樹(shù)的話,在數(shù)據(jù)量不是很多的情況下,數(shù)據(jù)都會(huì)“擠在”一個(gè)結(jié)點(diǎn)里面。這個(gè)時(shí)候遍歷效率就退化成了鏈表
?
一個(gè)m階的B樹(shù)具有如下幾個(gè)特征:
1.根結(jié)點(diǎn)至少有兩個(gè)子女。
2.每個(gè)中間節(jié)點(diǎn)都至少包含ceil(m / 2)個(gè)孩子,最多有m個(gè)孩子。
3.每一個(gè)葉子節(jié)點(diǎn)都包含k-1個(gè)元素,其中 m/2 <= k <= m。
4.所有的葉子結(jié)點(diǎn)都位于同一層。
5.每個(gè)節(jié)點(diǎn)中的元素從小到大排列,節(jié)點(diǎn)當(dāng)中k-1個(gè)元素正好是k個(gè)孩子包含的元素的值域分劃。
?
?
問(wèn)題3.ConcurrentHashMap為什么線程安全?哪些點(diǎn)保證了?
答:數(shù)組初始化的時(shí)候自旋來(lái)保證一定可以初始化成功,然后通過(guò) CAS 設(shè)置 SIZECTL 變量的值,來(lái)保證同一時(shí)刻只能有一個(gè)線程對(duì)數(shù)組進(jìn)行初始化,CAS 成功之后,還會(huì)再次判斷當(dāng)前數(shù)組是否已經(jīng)初始化完成,如果已經(jīng)初始化完成,就不會(huì)再次初始化;
新增槽點(diǎn)時(shí)通過(guò)自旋保證一定新增成功,然后通過(guò)CAS來(lái)新增,如果遇到槽點(diǎn)有值,通過(guò)鎖住當(dāng)前槽點(diǎn)或紅黑樹(shù)的根節(jié)點(diǎn);
擴(kuò)容時(shí)通過(guò)鎖住原數(shù)組的槽點(diǎn),設(shè)置轉(zhuǎn)移節(jié)點(diǎn),以及自旋等操作來(lái)保證線程安全。
?
ConcurrentHashMap 在 put 方法上的整體思路:
1. 如果數(shù)組為空,初始化,初始化完成之后,走 2;
2. 計(jì)算當(dāng)前槽點(diǎn)有沒(méi)有值,沒(méi)有值的話,cas 創(chuàng)建,失敗繼續(xù)自旋(for 死循環(huán)),直到成功,槽點(diǎn)有值的話,走 3;
3. 如果槽點(diǎn)是轉(zhuǎn)移節(jié)點(diǎn)(正在擴(kuò)容),就會(huì)一直自旋等待擴(kuò)容完成之后再新增,不是轉(zhuǎn)移節(jié)點(diǎn)走4;
4. 槽點(diǎn)有值的,先鎖定當(dāng)前槽點(diǎn),保證其余線程不能操作,如果是鏈表,新增值到鏈表的尾部,如果是紅黑樹(shù),使用紅黑樹(shù)新增的方法新增;
5. 新增完成之后 check 需不需要擴(kuò)容,需要的話去擴(kuò)容。
具體源碼如下:
數(shù)組初始化時(shí)的線程安全
數(shù)組初始化時(shí),首先通過(guò)自旋來(lái)保證一定可以初始化成功,然后通過(guò) CAS 設(shè)置 SIZECTL 變量的值,來(lái)保證同一時(shí)刻只能有一個(gè)線程對(duì)數(shù)組進(jìn)行初始化,CAS 成功之后,還會(huì)再次判斷當(dāng)前數(shù)組是否已經(jīng)初始化完成,如果已經(jīng)初始化完成,就不會(huì)再次初始化,通過(guò)自旋 + CAS + 雙重 check等手段保證了數(shù)組初始化時(shí)的線程安全,源碼如下:
? ? ? ? ? ? ? ? ? ?
?新增槽點(diǎn)值時(shí)的線程安全
?此時(shí)為了保證線程安全,做了四處優(yōu)化:
- ?通過(guò)自旋死循環(huán)保證一定可以新增成功。
- ?當(dāng)前槽點(diǎn)為空時(shí),通過(guò) CAS 新增。
- ?當(dāng)前槽點(diǎn)有值,鎖住當(dāng)前槽點(diǎn)。
- ?紅黑樹(shù)旋轉(zhuǎn)時(shí),鎖住紅黑樹(shù)的根節(jié)點(diǎn),保證同一時(shí)刻,當(dāng)前紅黑樹(shù)只能被一個(gè)線程旋轉(zhuǎn)
擴(kuò)容中時(shí)的線程安全
- 拷貝槽點(diǎn)時(shí),會(huì)把原數(shù)組的槽點(diǎn)鎖住;
- 拷貝成功之后,會(huì)把原數(shù)組的槽點(diǎn)設(shè)置成轉(zhuǎn)移節(jié)點(diǎn),這樣如果有數(shù)據(jù)需要 put 到該節(jié)點(diǎn)時(shí),發(fā)現(xiàn)該槽點(diǎn)是轉(zhuǎn)移節(jié)點(diǎn),會(huì)一直等待,直到擴(kuò)容成功之后,才能繼續(xù) put,可以參考 put 方 法中的 helpTransfer 方法;
- 從尾到頭進(jìn)行拷貝,拷貝成功就把原數(shù)組的槽點(diǎn)設(shè)置成轉(zhuǎn)移節(jié)點(diǎn)。
- 等擴(kuò)容拷貝都完成之后,直接把新數(shù)組的值賦值給數(shù)組容器,之前等待 put 的數(shù)據(jù)才能繼續(xù)?put。
? ? ? ? ? ? ? ? ? ??
// 擴(kuò)容主要分 2 步,第一新建新的空數(shù)組,第二移動(dòng)拷貝每個(gè)元素到新數(shù)組中去// tab:原數(shù)組,nextTab:新數(shù)組private final void transfer (Node < K, V >[]tab, Node < K, V >[]nextTab){// 老數(shù)組的長(zhǎng)度int n = tab.length, stride;if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)stride = MIN_TRANSFER_STRIDE; // subdivide range// 如果新數(shù)組為空,初始化,大小為原數(shù)組的兩倍,n << 1if (nextTab == null) { // initiatingtry {@SuppressWarnings("unchecked")Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n << 1];nextTab = nt;} catch (Throwable ex) { // try to cope with OOMEsizeCtl = Integer.MAX_VALUE;return;}nextTable = nextTab;transferIndex = n;}// 新數(shù)組的長(zhǎng)度int nextn = nextTab.length;// 代表轉(zhuǎn)移節(jié)點(diǎn),如果原數(shù)組上是轉(zhuǎn)移節(jié)點(diǎn),說(shuō)明該節(jié)點(diǎn)正在被擴(kuò)容ForwardingNode<K, V> fwd = new ForwardingNode<K, V>(nextTab);boolean advance = true;boolean finishing = false; // to ensure sweep before committing nextTab// 無(wú)限自旋,i 的值會(huì)從原數(shù)組的最大值開(kāi)始,慢慢遞減到 0for (int i = 0, bound = 0; ; ) {Node<K, V> f;int fh;while (advance) {int nextIndex, nextBound;// 結(jié)束循環(huán)的標(biāo)志if (--i >= bound || finishing)advance = false;// 已經(jīng)拷貝完成else if ((nextIndex = transferIndex) <= 0) {i = -1;advance = false;}// 每次減少 i 的值else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,nextBound = (nextIndex > stride ?nextIndex - stride : 0))) {bound = nextBound;i = nextIndex - 1;advance = false;}}// if 任意條件滿足說(shuō)明拷貝結(jié)束了if (i < 0 || i >= n || i + n >= nextn) {int sc;// 拷貝結(jié)束,直接賦值,因?yàn)槊看慰截愅暌粋€(gè)節(jié)點(diǎn),都在原數(shù)組上放轉(zhuǎn)移節(jié)點(diǎn),所以拷貝完成的節(jié)點(diǎn)的數(shù)據(jù)一定不會(huì)再發(fā)生變化。// 原數(shù)組發(fā)現(xiàn)是轉(zhuǎn)移節(jié)點(diǎn),是不會(huì)操作的,會(huì)一直等待轉(zhuǎn)移節(jié)點(diǎn)消失之后在進(jìn)行操作。// 也就是說(shuō)數(shù)組節(jié)點(diǎn)一旦被標(biāo)記為轉(zhuǎn)移節(jié)點(diǎn),是不會(huì)再發(fā)生任何變動(dòng)的,所以不會(huì)有任何線程安全的問(wèn)題// 所以此處直接賦值,沒(méi)有任何問(wèn)題。if (finishing) {nextTable = null;table = nextTab;sizeCtl = (n << 1) - (n >>> 1);return;}if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)return;finishing = advance = true;i = n; // recheck before commit}} else if ((f = tabAt(tab, i)) == null)advance = casTabAt(tab, i, null, fwd);else if ((fh = f.hash) == MOVED)advance = true; // already processedelse {synchronized (f) {// 進(jìn)行節(jié)點(diǎn)的拷貝if (tabAt(tab, i) == f) {Node<K, V> ln, hn;if (fh >= 0) {int runBit = fh & n;Node<K, V> lastRun = f;for (Node<K, V> p = f.next; p != null; p = p.next) {int b = p.hash & n;if (b != runBit) {runBit = b;lastRun = p;}}if (runBit == 0) {ln = lastRun;hn = null;} else {hn = lastRun;ln = null;}// 如果節(jié)點(diǎn)只有單個(gè)數(shù)據(jù),直接拷貝,如果是鏈表,循環(huán)多次組成鏈表拷貝for (Node<K, V> p = f; p != lastRun; p = p.next) {int ph = p.hash;K pk = p.key;V pv = p.val;if ((ph & n) == 0)ln = new Node<K, V>(ph, pk, pv, ln);elsehn = new Node<K, V>(ph, pk, pv, hn);}// 在新數(shù)組位置上放置拷貝的值setTabAt(nextTab, i, ln);setTabAt(nextTab, i + n, hn);// 在老數(shù)組位置上放上 ForwardingNode 節(jié)點(diǎn)// put 時(shí),發(fā)現(xiàn)是 ForwardingNode 節(jié)點(diǎn),就不會(huì)再動(dòng)這個(gè)節(jié)點(diǎn)的數(shù)據(jù)了setTabAt(tab, i, fwd);advance = true;}// 紅黑樹(shù)的拷貝else if (f instanceof TreeBin) {// 紅黑樹(shù)的拷貝工作,同 HashMap 的內(nèi)容,代碼忽略// 在老數(shù)組位置上放上 ForwardingNode 節(jié)點(diǎn)setTabAt(tab, i, fwd);advance = true;}}}}}}?
問(wèn)題4.剛才說(shuō)到分布式鎖,談?wù)勗O(shè)計(jì)思路和方案
答:主要根據(jù)具體的業(yè)務(wù)場(chǎng)景展開(kāi)描述(這邊各個(gè)項(xiàng)目不一樣,就不展開(kāi)說(shuō)了),主要是引入redis實(shí)現(xiàn)的分布式鎖,應(yīng)該保證互斥性(在任何時(shí)候只有一個(gè)客戶端持有鎖,使用setnx),不能死鎖(設(shè)置過(guò)期時(shí)間),保證上鎖和解鎖是同一個(gè)客戶端(設(shè)置不同的value值),業(yè)務(wù)時(shí)間太長(zhǎng),導(dǎo)致鎖過(guò)期(設(shè)置看門狗,自動(dòng)續(xù)鎖),鎖的重入性(使用redis的hset)。
?
如果在一個(gè)分布式系統(tǒng)中,我們從數(shù)據(jù)庫(kù)中讀取一個(gè)數(shù)據(jù),然后修改保存,這種情況很容易遇到并發(fā)問(wèn)題。因?yàn)樽x取和更新保存不是一個(gè)原子操作,在并發(fā)時(shí)就會(huì)導(dǎo)致數(shù)據(jù)的不正確。如果是單機(jī)應(yīng)用,直接使用本地鎖就可以避免。如果是分布式應(yīng)用,應(yīng)用部署在多個(gè)JVM中,沒(méi)有辦法控制,所以引入分布式鎖來(lái)解決。
?
互斥性
127.0.0.1:6379> setnx lock value1 #在鍵lock不存在的情況下,將鍵key的值設(shè)置為value1 (integer) 1 127.0.0.1:6379> setnx lock value2 #試圖覆蓋lock的值,返回0表示失敗 (integer) 0 127.0.0.1:6379> get lock #獲取lock的值,驗(yàn)證沒(méi)有被覆蓋 "value1" 127.0.0.1:6379> del lock #刪除lock的值,刪除成功 (integer) 1 127.0.0.1:6379> setnx lock value2 #再使用setnx命令設(shè)置,返回0表示成功 (integer) 1 127.0.0.1:6379> get lock #獲取lock的值,驗(yàn)證設(shè)置成功加鎖:使用setnx key value命令,如果key不存在,設(shè)置value(加鎖成功)。如果已經(jīng)存在lock(也就是有客戶端持有鎖了),則設(shè)置失敗(加鎖失敗)。
解鎖:使用del命令,通過(guò)刪除鍵值釋放鎖。
?
不能死鎖
設(shè)置過(guò)期時(shí)間,到點(diǎn)數(shù)據(jù)刪除,避免導(dǎo)致如果一個(gè)客戶端持有鎖的期間突然崩潰了,就會(huì)導(dǎo)致無(wú)法解鎖,則其他人將無(wú)法拿到該鎖,鎖會(huì)一直存在,最終導(dǎo)致出現(xiàn)死鎖的現(xiàn)象。
?
鎖過(guò)期
有效時(shí)間設(shè)置多長(zhǎng),假如我的業(yè)務(wù)操作比有效時(shí)間長(zhǎng),我的業(yè)務(wù)代碼還沒(méi)執(zhí)行完就自動(dòng)給我解鎖了,不就完蛋了嗎。
這個(gè)問(wèn)題就有點(diǎn)棘手了,在網(wǎng)上也有很多討論,第一種解決方法就是靠程序員自己去把握,預(yù)估一下業(yè)務(wù)代碼需要執(zhí)行的時(shí)間,然后設(shè)置有效期時(shí)間比執(zhí)行時(shí)間長(zhǎng)一些,保證不會(huì)因?yàn)樽詣?dòng)解鎖影響到客戶端業(yè)務(wù)代碼的執(zhí)行。但是這并不是萬(wàn)全之策,比如網(wǎng)絡(luò)抖動(dòng)這種情況是無(wú)法預(yù)測(cè)的,也有可能導(dǎo)致業(yè)務(wù)代碼執(zhí)行的時(shí)間變長(zhǎng),所以并不安全。有一種方法比較靠譜一點(diǎn),
就是給鎖續(xù)期。在Redisson框架實(shí)現(xiàn)分布式鎖的思路,就使用watchDog機(jī)制實(shí)現(xiàn)鎖的續(xù)期。當(dāng)加鎖成功后,同時(shí)開(kāi)啟守護(hù)線程,默認(rèn)有效期是30秒,每隔10秒就會(huì)給鎖續(xù)期到30秒,只要持有鎖的客戶端沒(méi)有宕機(jī),就能保證一直持有鎖,直到業(yè)務(wù)代碼執(zhí)行完畢由客戶端自己解鎖,如果宕機(jī)了自然就在有效期失效后自動(dòng)解鎖。
?
保證上鎖和解鎖都是同一個(gè)客戶端
key的值可以根據(jù)業(yè)務(wù)設(shè)置,比如是用戶中心使用的,可以命令為USER_REDIS_LOCK,value可以使用uuid保證唯一,用于標(biāo)識(shí)加鎖的客戶端,保證加鎖和解鎖都是同一個(gè)客戶端。
每次解鎖可以先判斷鎖的value是不是當(dāng)前用戶,如果是,說(shuō)明可以解鎖,如果不是,則不能解鎖,會(huì)導(dǎo)致誤解了其他人的鎖。
?
鎖的重入性
可重入鎖意思是在外層使用鎖之后,內(nèi)層仍然可以使用,那么可重入鎖的實(shí)現(xiàn)思路又是怎么樣的呢?在Redisson實(shí)現(xiàn)可重入鎖的思路,使用Redis的哈希表存儲(chǔ)可重入次數(shù),當(dāng)加鎖成功后,使用hset命令,value(重入次數(shù))則是1。
解鎖時(shí),先判斷可重復(fù)次數(shù)是否大于0,大于0則減一,否則刪除鍵值,釋放鎖資源。
?
問(wèn)題5.算法題
看到這算法題,我笑了,這不是力扣的第一題嗎,哈哈哈,幸好刷過(guò)。簡(jiǎn)單方法大家都會(huì)寫,暴力操作,但是性能有影響,但是評(píng)論區(qū)有為大神寫的很巧妙,我就直接搬過(guò)來(lái)了。
題目:
給定一個(gè)整數(shù)數(shù)組 nums?和一個(gè)整數(shù)目標(biāo)值 target,請(qǐng)你在該數(shù)組中找出 和為目標(biāo)值 的那?兩個(gè)?整數(shù),并返回它們的數(shù)組下標(biāo)。
你可以假設(shè)每種輸入只會(huì)對(duì)應(yīng)一個(gè)答案。但是,數(shù)組中同一個(gè)元素在答案里不能重復(fù)出現(xiàn)。
你可以按任意順序返回答案。
解法:
這個(gè)解法并不是從題目計(jì)算答案,而是從答案出發(fā),看需要什么數(shù)字。
public int[] twoSum(int[] nums, int target) {int[] indexs = new int[2];// 建立k-v ,一一對(duì)應(yīng)的哈希表HashMap<Integer,Integer> hash = new HashMap<Integer,Integer>();for(int i = 0; i < nums.length; i++){if(hash.containsKey(nums[i])){indexs[0] = i;indexs[1] = hash.get(nums[i]);return indexs;}// 將數(shù)據(jù)存入 key為補(bǔ)數(shù) ,value為下標(biāo)hash.put(target-nums[i],i);}// // 雙重循環(huán) 循環(huán)極限為(n^2-n)/2 // for(int i = 0; i < nums.length; i++){// for(int j = nums.length - 1; j > i; j --){// if(nums[i]+nums[j] == target){// indexs[0] = i;// indexs[1] = j; // return indexs;// }// }// }return indexs;}?
總結(jié)
以上是生活随笔為你收集整理的《关于我横扫一线厂的那些面经》拼多多Java岗(附答案)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: BLDC无刷电机驱动板,foc驱动板,有
- 下一篇: 大型银行敏捷DevOps转型之快速启动