找出整數陣列中兩個數差值的絕對值最小

2021-08-31 18:07:18 字數 4008 閱讀 1910

原文**

有乙個整數陣列,請求出兩兩之差絕對值最小的值,記住,只要得出最小值即可,不需要求出是哪兩個數。 下面是乙個結髮了,利用了stl 的sort 來晚成排序。

#include

<

iostream

>

#include

<

vector

>

#include

<

ctime

>

#include

<

cstdio

>

#include

<

cmath

>

using

namespace

std;

#define

n100

#define

m200

intinlinedis(

inta,

intb)

void

print(

int&

a)int

main()if(

!tmp)...

if(tmp

<

min)min

=tmp;

}cout

<<

endl

<<

"minimaldistanceis:

"<<

min<<

endl;

return0;

} 看過有這樣的一種解法,找出陣列中最大值和最小值,把所有的數map 到這個區間的bit 陣列中。 那麼兩個相鄰的bit 為 1 的兩個bit 位置的差值最小的就是這個值。如果有相同的數值,那麼會map 到乙個bit 位上,那麼最小的絕對值就是 0 了。

由於準備找工,最近也用零星的時間做了一些面試題,這道題屬於偏難的那種了,之所以說它偏難,主要是因為按常規思維不容易得到最優解,更重要的是,此題的轉化不容易想到。做過程式設計之美的同學都能了解到,很多題目都需要一種「轉化」的思想,把問題轉化為另一種已知的熟悉的問題去求解,這種思路在《如何解題》也是被提到了很多次,不過實施起來真的不容易。

一 題目描述:

有乙個整數陣列,請求出兩兩之差絕對值最小的值,只要求出最小值即可,不要求求出是哪兩個數。

二 常規思路:

求解此題的尋常思路是什麼?觀察題目我注意到後面強調不要求求出兩個數,那麼最最簡單的o(n^2)的演算法顯然做了很多無用功。嗯,好,既然這個辦法不行想想其他的。對於陣列也就是序列之類的題,有一種很常用的思路那就是預處理。這道題目貌似是可以的。

首先,對陣列進行排序,這個可以在o(n*logn)時間之類解決,然後,有了這個預處理,就會想到,絕對值之差最小值肯定只能發生在預處理的陣列之後的相鄰的元素上,這個是很顯然的事實。那麼我們便可以迴圈一遍陣列,記下兩兩之間絕對值的最小值,那麼所求得到值便是解答,總的時間複雜度是o(n*logn)。仔細想想這種方法,很明顯,排序減小了我們所需要搜尋的解空間,從而達到了減小時間複雜度的目的。不過這個解法仍然不能讓人滿意,因為我們還是浪費時間求出了最終的兩個元素,而題目不要求,所以,這肯定不是最優解。

三 轉化的思想

再仔細觀察題目,我們可以猜到,最優解應該是只求出最小值而不求出具體的元素的,那麼該怎麼做呢?我們可能能想到用輔助陣列,但是卻很難想到怎麼做這個輔助。其實這道題我一直在思考如何通過常規的思維去想到這個最優解,不過我當時沒有想出來,而這才是我寫這篇部落格的原因,即促使我了解並對這種思路印象深刻,不過這可能只適用於解這題或者類似能讓我聯想到這種方法的題,這背後更一般的思維(可以叫做轉化,但是還可以更具體些)我還沒有想到,希望想到的同學聯絡我!。

好了,本題要做的輔助陣列是這樣乙個陣列,設它為bn.原來題目中給定的陣列是an,則bn等於:

b1 = a1 - a2;

b2 = a2 - a3;

b3 = a3 - a4;

......

bn-1 = an-1 - an.

注意,bn的長度是n-1,正好比an要小乙個。聰明的同學看到這個輔助陣列,立馬就能猜到原因了,因為這樣做的話,我們能夠把這道看似無從下手求出最優解的問題轉化為求bn的絕對值最小的最長連續子串行和,因為bn的連續子串行和便是an任意兩數之差(注意,由於題目要求的是絕對值最小,所以求出a1-a2等效於得出a2-a1),例如:

a2 - a5 = b2 + b3 + b4 = a2 - a3 + a3 - a4 + a4 - a5 = a2 - a5

實際上,任何ai - aj(ik=j-1)(k)

這樣的話,我們就成功把問題轉化為了連續子串行問題,不過和我們以前做的最大或最小連續子串行還不完全相同,此處是絕對值最小。那麼怎麼樣的值可能是絕對值最小呢?正數最小或者負數最大,也就是說在數軸上離0更近的數其絕對值更小,基於此我們可以得到如下的方法。

和原來求最大連續子串行和一樣,要用數學歸納法思考,我們直接看歸納基礎,

歸納基礎: 假設已知b1..bk的絕對值最小連續的連續子串行和是min(bk)

我們利用這個求解b(k+1),加入b(k+1)後有可能比min(bk)小的只可能是以b(k+1)結尾的絕對值最小的連續子串行和,如果把這個和min(bk)比較就可以知道是否需要更新min(bk)。所以,我們加強這個歸納基礎。

更強的歸納基礎: 假設已知b1..bk的絕對值最小連續的連續子串行和min(bk),以及以bk結尾的絕對值最小連續子串行和suffix(bk)

有了這個歸納,我們可以去想如何維護這個suffix(bk),目標是使的suffix(b(k+1))仍然是以b(k+1)結尾的最小連續子串行和。如果按照求最小和的思路,那便是只要suffix(bk)是正數便置它為0,因為如果它是正數,那麼在後續求suffix(b(k+1))時就肯定比用0要更大,因為正數會使得整個值變大,而0不會。同樣的道理,我們只要使得求suffix的時候比直接置0更小即可,否則我們可以直接把suffix(b(k+1))置0以獲得更小值。由於我們求的是絕對值最小,直接按最小值的思路是不行的,因為可能某個suffix是暫時求得乙個很小的負數,下次加上某個正數會使得它成為很小的正數,所以不能以正數負數作定論而要以與0的距離。所以我們應該採取比較符號的方法,如果當前suffix和下乙個數的符號相反,那麼可以繼續相加以求得下乙個suffix,因為我們可以獲得絕對值更小的suffix;如果是同號,無論正負一定會比把當前suffix置0更糟糕,因為這將使得下次的suffix在數軸上離0更遠。所以我們維護suffix的公式如下: 

suffix(b(k+1)) = suffix(b(k)) + b(k+1), if (suffix(b(k))*b(k+1)) < 0

suffix(b(k+1)) = 0, if (suffix(b(k))*b(k+1)) ) > 0

這樣我們一直歸納下去,便可以求得最終的min(bn),即可求得解。整個的時間複雜度是o(n),空間複雜度是o(n)。

(update)經過昨晚的思考以及和網友fuwutu童鞋的討論,我此處歸納的過程是有問題的,同時對o(n)演算法的存在性也產生了懷疑。問題的關鍵在於,suffix(k)不僅僅受suffix(k-1)影響,還受前面的suffix影響,例如,明顯的這個例子的答案是0,單純看第二個-1,它的最小的絕對值是選擇自己即-1,然而後續2需要-1選擇和前面的和累加得到-2從而得到最終的答案0,換句話說,從dp的角度講,這道題不滿足無後效性,其某一狀態並不僅僅受前一狀態的影響,即便給你絕對值最小的suffix(k)也無法推出suffix(k+1)。所以,直接歸納是不可行的。那麼能不能對絕對值最小值這個問題進行轉換呢?我想到的是按情況分,例如如果陣列全是正數或者負數,可以直接以最小值或最大值的子串行和演算法求,如果既有正數又有負數,就要用其他方法處理。此處更新在於說明我歸納的錯誤同時不讓更多的同學產生錯誤的想法(對於沒有仔細思考問題的同學 :) ),也非常感謝大家的指正,讓我能夠更加仔細的反思自己的思考過程,從而提高自己,謝謝大家啦 :)。

五 總結

整個思路過程便是這樣,總的來說,這類題目還是很有思考價值的,至少讓我們體會到了各種美,也能深刻領會轉化的意義。

找出整數陣列中兩個數差值的絕對值最小

有乙個整數陣列,請求出兩兩之差絕對值最小的值,記住,只要得出最小值即可,不需要求出是哪兩個數。下面是乙個結髮了,利用了stl 的sort 來晚成排序。include iostream include vector include ctime include cstdio include cmath ...

給定乙個整數陣列,找出其中兩個數相加等於目標值

兩層遍歷,最直觀 時間複雜度 o n n 實現 public int twosum int numbers,int target return result 時間複雜度 o n 第一遍遍歷 將 target a 和i 作為鍵值對,存入hash表,遍歷時間複雜度為o n 第二遍遍歷 查詢在hash表中...

給定乙個整數陣列,找出其中兩個數相加等於目標值

example given nums 2,7,11,15 target 9,because nums 0 nums 1 2 7 9,return 0,1 題目的意思 在無序的陣列中找兩個數,使得這兩個數之和與給定的目標值相等,返回這兩個數的下標。大佬們的做法 下面 不是好的實現方法,可以用hash表...