1. 程式人生 > >LeetCode : Intersection of Two Arrays II 兩個向量去重問題 求交集,並集,差集

LeetCode : Intersection of Two Arrays II 兩個向量去重問題 求交集,並集,差集

std :: set_intersection

 

兩個排序範圍的交叉點(交集)

構造一個從result指向的位置開始的排序範圍,其中包含兩個已排序範圍 [ first1,last1 ) 和 [ first2,last2)的集合交集。

兩組的交集僅由兩組中存在的元素形成。 函式複製的元素始終來自第一個範圍,順序相同。

使用operator <作為第一個版本比較元素,comp作為第二個版本。 如果(!(a <b)&&!(b <a))或 如果(!comp(a,b)&&!comp(b,a)),則認為兩個元素a和b是等價的。

範圍中的元素應根據相同的標準(操作符<或comp)進行排序。 結果範圍也根據此進行排序。

格式:

default (1)
template <class InputIterator1, class InputIterator2, class OutputIterator>
  OutputIterator set_intersection (InputIterator1 first1, InputIterator1 last1,
                                   InputIterator2 first2, InputIterator2 last2,
                                   OutputIterator result);
custom (2)
template <class InputIterator1, class InputIterator2,
          class OutputIterator, class Compare>
  OutputIterator set_intersection (InputIterator1 first1, InputIterator1 last1,
                                   InputIterator2 first2, InputIterator2 last2,
                                   OutputIterator result, Compare comp);

相當於:

template <class InputIterator1, class InputIterator2, class OutputIterator>
  OutputIterator set_intersection (InputIterator1 first1, InputIterator1 last1,
                                   InputIterator2 first2, InputIterator2 last2,
                                   OutputIterator result)
{
  while (first1!=last1 && first2!=last2)
  {
    if (*first1<*first2) ++first1;
    else if (*first2<*first1) ++first2;
    else {
      *result = *first1;
      ++result; ++first1; ++first2;
    }
  }
  return result;
}

 

一定謹記:兩個區間必須是有序區間(從小到大),所以在intersection前務必對取交集的物件a和b進行sort

 

結果

將迭代器輸出到儲存結果序列的範圍的初始位置。
指向的型別應支援從第一個範圍分配元素的值。

補償(comp)

二進位制函式,接受輸入迭代器指向的型別的兩個引數,並返回一個可轉換為bool的值。返回的值表示第一個引數是否被認為是在它定義的特定嚴格弱順序中的第二個引數之前。
該函式不得修改其任何引數。
這可以是函式指標或函式物件。

範圍不得重疊。

事例:

#include <iostream>     // std::cout
#include <algorithm>    // std::set_intersection, std::sort
#include <vector>       // std::vector

int main() {
	int first[] = { 5,10,15,20,25 };
	int second[] = { 50,40,30,20,10 };
	std::vector<int> v(10);                      // 0  0  0  0  0  0  0  0  0  0
	std::vector<int>::iterator it;

	std::sort(first, first + 5);     //  5 10 15 20 25
	std::sort(second, second + 5);   // 10 20 30 40 50

	it = std::set_intersection(first, first + 5, second, second + 5, v.begin());
	// 10 20 0  0  0  0  0  0  0  0
	v.resize(it - v.begin());                      // 10 20

	std::cout << "The intersection has " << (v.size()) << " elements:\n";
	for (it = v.begin(); it != v.end(); ++it)
		std::cout << ' ' << *it;
	std::cout << '\n';

	return 0;
}

 

set_intersection()返回一個迭代器,指向並集的下一個元素的地址。

 

 

讓我們看下原題:

給定兩個陣列,編寫一個函式來計算它們的交集。

Example 1:

Input: nums1 = [1,2,2,1], nums2 = [2,2]
Output: [2,2]

Example 2:

Input: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
Output: [4,9]

注意:

結果中的每個元素應該出現在兩個陣列中顯示的次數。
結果可以是任何順序。

優化:

如果給定的陣列已經排序怎麼辦? 你會如何優化你的演算法?
如果nums1的尺寸與nums2的尺寸相比較小怎麼辦? 哪種演算法更好?
如果nums2的元素儲存在磁碟上,並且記憶體有限,以致您無法一次將所有元素載入到記憶體中,該怎麼辦?

 

程式碼分析:

#include <iostream>
#include <vector>
#include <unordered_map>
//#include <allocators>
#include <algorithm>
#include <iterator>
using namespace std;

//最常規的方法:先排序,然後迴圈比較
//class Solution {
//public:
//	vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
//		sort(nums1.begin(), nums1.end());
//		sort(nums2.begin(), nums2.end());
//		int n1 = (int)nums1.size(), n2 = (int)nums2.size();
//		int i1 = 0, i2 = 0;
//		vector<int> res;
//		while (i1 < n1 && i2 < n2) {
//			if (nums1[i1] == nums2[i2]) {
//				res.push_back(nums1[i1]);
//				i1++;
//				i2++;
//			}
//			else if (nums1[i1] > nums2[i2]) {
//				i2++;
//			}
//			else {
//				i1++;
//			}
//		}
//		return res;
//	}
//};

//自己的嘗試:想要利用一箇中間陣列,判斷有多少相同的數(陣列對應下標元素),例如i=2,a[2]++,則可以知道有幾個元素等於2。
//失敗了,因為同一個向量裡面也有相同元素。o(╥﹏╥)o
//class Solution {
//public:
//	vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
//		int arr_[1000];
//		for (int i = 0; i < nums1.size(); i++)
//			arr_[i] = 0;
//		for (int i = 0; i < nums1.size(); i++)
//		{
//			arr_[nums1[i]]++;
//		}
//		for (int i = 0; i < nums2.size(); i++)
//		{
//			arr_[nums2[i]]++;
//		}
//		int num_arr = 0;
//		for (int i = 0; i < nums1.size(); i++)
//		{
//			if (arr_[i] == 2)
//				num_arr++;
//		}
//
//		vector<int> arr_copy;
//		for (int i = 0; i < (nums1.size() > nums2.size() ? nums1.size() : nums2.size()); i++)
//		{
//			if (arr_[i] == 2)
//				arr_copy.push_back(i);
//		}
//
//		return arr_copy;
//
//	}
//};


//利用set_intersection()函式求排序範圍交叉點,然後去重
class Solution {
public:
	vector<int> intersect(vector<int>& a, vector<int>& b) {
		sort(a.begin(), a.end());
		sort(b.begin(), b.end());
		std::vector<int>::iterator it = set_intersection( a.begin(), a.end(), b.begin(), b.end(), a.begin() );
		int num = it - a.begin();                  //num=2
		a.resize(num);
		//a.resize(it - a.begin()); 
		//a.erase(set_intersection(a.begin(), a.end(), b.begin(), b.end(), a.begin()), a.end());
		return a;
	}
};

//利用無序對映,鍵值通常用於唯一標識元素
//class Solution {
//public:
//	vector<int> intersect(vector<int>& a, vector<int>& b) {
//		unordered_map<int, int> ctr;
//		vector<int> out;
//		for (int i : a)
//			ctr[i]++;
//		for (int i : b)
//			if (ctr[i]-- > 0)
//				out.push_back(i);
//		return out;
//	}
//};

//class Solution {
//public:
//	vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
//		unordered_map<int, int> dict;
//		vector<int> res;
//		for (int i = 0; i < (int)nums1.size(); i++) dict[nums1[i]]++;
//		for (int i = 0; i < (int)nums2.size(); i++)
//			if (--dict[nums2[i]] >= 0) res.push_back(nums2[i]);
//		return res;
//	}
//};



int main()
{
	vector<int> nums1{ 4,9,5};
	vector<int> nums2{ 9,4,9,8,4};
	Solution sol;
	
	
	for (auto i : sol.intersect(nums1, nums2))
		cout << i << " ";
	cout << endl;

	return 0;
}

能進行算術運算的迭代器只有隨即訪問迭代器,要求容器元素儲存在連續記憶體空間裡,vector,string,deque的迭代器是有加減法的,但是map,set,multimap,multiset的迭代器是沒有加減法的,list也不可以 。
該知識點是在刷leetcode347題時想到的。

裡面值得注意的點:

1.巧妙運用自減運算子

#include <iostream>
using namespace std;
int main()
{
	int a = 2 ;
	if(a--==2)
		cout << a <<endl;
	return 0;
}

輸出:1

我的寫法好像也可以這樣改進。。。我用的陣列,他用的對映。

 

 

 

迭代器的算術操作

1.ter+n    iter-n

可以對迭代器物件加上或減去一個整型值.這樣做將產生一個新的迭代器,其位置在iter所指元素之前(加法)或之後(減法)n個元素的位置.加或減之後的結果必須指向iter所指vector中的某個元素,或者是vector末端的後一個元素.加上或減去的值的型別應該是vector的size_type或difference_type型別

2.iter1-iter2

該表示式用來計算兩個迭代器物件的距離,該距離是名為difference_type的signed型別的值,這裡的difference_type型別類似於size_type型別,也是由vector定義的.difference_type是signed型別,因為減法運算可能產生負數的結果.該型別可以保證足夠大以儲存任何兩個迭代器物件間的距離.iter1與iter2兩者必須都指向同一vector中的元素,或者指向vector末端之後的下一個元素.

可以用迭代器算術操作來移動迭代器直接指向某個元素,例如,下面語句直接定義於vector的中間元素:

vector<int>::iterator mid= vi.begin() + vi.size()/2;

迭代器加上一個整型值就是產生一個新的迭代器,指向其後的n個位置,假設vi.size()返回十個元素,那麼上面得到的就是第六個元素.

上述程式碼用來初始化mid,使其指向vi中最靠近正中間的元素.這種直接計算迭代器的方法,與用迭代器逐個元素自增操作到達中間元素的方法是等價的,但前者效率要高很多.

注意:任何改變vector長度的操作都會使已存在的迭代器失效.例如,在呼叫push_back之後,就不能再信賴指向vector的迭代器的值了.

如果採用下面的方法來計算mid會產生什麼結果?

vector<int>::iterator mid=( vi.begin()+vi.end() )/2;

將兩個迭代器相加,沒有這種運算,上面說了,迭代器的算術操作僅有將兩個迭代器相減,算出兩個迭代器之間的距離.上面的程式會導致編譯錯誤
 

 

更多函式:  std::set_difference() 求兩個排序範圍的差異

                    std::set_union() 求兩個排序範圍的並集

                    std::merge() 合併排序範圍              

                    std::set_symmetric_difference() 求兩個排序範圍的對等差分 ( or )

給出兩個集合 (如集合 A = {1, 2, 3} 和集合 B = {2, 3, 4})

而數學術語 "對等差分" 的集合就是指由所有隻在兩個集合其中之一的元素組成的集合(A △ B = C = {1, 4}).

對於傳入的額外集合 (如 D = {2, 3}), 你應該安裝前面原則求前兩個集合的結果與新集合的對等差分集合 (C △ D = {1, 4} △ {2, 3} = {1, 2, 3, 4}).