数组练习

二、题目

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

1、思路

大家首先想到的是顺序扫描法,但是这种方法的时间复杂度是O(n^2)。接着大家又会考虑用哈希表的方法,但是空间复杂度不是O(1)。

应该怎么做才能即满足时间复杂度是O(n)又满足空间复杂度是O(1)的要求呢?

我们可以想一想“异或”运算的一个性质,我们直接举例说明。

举例:{2,4,3,6,3,2,5,5}

这个数组中只出现一次的两个数分别是4和6。怎么找到这个两个数字呢?

我们先不看找到俩个的情况,先看这样一个问题,如何在一个数组中找到一个只出现一次的数字呢?比如数组:{4,5,5},唯一一个只出现一次的数字是4。

我们知道异或的一个性质是:任何一个数字异或它自己都等于0。也就是说,如果我们从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字。比如数组{4,5,5},我们先用数组中的第一个元素4(二进制形式:0100)和数组中的第二个元素5(二进制形式:0101)进行异或操作,0100和0101异或得到0001,用这个得到的元素与数组中的三个元素5(二进制形式:0101)进行异或操作,0001和0101异或得到0100,正好是结果数字4。这是因为数组中相同的元素异或是为0的,因此就只剩下那个不成对的孤苦伶仃元素。

现在好了,我们已经知道了如何找到一个数组中找到一个只出现一次的数字,那么我们如何在一个数组中找到两个只出现一次的数字呢?如果,我们可以将原始数组分成两个子数组,使得每个子数组包含一个只出现一次的数字,而其他数字都成对出现。这样,我们就可以用上述方法找到那个孤苦伶仃的元素。

我们还是从头到尾一次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数组的异或结果。因为其他数字都出现了两次,在异或中全部抵消了。由于两个数字肯定不一样,那么异或的结果肯定不为0,也就是说这个结果数组的二进制表示至少有一个位为1。我们在结果数组中找到第一个为1的位的位置,记为第n位。现在我们以第n位是不是1为标准把元数组中的数字分成两个子数组,第一个子数组中每个数字的第n位都是1,而第二个子数组中每个数字的第n位都是0。

举例:{2,4,3,6,3,2,5,5}

我们依次对数组中的每个数字做异或运行之后,得到的结果用二进制表示是0010。异或得到结果中的倒数第二位是1,于是我们根据数字的倒数第二位是不是1分为两个子数组。第一个子数组{2,3,6,3,2}中所有数字的倒数第二位都是1,而第二个子数组{4,5,5}中所有数字的倒数第二位都是0。接下来只要分别两个子数组求异或,就能找到第一个子数组中只出现一次的数字是6,而第二个子数组中只出现一次的数字是4。

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        int length = data.size();
        if(length < 2){
            return;//一定要出现3次数
        }
        
        // 对原始数组每个元素求异或
        int resultExclusiveOR = 0;//初始化
        for(int i = 0; i < length; ++i){
            resultExclusiveOR ^= data[i];//求异或,其他数字出现了两次,在异或中被抵消了,
由于两个数字肯定不一样,那么异或的结果肯定不为0,也就是说这个结果数组的二进制表示至少有一个位为1
        }
        
        unsigned int indexOf1 = FindFirstBitIs1(resultExclusiveOR);
        
        *num1 = *num2 = 0;
        for(int j = 0; j < length; j++){
            if(IsBit1(data[j], indexOf1)){//我们在结果数组中找到第一个为1的位的位置,记为第n位。
                *num1 ^= data[j];
            }
            else{
                *num2 ^= data[j];
            }
        }
    }//以第n位是不是1为标准把元数组中的数字分成两个子数组,第一个子数组中每个数字的第n位都是1,而第二个子数组中每个数字的第n位都是0。
private:
    // 找到二进制数num第一个为1的位数,比如0010,第一个为1的位数是2。
    unsigned int FindFirstBitIs1(int num){
        unsigned int indexBit = 0;
        // 只判断一个字节的
        while((num & 1) == 0 && (indexBit < 8 * sizeof(unsigned int))){
            num = num >> 1;
            indexBit++;
        }
        return indexBit;
    }
    // 判断第indexBit位是否为1
    bool IsBit1(int num, unsigned int indexBit){
        num = num >> indexBit;
        return (num & 1);//接下来只要分别两个子数组求异或,就能找到第一个子数组中只出现一次的数字是6,而第二个子数组中只出现一次的数字是4。
    }
};
class Solution:
    # 返回[a,b] 其中ab是出现一次的两个数字
    def FindNumsAppearOnce(self, array):
        # write code here
        if len(array) <= 0:
            return []
        resultExclusiveOR = 0
        length = len(array)
        for i in array:
            resultExclusiveOR ^= i
        firstBitIs1 = self.FindFisrtBitIs1(resultExclusiveOR)
        num1, num2 = 0, 0
        for i in array:
            if self.BitIs1(i, firstBitIs1):
                num1 ^= i
            else:
                num2 ^= i
        return num1, num2
        
    def FindFisrtBitIs1(self, num):
        indexBit = 0
        while num & 1 == 0 and indexBit <= 32:
            indexBit += 1
            num = num >> 1
        return indexBit
    
    def BitIs1(self, num, indexBit):
        num = num >> indexBit
        return num & 1

 

 

举例说明:{2,3,1,0,2,5,3}

 

 

个人感觉最优的方法:题目里写了数组里数字的范围保证在0 ~ n-1 之间,所以可以利用现有数组设置标志,当一个数字被访问过后,可以设置对应位上的数 + n,之后再遇到相同的数时,会发现对应位上的数已经大于等于n了,那么直接返回这个数即可。

  • 记数组中存在的唯一的两个数字分别为a,b
  • 首先以二进制的角度去看所有的数字,每一位均为0,1组成
  • 对所有数字进行个位上的异或,成对相同的数字结果为0,每一位上都这样异或依次,所以最终每一位上存在1的则必然是因为a,b在这一位上不同
  • 根据最终结果上存在‘1’的这一位,将原数组分为两组,一组‘1’,一组‘0‘,
  • 两组数字再分别异或,最终两个结果就是a,b;
  • 
    
    class Solution {
    public:
        void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
            int num=0;
     
            for(int i=0;i < data.size();i++){
                num^=data[i];//所有数异或,结果为不同的两个数字的异或,0与任何数字异或等于数字本身
            }//最后肯定不是0,这个1出现的位置就是AB不同的地方,以这个地方来分界,划分为两个数组
     
            int count=0;//标志位,记录num中的第一个1出现的位置
            for(;count < data.size();count++){
                if((num&(1<<count))!=0){
                    break;
                }
            }
     
            int num_1 = 0;
            int num_2 = 0;
     
            for(int i=0; i < data.size(); i++){
     
                if((data[i]&(1<<count))==0){//标志位为0的为一组,异或后必得到一个数字(这里注意==的优先级高于&,需在前面加())
                    num_1^=data[i];
                }else{
                    num_2^=data[i];//标志位为1的为一组
                }
            }
            *num1 = num_1;
            *num2 = num_2;//最后输出的NUM就是那个数字
        }
    };

    #include<stdio.h>

    #include<stack>

    #include<map>

    using namespace std;

    int main(){

        stack<char> s;

        map<char,char> book;

        book[')']='(',book[']']='[',book['}']='{';

        char in[100];

        scanf("%s",in);

        int flag=1,i;

        for(i=0;in[i]!='\0';i++)

            if(in[i]=='('||in[i]=='['||in[i]=='{') s.push(in[i]);

            else{

                if(s.empty()||s.top()!=book[in[i]]){

                    flag=0;break;

                }

                s.pop();

            }

        printf("%s",s.empty()&&flag?"true":"false");

    }

  • 输入一组未排序的整数,其中一个数字只出现一次,剩下的所有数字都出现了三次。找出这个只出现一次的数字。例如输入: [1,2,2,2,3,3,3],输出为1
  • n&(1<<i)是用来判断n的第i位是否为1。i>>1表示i右移1位,1<<i表示1左移i位,相当于第i位为1,其他位为0的整数。因此n&(1<<i)可以判断整数n的第i位是否为1。
  • 
    import java.util.*;
    public class Main{
        public static void main(String[] args){
            Scanner sc=new Scanner(System.in);
            int n=sc.nextInt();//N是数组里面的个数
            int[] a=new int[n];
            int sum=0;
            for(int i=0;i<n;i++){
                a[i]=sc.nextInt();//输入每一个数字
            }
           
            int x=0;
            for(int i=0;i<32;i++){//查看32个位置上面的数字为1的个数是否是3的倍数多1,整数不超过32次
               int bit=1<<i;//设置位置,表示第I位为1
                int count=0;
                for(int j=0;j<n;j++){
                   if((bit&a[j])!=0)//判断一个整数的第I位是不是1,第I位是1 就加一次个数
                       count++;
                }            
                if(count%3==1)//代表单独出现的那个数字在 1<<i(第I位是1)这个位置上有出现1,故将其相加
                    x=x|bit;
            }
             
               System.out.println(x);
        }
        
    }//x=x&(x-1);
    这行代码什么意思,&的这个用法是什么意思?
    //这是“位运算”中的一种很经典的用法,“&”是“位于”的意思。它具体点的意思就是把x的二进制表示数最右边的一个1变成0  例如:   
      e1:   
      x      = 01001000   
      x-1    = 01000111   
      x&(x-1)= 01000000   
      e2:   
      x      = 01001001   
      x-1    = 01001000  

    有一个长度为N的序列。一开始,这个序列是1, 2, 3,... n - 1, n的一个排列。

    对这个序列,可以进行如下的操作:

    每次选择序列中k个连续的数字,然后用这k个数字中最小的数字替换这k个数字中的每个数字。

    我们希望进行了若干次操作后,序列中的每个数字都相等。请你找出需要操作的最少次数。

    输入描述:

     

    第一行:两个数字n, k,含义如题,满足2 <= k <= n <= 105;

    第二行:n个数字,是1, 2, 3,...n的一个排列。

    输出描述:

    一个整数,表示最少的次数。

    示例1

    输入

    复制

    2 2
    2 1

    输出

    复制

    1

    示例2

    输入

    复制

    4 3
    1 2 3 4

    输出

    复制

    2

  • 方法1:

  •  

    对于结果只有一个数字,输入是N个不同的数字,那么肯定有N-1次改变,每次要改变K-1次,向上取整。

    序列的最终结果为一个数,那么长度为n的序列有n-1个数字需要改变;每次取k个数字,则最多有k-1个数字需要改变,所以次数为(n-1)/(k-1)向上取整。

    #include<bits/stdc++.h>

    using namespace std;

    int main(){

    int n,k;

    while(cin>>n>>k){

    for(int i = 0;i<n;i++){

    int x;

    cin>>x;

    }

    cout<<ceil((double)(n-1)/(k-1))<<endl;

    }

    system("pause");

    return 0;

    }

    方法2:

    第一步:从n个数字中不重复的取k个数字,则可取n/k次,剩余(n/k+n%k)个数字不同;

    第二步:从n/k+n%k个数中取k个数字,重复第一步,直到剩余数字<=k个。

    #include<iostream>

    #include <string>

    usingnamespacestd;

    intmain()

    {

    intn,k;

    intcycle=0,num=0;

    string s;

    cin>>n>>k;

    getline(cin,s);

    while((n>k))

    {

    cycle=n/k;

    n=cycle+n%k;

    num+=cycle;

    }

    num++;

    cout<<num;

    }


  •  

    import java.util.Scanner;

    public class Main {

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

      public static void main(String[] args) {

       int result = 0;

             Scanner sc = new Scanner(System.in);

             int  n = sc.nextInt();

             int  l = sc.nextInt();        

             if(n==l){

                 System.out.println(1);//输入5个数字,每次取5个,肯定要改变1次搞定

             }

             else{

                 result = (n-l)/(l-1) +1;//向上取整

                 if((n-l)%(l-1)>0) {

                        result++;

                    }         

                System.out.println(result);

             }     

         

    }

  • 对于一个由0..n的所有数按升序组成的序列,我们要进行一些筛选,每次我们取当前所有数字中从小到大的第奇数位个的数,并将其丢弃。重复这一过程直到最后剩下一个数。请求出最后剩下的数字。

    输入描述:

    每组数据一行一个数字,为题目中的n(n小于等于1000)。

    输出描述:

    一行输出最后剩下的数字。

    示例1

    输入

    复制

    500

    输出

    复制

    255


  • 因为是从0开始,所以第一轮移走的是二进制下最右边为0的位置(从0开始的偶数位置)上的数,然后我们发现第二轮各个number的位置等于number/2,即从number位置到number>>1位置,这时候我们依然移走二进制下最右边为0的位置(1(01)  5(101) 9(1001) ……它们第二轮对应的位置是0, 2, 4),最后剩一个数肯定是0到n中二进制下1最多的那个数,因为它每次的位置都是奇数位置。代
  •  

    //特殊思路,每次删除所在数组位置的二进制最右端为0的元素。如0(0)2(10)4(100)

    //剩余的元素1(01)3(11)5(101)下一次其位置变成了之前位置左移一次后的

    // 1(1) 3(10) 5(10) 然后继续按之前规则删除最右端为0的元素。故原始序列中,谁的//二进制下从右往左数,1最多,则最后删除,因每次删除移位后,最右端仍然为1,会保留

    #include<iostream>

    using namespace std;

    int main(){

        int n;

        while( cin >> n ){

            int b = 1;

            while( b <= n )

                /*b = (b<<1) + 1;//或者 用*/ b = b*2 +1;

            cout << (b>>1) << endl;

        }

    }

  • 这个表示移位运算,就是把num转换成二进制表示后所有位向后移动一位,高位补0

  • 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

  • 1、思路

    还可以把当前序列当成是一个下标和下标对应值是相同的数组(时间复杂度为O(n),空间复杂度为O(1)); 遍历数组,判断当前位的值和下标是否相等:

  • 若相等,则遍历下一位;
  • 若不等,则将当前位置i上的元素和a[i]位置上的元素比较:若它们相等,则找到了第一个相同的元素;若不等,则将它们两交换。换完之后a[i]位置上的值和它的下标是对应的,但i位置上的元素和下标并不一定对应;重复2的操作,直到当前位置i的值也为i,将i向后移一位,再重复2。
  • 0(索引值)和2(索引值位置的元素)不相等,并且2(索引值位置的元素)和1(以该索引值位置的元素2为索引值的位置的元素)不相等,则交换位置,数组变为:{1,3,2,0,2,5,3};
  • 0(索引值)和1(索引值位置的元素)仍然不相等,并且1(索引值位置的元素)和3(以该索引值位置的元素1为索引值的位置的元素)不相等,则交换位置,数组变为:{3,1,2,0,2,5,3};
  • 0(索引值)和3(索引值位置的元素)仍然不相等,并且3(索引值位置的元素)和0(以该索引值位置的元素3为索引值的位置的元素)不相等,则交换位置,数组变为:{0,1,2,3,2,5,3};
  • 0(索引值)和0(索引值位置的元素)相等,遍历下一个元素;
  • 1(索引值)和1(索引值位置的元素)相等,遍历下一个元素;
  • 2(索引值)和2(索引值位置的元素)相等,遍历下一个元素;
  • 3(索引值)和3(索引值位置的元素)相等,遍历下一个元素;
  • 4(索引值)和2(索引值位置的元素)不相等,但是2(索引值位置的元素)和2(以该索引值位置的元素2为索引值的位置的元素)相等,则找到了第一个重复的元素。
  • class Solution {
    public:
        // Parameters:
        //        numbers:     an array of integers
        //        length:      the length of array numbers
        //        duplication: (Output) the duplicated number in the array number
        // Return value:       true if the input is valid, and there are some duplications in the array number
        //                     otherwise false
        bool duplicate(int numbers[], int length, int* duplication) {
            // 非法输入
            if(numbers == NULL || length <= 0){
                return false;
            }
            // 非法输入
            for(int i = 0; i < length; i++){
                if(numbers[i] < 0 || numbers[i] > length - 1){
                    return false;
                }
            }
            // 遍历查找第一个重复的数
            for(int i = 0; i < length; i++){
                while(numbers[i] != i){
                    if(numbers[i] == numbers[numbers[i]]){
                        *duplication = numbers[i];
                        return true;
                    }
                    swap(numbers[i], numbers[numbers[i]]);
                }
            }
            return false;
        }
    };

    class Solution:

        # 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]

        # 函数返回True/False

        def duplicate(self, numbers, duplication):

            # write code here

            n = len(numbers)

            if n == 0:

                return False

            for i in range(n):

                if numbers[i] < 0 or numbers[i] > n-1:

                    return False

            for i in range(n):

                while numbers[i] != i:

                    if numbers[i] == numbers[numbers[i]]:

                        duplication[0] = numbers[i]

                        return True

                    numbers[numbers[i]], numbers[i] = numbers[i], numbers[numbers[i]]

            return False

  • class Solution:

        # 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]

        # 函数返回True/False

        def duplicate(self, numbers, duplication):

            # write code here

            n = len(numbers)

            if n == 0:

                return False

            for i in range(n):

                index = numbers[i]

                if index >= n:

                    index -= n

                if numbers[index] >= n:

                    duplication[0] = index

                    return True

                numbers[index] += n

            return False

    class Solution:
        # 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]
        # 函数返回True/False
        def duplicate(self, numbers, duplication):
            # write code here
            n = len(numbers)
            if n == 0:
                return False
            for i in range(n):
                index = numbers[i]//把每个位置的值映射到数组的下标,这样有重复的数字的话就会对这个位置访问两次以上,如果我们在第一次的时候进行一些操作,那么第二次访问时肯定就不一样了,就能找到我们想要的结果。
                if index >= n:
                    index -= n//类似于桶排序,把把取模,归一化到0到N的范围
                if numbers[index] >= n:
                    duplication[0] = index//如果通过这个下标访问的数字大于N,肯定被操作了,就是重复的数字
                    return True
                numbers[index] += n//如果没有大于N,说明是第一次访问,我们把它加上N,作为一个标记
            return False

     

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值