从零开始的刷题生活

刷刷刷

Posted by Elias on February 26, 2019

记录从零开始的幸福刷题生活

前言

记录刷题过程中遇到的对自己有启发的代码或是思维方式

水滴石穿,绳锯木断

数据总喜欢对自己有过多的要求!

  • 数据需要保留1位(或多位x)小数(需要考虑四舍五入)

    对于上述要求,可以在原有数据基础上乘以(*)相应的倍数,即先扩大为原来的多少倍,这样可以把需要的小数部分转到整数部分,再让该数据+0.5,然后int强制转换,将转换后的数据处以(/)之前扩大的倍数,这样得到的数据即满足上述要求。

    例如在一次消费中,消费金额为 cost = 178.57 ,保留1位小数,此时:

    double cost = 178.57;
    cost = int(cost*10+0.5)/10.0;
    

    由于/运算向下取整,且int强制转换只保留整数部分,因此在原数据基础上*10后,需要考虑进位的百分位到了十分位,此时在该数据基础上+0.5,如果不进位(<0.5),则加上0.5后,并不会向个位进位,如果进位(>=0.5),则此时的个位数+1,经过强制转换保留整数位再/10后,该数又回到十分位,并且已完成四舍五入(即保留了1位小数)。

    最后得到的cost = 178.6。

  • 在大量数据(整型)中统计某一个数字出现的次数

    统计1-n个连续数字中,x(0<=x<=9)出现的次数,对于这种连续的数据,通过循环得到每一个数据,再不断的处理该数字的个位数,个位数与x判断完成后,数据/10,将原十位数转变为个位数继续进行判断。

    for(i=1;i<=n;i++){
        b = i;
        while(b!=0){
            if(b%10 == x)	count++; //比较个位数
              
            b = b/10;	//原十位数转到个位数,进行下一个循环比较
              
        }
    }
    cout<<count<<endl;
    
  • 数据需要进行大小写转换

    对于string类,中有方法能够进行大小写转换。即:

    #include <iostream>
    #include <algorithm>
      
    string str;
      
    transform(str.begin(),str.end(),str.begin(),::tolower);	//全部转换为小写
      
    transform(str.begin(),str.end(),str.begin(),::toupper);	//全部转换为大写
    

    除此之外,我们还可以自己写大小写转换的函数,即利用ASCII的差值,通过加减就能转换为另外一个字母所对应的ASCII值。

一切为了节省空间!

  • 数值互换

    现有a,b两个数据类型相同的变量,需要将这两个变量数值互换。

    一般方法是构建一个中间变量c,通过c暂存其中一个数值,使得这两个数值可以在不丢失数值的前提下互换。

    //以整型变量举例
      
    int c;
    c = a;
    a = b;
    b = c;
    

    为了节省空间,可以通过加减运算来进行互换而不用引入一个新的变量。 通过加减运算,节省了1/3的空间!(滑稽

    a = a + b;
    b = a - b;
    a = a - b;
    

灵活多变的vector!

  • 字符串拆分

    一串待输入的未知长度的字符串xxxxxx,我们在输入后还要对其进行拆分x x x x x,并对拆分后的字符进行处理,这种情况下使用char[]或者string型的数据类型,后续的处理都十分麻烦,这个时候使用vector就显得十分方便,因为vector本身并不会要求在初始时就定好空间大小,而是可以随着需要不断扩大或者缩小的。在此基础上,我们继续对输入进行识别、控制,就能够将一串字符输入到vector_1里,并且还能继续下一串字符vector_2,且双方的字符串并不会有冲突。

    在这里除了用到了vector本身的特性,我们还会使用到cin.get(),这个函数能够将“回车”也保留下来,接着对接收到的字符串进行识别处理,就知道当前输入的字符串是否完毕了。

    vector的部分使用

    vector<char>::iterator it;
    vector<char> a, b;
    char c;
      
    while(cin.get(c)){	//将输入的所有字符全部保留在c中,包括"回车"
          
        if(c == '\n')	//对字符c进行判别,如果识别到回车,则本次输入结束
              
            break;
        a.push_back(c);	//如果当然接收的字符不是“回车”,则将当然字符放入vector a中,继续接收下一字符
          
    }
    while(cin.get(c)){	//同理,在上一串字符完全接收后(即识别到了“回车”),开始接收下一串字符
          
        if(c == '\n')
            break;
      b.push_back(c);
    }
      	
    

    vector中对自身的遍历

    vector<char>::iterator it;
    vector<char> a;
      
    for(it = a.begin();it!=a.end();it++){
    	sum_a *= *it - 64;//这里减64是因为原题中A代表数值“1”,A的ASCII是“65”
          
        //因此A~Z减去64即为对应的数字
          
    }
    

让我们快乐搜索吧!

  • 深度优先搜索(DeepFirstSearch)

    就像它的名字这样简单易懂,我们搜索时总是以往更深层次的搜索为首要目的,即不断远离初始节点,离它越远越好,直到当前不能再继续往深了走了,我们再向上返回,每返回一层,我们就找找有没有其它路径能继续向下走,直到我们回到了初始节点,就将所有数据都遍历了一遍。

    排列组合中的深度优先搜索经常会用到标记符,标记该节点当前是否被遍历过,如果某一元素在前几层被遍历了,我们就可以直接选择放弃这一条支路,这样可以去重复,节省时间。让我们来使用一个vis[]数组保存它吧。如果vis[]=0,就表明它还没有被光临过,如果vis[]=1,就表示当前元素已经被光临过了。

    并且vis[]=1,vis[]=0这两个标记经常嵌套使用,在我们进入下一层前,先把当前层标记上(vis[]=1),当从下一层退回来,并且要退回上一层时,我们就把当前层的标记解除(vis[]=0),这样就能够保证所遍历的一条路中不会有重复的元素。

    假如现在我们需要在n个数中寻找任意k个数的排列组合(k<n),并求每一个组合本身的和。

    这个问题在于我们需要找到所有的任意k个数的排列组合.

    实例代码如下:

    bool vis[25];
    void dfs(int t,int sum){
      	//t代表现在select了多少个数
            
      	//sum表示当前的和
            
          for(int i=0;i<n;i++){
              if(vis[i])//如果被选过
                    
                  continue;//直接进入下一次循环
                
              vis[i]=true;//标记为选过
                
              dfs(t+1,sum+a[i]);//递归
                
              vis[i]=false;//要跳出当前元素了,解除标记
              
          }
        return ;//这一个步骤下,所有的都枚举完了,直接返回去
      
    }
    

    当然,排列组合中的深度优先搜索,除了使用vis[]进行已选择元素的标记外,还可以使用不降原则,这样选择到的下一元素的数组下角标,一定不是遍历过的。

    那么什么是不降原则呢?

    举个例子栗子:

    我们在1 2 3 4 5 6六个数字中求其中任意三个数字的排列组合

    根据不降原则,我们第一轮会得到:

    1 2 3

    1 2 4

    1 2 5

    1 2 6

    那么第二轮,我们会从1 3 开始,

    *号应该是什么数字呢

    根据不降原则,*所选的数必须大于已经选择(1、3)的数字

    即此时的*不能为1 2 3,

    让我们看看第二轮的选择:

    1 3 4

    1 3 5

    1 3 6

    很明显,根据不降原则,我们筛选掉了“1 3 2”这个和第一轮中“1 2 3”重复的组合

    再来第三轮:

    1 4 5

    1 4 6

    //筛掉了“1 4 2”、“1 4 3”两个已重复的组合

    第四轮:

    1 5 6 至此,第一位是“1”的排列已经完全遍历完毕了

    接着找第一个是“2”的排列

    2 3 4

    2 3 5

    可以发现,越往后,我们筛选掉的重复组合越多,并且通过不降原则,在类似问题中我们甚至可以省略vis[]的标记,因为遵循不讲原则的情况下,我们是不会走重复路线的(仅所选元素无先后关系)

    不降原则的代码实现

    在不降原则的代码实现中,会多一个变量,这个变量用于控制在进入下一层后,开始循环时的起始下标:

    void dfs(int t, int sum, int startx){//不降原则,去重复
            
      	//t代表现在select了多少个数
            
      	//sum表示当前的和  
            
      	for(int i=startx;i<n;i++){
      		dfs(t+1, sum+a[i], i+1);//i+1采用不降原则,继续进入深搜的过程中要找角标比i大的那些数据
                
      	}
      	return;	//至此,所有组合已枚举完毕
            
          //直接返回
            
      }