Android利用包含排斥原理求x~y之间不能被x1,x2,x3.....xn整除的个数

前言

        撰写此博客,也是源于一道离散数学题。求1到1000之间(包含1和1000在内)既不能被5和6,也不能被8整除的数的个数。在第一次写的时候,用Java很简单的实现了上述讲到的数学问题。可在实际应用中,我们利用包含排斥原理来筛选数据,这些数据和筛选的规则往往不是固定的,所以,突发奇想:能不能利用包含排斥原理求x~y之间不能被x1,x2,x3.....xn整除的个数?本文为了让效果更明显,也为了本人温习下Android,所以用Android xml绘制界面和Java代码实现了这一过程。大笑 so,阅读本文章的你需要有一点Android基础了。

效果预览

那我们来看一看是如何实现的吧!

需求与分析

需求:

利用包含排斥原理计算x~y之间不能被x1,x2,x3.....xn整除的个数

包含排斥原理:


分析:

步骤:

  1. 先求出x1,x2,x3.....xn(类型为List)的排列数(C(n,k))将其存储在map集合中;
  2. 遍历map集合中的key对应的list,对每个key对应的list中的元素求最小公倍数;
  3.  x~y之间不能被x1,x2,x3......整除的个数:
    利用包含排斥原理:
           个数=区间长度(-/+,集合中每个key对应的list中的元素个数为奇数是为-,偶数为+)区间长度除以集合中每个key对应的list中的元素求最小公倍数个数求和得到的个数;

实现

界面实现

界面的代码很简单就是简单的相对布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="16dp"
    android:background="@drawable/bg"
    tools:context="com.example.huerfeng.counter.MainActivity">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#d5ecfa"
        android:textAlignment="center"
        android:padding="15dp"
        android:layout_margin="10dp"
        android:text="@string/t_title"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/t_start_num"
        android:id="@+id/tv_startNum"
        android:layout_below="@+id/title"
        android:layout_alignStart="@+id/title"
        android:layout_marginTop="44dp"
        android:paddingBottom="5dp"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/t_end_num"
        android:id="@+id/tv_endNum"
        android:layout_below="@+id/tv_startNum"
        android:layout_alignStart="@+id/tv_startNum"
        android:layout_marginTop="56dp"
        android:paddingBottom="5dp"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/t_divisor"
        android:id="@+id/tv_divisor"
        android:layout_centerVertical="true"
        android:layout_alignStart="@+id/tv_endNum" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/t_result_view"
        android:id="@+id/tv_calc"
        android:layout_marginTop="121dp"
        android:layout_below="@+id/tv_divisor"
        android:layout_centerHorizontal="true"
        android:textAlignment="center"/>

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/et_startNum"
        android:layout_above="@+id/tv_endNum"
        android:layout_alignEnd="@+id/title"
        android:textAlignment="center"
        android:layout_toEndOf="@+id/tv_calc"
        android:layout_alignStart="@+id/tv_calc"
        android:inputType="number"/>

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAlignment="center"
        android:id="@+id/et_endNum"
        android:layout_alignBottom="@+id/tv_endNum"
        android:layout_alignStart="@+id/et_startNum"
        android:layout_alignEnd="@+id/et_startNum"
        android:inputType="number"/>

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAlignment="center"
        android:id="@+id/et_divisor"
        android:layout_centerVertical="true"
        android:layout_alignStart="@+id/et_endNum"
        android:layout_alignEnd="@+id/title"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/bt_calc"
        android:text="@string/t_calc"
        android:background="#aae2df"
        android:layout_below="@+id/et_divisor"
        android:layout_margin="30dp"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="41dp" />

</RelativeLayout>

功能实现

代码如下:

import android.util.Log;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Counter {
	// 区间长度
	int intervalLen = 0;
	int count = 0;
	Map<Integer, List> map = new HashMap<>();
	int key = 0;

	// 求两个数的最大公约数
	public int getGcd(int a, int b) {
		if (b == 0)
			return a;
		return getGcd(b, a % b);
	}

	// 求两个数的最小公倍数
	public int getLcm(int a, int b) {
		return (a * b) / getGcd(a, b);
	}

	// 求多个数的最小公倍数
	public int getMoreLcm(List num, int n) {
		if (n == 1)
			return (int) num.get(n - 1);
		return getLcm((int) num.get(n - 1), getMoreLcm(num, n - 1));
	}

	/**
	 * 步骤::每次递归时,把原始数据和满足条件的工作空间复制一份,所有的操作均在复制文件中进行,目的就是保证不破坏原始数据,
	 * 从而可以让一轮递归结束后可以正常进行下一轮。
	 * 其次,把数据的第一个元素添加到工作空间中,判断工作空间的大小,如果小于k,则需要继续递归,但此时,传入递归函数的
	 * 参数需要注意:假设当前插入的节点的下标是i,因为是顺序插入的,所以i之前的所有数据都应该舍去,只传入i之后的未使用过的数据。
	 * 因此在传参之前,应该对copydata作以处理;当大于k的时候,则表明已经找到满足条件的第一种情况,然后只需修改该情况的最后一个结果即可。
	 * 如:找到abc时,则只需替换c为d即可完成该轮递归。
	 *
	 * @param data
	 *            原始数据
	 * @param workSpace
	 *            自定义一个临时空间,用来存储每次符合条件的值
	 * @param k
	 *            C(n,k)中的k
	 */
	public <E> void combinerSelect(List<E> data, List<E> workSpace, int n, int k) {
		List<E> copyData;
		List<E> copyWorkSpace;
		List<E> list = new ArrayList<>();
		if (workSpace.size() == k) {
			key++;
			for (E c : workSpace) {
				list.add(c);
			}
			map.put(key, list);
		}

		for (int i = 0; i < data.size(); i++) {
			copyData = new ArrayList<E>(data);
			copyWorkSpace = new ArrayList<E>(workSpace);
			copyWorkSpace.add(copyData.get(i));
			for (int j = i; j >= 0; j--)
				copyData.remove(j);
			combinerSelect(copyData, copyWorkSpace, n, k);
		}
	}

	public int getCount(int startNum, int endNum, List<Integer> data) {
		intervalLen=endNum-startNum+1;
		count=intervalLen;//初始长度为区间长度
		for (int i = 1; i <= data.size(); i++) {
			combinerSelect(data, new ArrayList<Integer>(), data.size(), i);
		}

		Log.e("x1,x2,x3.....xn的排列数",map.toString());

		for (int i = 1; i <= map.size(); i++) {
			if(map.get(i).size()%2==0){
				//集合中每个key对应的list中的元素个数为偶数时为+
				count+=intervalLen/getMoreLcm(map.get(i), map.get(i).size());
			}else {
				//集合中每个key对应的list中的元素个数为奇数时为-
				count-=intervalLen/getMoreLcm(map.get(i), map.get(i).size());
			}
		}
		return count;
	}
}
我在这里创建了一个Counter类,按照需求分析的步骤,里面包含了几个方法:计算最小公倍数

和计算最小公倍数时需要用到的最大公约数方法,以及求全排列的方法。都是用了递归的思想,其中求全排列的方法是引用了另外一个博主的,当然部分有做修改,如果你想要了解更多关于排列的算法,请参照博客:https://blog.csdn.net/yhyr_ycy/article/details/52523243。最后一个是getCount方法,参照需求分析第三个步骤。关于方法的用处在这里不做过多的解释,在代码中注释都有详细的解释。如果你有任何不明白的地方,欢迎在下方评论交流哦。

界面交互的MainActivity类

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    public static final String COMMA_NUMBER= "^[0-9]+([,][0-9]+)*$";
    private EditText et_startNum;
    private EditText et_endNum;
    private EditText et_divisor;
    private TextView tv_calc;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_startNum= (EditText) findViewById(R.id.et_startNum);
        et_endNum= (EditText) findViewById(R.id.et_endNum);
        et_divisor= (EditText) findViewById(R.id.et_divisor);
        tv_calc= (TextView) findViewById(R.id.tv_calc);
        Button bt_calc = (Button) findViewById(R.id.bt_calc);

        bt_calc.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                List<Integer> data=getDivisorList(et_divisor.getText().toString());
                String startNum=et_startNum.getText().toString();
                String endNum=et_endNum.getText().toString();
                checkDataAndCalc(startNum,endNum,data);
            }
        });
    }
    //检测输入框中数据并计算
  public void  checkDataAndCalc(String startNum, String endNum, List<Integer> data){

      if(startNum.isEmpty()){
          Toast.makeText(MainActivity.this,"开始数不能为空",Toast.LENGTH_SHORT).show();
      }
      else if(endNum.isEmpty()){
          Toast.makeText(MainActivity.this,"结束数不能为空",Toast.LENGTH_SHORT).show();
      }
      else if (Integer.parseInt(startNum)>Integer.parseInt(endNum)){
          Toast.makeText(MainActivity.this,"开始数不能大于结束数",Toast.LENGTH_SHORT).show();
      }
      else {
          if(data!=null){
              tv_calc.setText(String.valueOf(new Counter().getCount(Integer.parseInt(startNum),Integer.parseInt(endNum),data)));
          }
      }
    }

    public List<Integer> getDivisorList(String str){
        String str2=str.replace(" ", "");//去掉用户可能输入的空格
        if(str2.matches(COMMA_NUMBER)){//去掉空格后,对除数的输入格式进行正则匹配,只允许包含,和数字
            List<Integer> divisorList=new ArrayList<>();
            List<String> list= Arrays.asList(str2.split(","));
            Log.e("list",list.toString());
            for (String i:list) {
                divisorList.add(Integer.parseInt(i));
            }
            return divisorList;
        }else {
            Toast.makeText(this,"除数请严格按1,2,3.." +"的格式输入",Toast.LENGTH_SHORT).show();
        }
        return null;
    }

}

Activity和AppCompatActivity都是Android的核心类,在Activity类有onCreate事件方法,一般用于对Activity进行初始化,相当于程序的入口,并且通过setContentView方法将View(通过XML实现的视图等)放到Activity上,绑定后,Activity会显示View上的控件。在这里,我们一般都要对用户输入的数据进行检测,所以在这里写了一个checkDataAndCalc方法对用户输入的数据进行检测。对于开始数x和结束数y的限制输入而言,很简单,只需要对XML中的EditText控件中的inputType属性设置输入类型就ok了

        android:inputType="number"/>
而对于除数的输入,我们需要做一些处理,定义输入的规则,因此要用到正则表达式
    public static final String COMMA_NUMBER= "^[0-9]+([,][0-9]+)*$";
 String str2=str.replace(" ", "");//去掉用户可能输入的空格
        if(str2.matches(COMMA_NUMBER)){//去掉空格后,对除数的输入格式进行正则匹配,只允许包含,和数字

限定用户第一个数和最后一个数只能为数字,以逗号分割输入的数,这样可以方便我们对用户输入的数据进行截取。在检测用户输入的数据之后就可以调用Counter类中getCount方法进行运算了。
在上述工作做完之后,就可以运行预览效果了。 吐舌头运行效果在上面哦!

总结

       在处理一些问题的时候, 对于人来说,处理起来耗时费力,所以交给计算机处理就ok了。通过对包含排斥原理的运用,我们可以处理一些数学逻辑上的运算来达到减少运算次数的目的,在实际生活中,我们可以利用它对数据进行筛选。其实我们在实现的时候,都是一些算法的组合应用,只是在这里并没有仔细研究效率的问题。我还是个未入门的菜鸟,如果有错误的地方,欢迎指正。如果你有任何不了解的地方或者有更好更快速的方法,欢迎在下方留言评论哦。大笑


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值