天天看點

劍指offer之求數組裡面隻出現一次的的兩個資料

1 問題

一個整型數組裡除了兩個數字之外,其他的數字都出現了兩次。請寫程式找出這兩個隻出現一次的數字。

2 分析

第一種方法:我們用位運算

我們想到位運算

(1) a^a=0
 
(2)a^0=a
 
(2)a^b^c=a^(b^c)=(a^c)^b      

1) 對所有運算進行異或運算,最後結果就是兩個出現一次的元素異或結果,接下來問題演變成了我們知道兩個不同資料的異或值,那麼怎麼求出這兩個值呢?

2) 因為這兩個元素不相等,是以異或的結果肯定不是0,也就是可以再異或的結果中找到1位不為0的位,例如異或結果的最後一位為1,我們把這個位置标記為index,然後我們把原始數組分為2個數組,第一個數組中的每個數組的第index位都是1的數組和每個數組的第index位都是0的數組,然後我們再把這個兩組資料進行異或處理,分别求出的資料就是我們不同的兩個數。

第二種方法:我們用map,key為元素值,如果出現幾次放進value裡面去,然後最後周遊如果value是1的話,我們得到2個key就行。

3 代碼實作

用異或處理的C++版本如下

#include <iostream>
#include <vector>
 
using namespace std;
 
void findApperanceTwoNumber(vector<int>& input, int& num1, int& num2)
{
    if (input.size() < 2)
    {
        std::cout << "input.size() < 2" << std::endl;  
        return;
    }
    int sum = 0;
    //對所有資料進行異或處理
    for (int i = 0; i < input.size(); ++i)
    {
        sum ^= input[i];
    }
    //我們通過index找到這個2個不同元素異或值的位1的位置
    int index = 0;
    for (int i = 0; i < 32; ++i)
    {
        if (((sum >> i) & 1) == 1)
        {
            index = i;
            break;
        }
    }
    //然後我們周遊數組把每個資料的第index位和1進行異或處理,分别得到結果為1和0的2組資料,然後把這2組資料分别異或處理的和就是2個不同的資料
    for (int i = 0; i < input.size(); ++i)
    {
        if (((input[i] >> index) & 1) == 1)
        {
            num1 ^= input[i];
        }
        else
        {
            num2 ^= input[i];
        }
    } 
}
 
 
int main()
{
    vector<int> v2;
    
    v2.push_back(2);
    v2.push_back(2);
    v2.push_back(3);
    v2.push_back(4);
    v2.push_back(6);
    v2.push_back(6);    
 
    int a = 0, b = 0;
    findApperanceTwoNumber(v2, a, b);
    std::cout << "a is "<< a << " b is " << b << std::endl; 
 
    return 0;
}      

用map處理的C++版本如下

#include <iostream>
#include <vector>
#include <map>
 
using namespace std;
 
void findApperanceTwoNumber2(vector<int>& input, int& num1, int& num2)
{
    if (input.size() < 2)
    {
        std::cout << "input.size() < 2" << std::endl;
        return;
    }
    map<int, int> datas;
    for (int i = 0; i < input.size(); ++i)
    {
        //C++裡面map如果要通過key擷取value的話,我們先需要探測map裡面是不是有這個key,我們可以count函數,這裡如果是java版本的話,就算key不存在的話,我們執行get方法操作,得到的null,沒關系。
        if (datas.count(input[i]))
        {
            if (datas[input[i]] == 1)
            {
                //這裡用數組形式是因為如果用insert如果發現key一樣的話,再次插入會失敗,我們是以用數組的形式,這裡是通過key更新value
                datas[input[i]] = 2;
            }
        }
        else
        {  
            datas[input[i]] = 1;
        }
    }
    ///然後我們再周遊map
    for (map<int, int>::iterator it = datas.begin(); it != datas.end(); ++it)
    {
         if (it->second == 1)
         {
             if (num1 == 0)
             {
                 //這裡是擷取key
                 num1 = it->first;
             }
             else
             {
                 //這裡是擷取value
                 num2 = it->first;
             }
         }
    }
}
 
int main()
{
    vector<int> v2;
    
    v2.push_back(2);
    v2.push_back(2);
    v2.push_back(3);
    v2.push_back(4);
    v2.push_back(6);
    v2.push_back(6);    
 
    int a = 0, b = 0;
    findApperanceTwoNumber2(v2, a, b);
    std::cout << "a is "<< a << " b is " << b << std::endl; 
 
    return 0;
}      

運作結果如下

a is 3 b is 4
 
       

用map處理的java版本如下

import java.io.*;
import java.util.ArrayList;
import java.util.*;
 
class St
{
    public St() {}
    ArrayList<Integer> findApperanceTwoNumber2(int[] datas) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        if (datas == null || datas.length < 2)
            return list;   
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int i = 0; i < datas.length; ++i) {
            if (map.containsKey(datas[i])) {
                if (map.get(datas[i]) == 1)
                {
                    map.put(datas[i], 2);
                }
            } else {
                map.put(datas[i], 1);
            }
        }
        Set<Integer> keys = map.keySet();
        for (int key : keys)  {
            if (map.get(key) == 1)  {
                list.add(key);
            }
        }
        return list;
    }
}
 
 
 
class test  
{
    public static void main (String[] args) throws java.lang.Exception
    {
        ArrayList<Integer> list = new ArrayList<Integer>();
        St s = new St();
        int [] a = {1, 1, 3, 5, 4, 4};
        list = s.findApperanceTwoNumber2(a);
        for (int i : list)
        {
            System.out.println("value is " + i);
        }
    }
}      
value is 3
value is 5      

4 總結

如果我們有2個不同數的異或值,那我們怎麼知道這2個資料值呢?(這裡不是說直接知道這2個元素的數組意思,不然還要你求幹吊) 因為這兩個元素不相等,是以異或的結果肯定不是0,也就是可以再異或的結果中找到1位不為0的位,例如異或結果的最後一位為1,我們把這個位置标記為index,然後我們把原始數組分為2個數組,第一個數組中的每個數組的第index位都是1的數組和每個數組的第index位都是0的數組,然後我們再把這個兩組資料進行異或處理,分别求出的資料就是我們不同的兩個數。

C++版本的map操作,我們最好是用數組形式,因為數組形式既可以指派和可以用來進行修改操作,如果是用insert函數插入的當key是同樣而value不是同樣值的時候會失敗,然後我們擷取的話最好也是通過數組形式的key擷取,但是擷取之前我們需要判斷key是否存在map裡面的key沒有,如果沒在的話,直接擷取就有問題,但是java的話,就算沒有key,擷取的也是null值,沒關系。

C++的話我們map用count(key)函數來判斷是否key存在,而java的話,我麼可以用map的containsKey(key)函數判斷key是否存在,無論在C++版本還是java版本,我們要對map通過key擷取value操作,如果不是通過keySet來擷取(就是這個key必然存在map裡面的時候),我們都要先用上面的函數進行探測是否包含key,這樣代碼的健壯性好點。