算法心經——分治,抽屜原理

分治,抽屜原理

引例1:給定一個長度爲 n 的整數數組 nums,數組中所有的數字都在 0∼n−1 的範圍內。數組中某些數字是重複的,但不知道有幾個數字重複了,也不知道每個數字重複了幾次。請找出數組中任意一個重複的數字。

引例2:給定一個長度爲 n+1 的數組nums,數組中所有的數均在 0∼n 的範圍內,其中 n≥0。請找出數組中任意一個重複的數,但不能修改輸入的數組。

我們先看第一題,最簡單的做法自然是遍歷一遍挨個找。但這種做法不是很高明。最好的做法是這樣的:
給出n個數字,而且範圍在0~n-1。我們可以這麼做,把值爲0~n-1的n個數放到與他們值對應的位置上去。
比如數字0,應該放到索引爲0的位置中;數字1應該放到索引爲1的位置中。當發現要放置的位置已經存在這個數了,自然就找到了重複的數字。
所以我們要開闢一個大小爲n的空位數組home,將題目中給的nums數組中的元素在home中爲它找一個家。
class Solution {
public:
	int duplicateInArray(vector<int>& nums) {
		vector<int> home(nums.size(), -1);
		for (int i = 0; i < nums.size(); i++) {
			if (home[nums[i]] == -1) {//房子是空的時候給數字安家
				home[nums[i]] = nums[i];
			}
			else {
				return nums[i];
			}
		}
	}
};
仔細分析一下,上面的home數組是多餘的。這是因爲我們每一次都會將一個數送回家,所以我們關心的只是將數字x送到x索引處,而不關心這個數字存在於哪個數組。所以完全可以在nums數組上進行操作,那麼就要思考,什麼時候我們才需要把這個數字送回家呢?僅當家裏存在另一個數的時候才能送它回家。

形象一點的說,上面給的代碼是“分房子”,而這次給的代碼是“換房子”,只有兩家戶主都不對的才能互換,而不能把正確的戶主趕走。這樣我們每一次可以將一個數字送回家,最多做n次。
class Solution {
public:
	int duplicateInArray(vector<int>& nums) {
		for (int i = 0; i < nums.size(); i++) {//i既指向家,又指向原數組的元素
			while (nums[nums[i]] != nums[i]) swap(nums[nums[i]], nums[i]);//兩個位置都不對的才能互換
			if (nums[i] != i)return nums[i];//檢查是否爲nums[i]安好了家
		}
	}
};
現在看到引例2,現在不允許交換位置了,那麼需要怎麼做呢?
同樣利用安家的思想,現在有1~n個房間,但是卻有n+1個數字,所以必然會多出來一個,多出來的這個數字顯然就是重複的。這就是抽屜原理。要找出那個重複的數字,就要用到分治的思想。

我們首先將所有的房間號一分爲二,分爲[0,mid], [mid+1,n-1],然後遍歷nums數組,對於任意一個數字x,只要0 <= x <= mid,則它的家一定在左邊,sumLeft++,一直遍歷完成,如果sumLeft > mid + 1,說明多出來的這個數想分配到左邊,於是再將[0,mid]一分爲二,分爲[0,mid/2], [mid/2+1,mid],在利用這個思想檢查,最終剩下的那個房子對應的編號就是多餘的數字。
class Solution {
public:
	int duplicateInArray(vector<int>& nums) {
		int left = 0, right = nums.size() - 1;
		while (left < right) {
			int mid = (left + right) / 2;
			int sumleft = 0;
			for (int i = 0; i < nums.size(); i++) {
				if (nums[i] >= left && nums[i] <= mid) {
					sumleft++;
				}
			}
			if (sumleft > mid - left + 1) {
				right = mid;
			}
			else {
				left = mid + 1;
			}
		}
		return right;
	}
};
THE END
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章