leetcode 4——Median of Two Sorted Arrays

    這道題很難,看了別人的代碼都還搞了一天阿。。。

        記錄一下解題思路。

        首先要區分中位數,不是平均數!該題是要找中位數,如果序列是奇數個,那麼直接取中間的,如果是偶數個,那麼取中間的兩個數的平均數,所以,核心問題是,如何把兩個排好的序列整合成一個序列,並且時間複雜度要求低於:o(log(m+n))。

        既然要求算法時間複雜度在log級別,那肯定和二分或者二叉樹之類的帶二分思想的東西有關。

        慢慢分析,假設這裏有兩個數組:

        nums1 [1,3*,4,5];

        nums2 [2,3,6,6,8,9],我們要找的是這兩個數組合起來的中位數,這裏一共有10個數,所以中位數一定是中間兩個數的平均,即中位數把序列劃分成兩部分:{小於中位數的部分}(A),中位數,{大於中位數的部分}(B)

        假設nums1中的3*是B集合中的第一個,即排列後的10個數爲:

        x x x x x | 3* x x x x

        也就是說3*的前面有5個比它小的數,但是昕仔nums1中3*前面只有一個數,不夠,因此,要從nums2中從前往後再取4個,因此:nums2 = [ 2,3,6,6,8*,9],即要把8*前面的數字也放到合併後的A集合中,合併後得到的整個序列爲:1,2,3,5,5,3*,4,5,8,9。但是很明顯,3*不能比A集合中最大的數大,因此3*不可能是B集合中的第一個。(其實如果nums1序列中星號數字的位置確定 i,那麼nums2中星數字的位置 j 也能確定了,j = half - i)。接着剛纔的分析,因爲 8* 的前一個數字(5)大於3*(只需要看前一個數字即可),所以不成立,表明3小了,因此 i 往後移動,那麼 j 就只能往前移動了,y因爲 j = half - i . 

       i 後移 ,j 前移的結果:

       nums1 : [1,3,4*,5]

       nums2:[2,3,6,6*,8,9]

       nums2中6*的前一個數字6大於nums1中的4*,因此還不對,還要 i 往前移動,     

       nums1 : [1,3,4,5*]

       nums2:[2,3,6*,6,8,9]

       nums1中5*的前一個數字4 < nums2中6*

       nums2中6*的前一個數字3 < nums1中5*

       OK。

       因此,前5個數字找到:1,2,3,3,4,後5個數字:5*,6*,6,8,9

       中位數:[max(nums1中5*的前一個數字,nums2中6*的前一個數字)+ min(5*,6*)] / 2。

       但是這個算法描述出來, 雖然可以寫了,但是i 從 0 到 n,寫出來時間複雜度是o(n),高於o(log(n)),因此,還要優化。

       這道題其實就是在找滿足條件的一個數,只是這個規則比較複雜,本質找數依然不變,因此可以用二分查找,之前i是從0到n一個個找,現在 i 從中間開始,使用二分的思想。要讓 i 增加,就把 i 以前的全部砍掉。加了二分查找,就可以將算法的複雜度降低到log級別。

       下面是代碼:

        

class Solution
{
public:
	double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) 
	{
		int total = nums1.size() + nums2.size();
		//0x1是16進制寫法,0x116進制對應的二進制是00000001,如果
		//toal與0x1相與等於1(即total是奇數),那麼執行...
		int* a = new int[nums1.size()];
		for (int i = 0; i < nums1.size(); i++)
			a[i] = nums1.at(i);
		int* b = new int[nums2.size()];
		for (int i = 0; i < nums2.size(); i++)
			b[i] = nums2.at(i);
		if (total & 0x1)
			return findKth(a, nums1.size(), b,nums2.size(),total / 2 + 1);//total / 2 + 1是合併後中間的位置
		else//total是偶數
			return (findKth(a, nums1.size(), b, nums2.size(), total / 2)
				+ findKth(a, nums1.size(), b, nums2.size(), total / 2 + 1)) / 2;
	}
private:
	void earseVector(vector<int>& arr,int time)
	{
		vector<int>::iterator it;
		int m = 0;
		for (it = arr.begin(); it != arr.end();)
		{
			if (m < time)
			{
				
				it = arr.erase(it);    //刪除元素,返回值指向已刪除元素的下一個位置  
				++it;    //指向下一個位置
				m++;
			}
			else
				break;
		}
	}
	double findKth(int a[], int m, int b[], int n, int k)
	{
		//如果a更長,那麼交換一下參數位置,保證m<=n
		if (m > n)
			return findKth(b, n, a, m, k);
		if (m == 0)//特列,如果a數組長度爲0,直接返回b的k-1位置元素
		{
			cout << "return.." << endl;
			return b[k - 1];
		}
		if (k == 1)//特列,如果a,b長度有隻有1,那麼返回a,b中的最小值
		{
			cout << "k==1\n";
			return min(a[0], b[0]);
		}

		//divide k into two parts
		//二分查找,直接從中間找,因爲第一次傳過來的k就是二分之total,如果k再除以2,其實就是較短數組的中間位置了
		//k就是half,所以pb = half - pa
		int pa = min(k / 2, m);//取min表示如果超出m,取m,否則取k/2,邊界處理
		int pb = k - pa;
		if (a[pa - 1] < b[pb - 1])//表明小了,i往前移動
		{
			cout << "e1\n";
			return findKth(a + pa, m - pa, b, n, k - pa);//a+pa其實就是移動數組的首指針,到a+pa位置,也就是說,pa之前的元素不考慮了,cut掉,因此長度變爲m-pa,k也減pa
		}

		else if (a[pa - 1] > b[pb - 1])//表明大了,j往後移動
		{
			cout << "n - pb = " << n - pb << endl;
			return findKth(a, m, b + pb, n - pb, k - pb);
		}

		else
		{
			cout << " xx= :" << a[pa - 1] << endl;
			return a[pa - 1];
		}

	}
};        
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章