发票额度报销最优排列组合问题

news2024/10/4 9:31:45

1、问题描述

        因为我的公司每个月给员工会有一定的交通费额度,需要拿发票去报销才能获得的。要求的是发票总金额不能大于报销的额度。因此在实际报销的时候,你要一张张发票去排列组合经可能的把报销金额往报销额度那里去凑。比如你有1000元额度,那么你报销的时候就要尽可能的把你要报销的发票往1000元去凑,因为发票金额不能大于报销额度。

2、解决方法

        其实就是一个求限定条件下最优解的问题,如果人工手动去凑发票费时又费力,而且还不能保证你凑的一定是最优解。所以就写个程序帮助求最优解的排列组合。

3、代码

3.1、列出所有的组合,并一一计算这些组合的和并保存不大于给定值的组合

package Algorithm;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import Algorithm.MyInvoice.Compose;

public class MyInvoiceV3 {
	static List<Compose> composeList = new ArrayList();
    //和不能超过的最大金额
	static double SUM = 1000;

	class Compose {
		List<Double> list;
		Double sum;

		Compose(List<Double> list, Double sum1) {
			this.list = list;
			this.sum = sum1;
		}

		Double getSum() {
			return this.sum;
		}

		public String toString() {
			// 把double转成带2个小数点的十进制数
			return list.toString() + ":" + new BigDecimal(sum).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
		}
	}

	public static void main(String[] args) {
		// 一组发票金额
		String[] array = new String[] { "10.1", "200.14", "300", "450.91", "556.5", "226", "10.1", "200.14", "300",
				"130" };
		for (int i = 0; i < array.length; i++) {
			combinationSelect(array, i);
		}
		for (Compose c : composeList.stream().sorted(Comparator.comparing(Compose::getSum).reversed())
				.collect(Collectors.toList())) {
			System.out.println(c);
		}

	}

	/**
	 * 组合选择(从列表中选择n个组合)
	 * 
	 * @param dataList 待选列表
	 * @param n        选择个数
	 */
	public static void combinationSelect(String[] dataList, int n) {
		// System.out.println(String.format("C(%d, %d) = %d", dataList.length, n,
		// combination(dataList.length, n)));
		combinationSelect(dataList, 0, new String[n], 0);
	}

	/**
	 * 计算组合数,即C(n, m) = n!/((n-m)! * m!)
	 * 
	 * @param n
	 * @param m
	 * @return
	 */
	public static long combination(int n, int m) {
		return (n >= m) ? factorial(n) / factorial(n - m) / factorial(m) : 0;
	}

	/**
	 * 计算阶乘数,即n! = n * (n-1) * ... * 2 * 1
	 * 
	 * @param n
	 * @return
	 */
	private static long factorial(int n) {
		return (n > 1) ? n * factorial(n - 1) : 1;
	}

	/**
	 * 组合选择
	 * 
	 * @param dataList    待选列表
	 * @param dataIndex   待选开始索引
	 * @param resultList  前面(resultIndex-1)个的组合结果
	 * @param resultIndex 选择索引,从0开始
	 */
	private static void combinationSelect(String[] dataList, int dataIndex, String[] resultList, int resultIndex) {
		int resultLen = resultList.length;
		int resultCount = resultIndex + 1;
		if (resultCount > resultLen) { // 全部选择完时,输出组合结果
			// System.out.println(Arrays.asList(resultList));
			List<Double> resultList1 = new ArrayList<>();
			for (int i = 0; i < resultList.length; i++) {
				resultList1.add(Double.parseDouble(resultList[i]));
			}
			double sum1 = resultList1.stream().mapToDouble(Double::doubleValue).sum();
			// System.out.println("sum1="+sum1);
			if (sum1 <= SUM) {
				Compose compose = new MyInvoiceV3().new Compose(resultList1, sum1);
				composeList.add(compose);
			}

			return;
		}

		// 递归选择下一个
		for (int i = dataIndex; i < dataList.length + resultCount - resultLen; i++) {
			resultList[resultIndex] = dataList[i];
			combinationSelect(dataList, i + 1, resultList, resultIndex + 1);
		}
	}

}

  

这个方法我是直接用别人写的用Java实现排列、组合算法_codertcm的博客-CSDN博客

自己只是针对我的需求做了一些小修改。

3.2、用二进制罗列所有可能的数字组合并一一计算出它们的和

package Algorithm;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class MyInvoiceV2 {

	class Compose {
		List<Double> list;
		Double sum;

		Compose(List<Double> list, Double sum1) {
			this.list = list;
			this.sum = sum1;
		}

		Double getSum() {
			return this.sum;
		}

		public String toString() {
			// 把double转成带2个小数点的十进制数
			return list.toString() + ":" + new BigDecimal(sum).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
		}
	}

	public static void main(String[] args) {
        //一组发票的金额
		double[] data = new double[] { 10.1, 200.14, 300, 450.91, 556.5, 226, 10.1, 200.14, 300, 450.91 };
        //代码里的1000表示和不能超过的最大金额
		for (Compose c : binaryCal(data, 1000).stream().sorted(Comparator.comparing(Compose::getSum).reversed())
				.collect(Collectors.toList())) {
			System.out.println(c);
		}

	}

	public static List<Compose> binaryCal(double[] a, double m) {
		MyInvoiceV2 t = new MyInvoiceV2();
		List<Compose> composeList = new ArrayList<Compose>();
		int n = a.length;
		// 最多有2的n次方种组合
		int max = 1 << n;
		for (int i = 1; i < max; i++) {
			// 转为二进制
			String binaryNum = Integer.toBinaryString(i);
			// 转成相同的位数,不足n位的在前补0
			// 用二进制表示所有可能的组合
			binaryNum = toSameLen(binaryNum, n);
			char[] bitNum = binaryNum.toCharArray();
			List<Double> sumList = new ArrayList<Double>();
			double sum = 0;
			for (int j = 0; j < bitNum.length; j++) {
				// 二进制数当前位置为1,则加起来
				if (bitNum[j] == '1') {
					sumList.add(a[j]);
					sum += a[j];
				}
			}
			// 和小于等于m了,则记录下来
			if (sum <= m) {
				Compose compose = t.new Compose(sumList, sum);
				composeList.add(compose);
			}
		}
		return composeList;
	}

	private static String toSameLen(String binaryNum, int len) {
		// 数的长度
		int numLen = binaryNum.length();
		if (numLen == len) {
			return binaryNum;
		}
		StringBuilder sb = new StringBuilder();
		// 差几位补几个0
		for (int i = 0; i < len - numLen; i++) {
			sb.append(0);
		}
		return sb.append(binaryNum).toString();
	}

}

我觉得这种方法很巧妙,是用了这篇文章作者的代码Java笔试题:在一个数组中,求出所有和为m的组合_Listener10的博客-CSDN博客

用了二进制来罗列所有的可能组合。虽然也是穷举,但我没有想到。

4、总结

        无

5、参考资料

java排列组合(递归算法)_Ansel_TbN1的博客-CSDN博客_递归法求排列组合公式

Java笔试题:在一个数组中,求出所有和为m的组合_Listener10的博客-CSDN博客

用Java实现排列、组合算法_codertcm的博客-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/136762.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity 3D JavaScript 脚本基础||Unity 3D C# 脚本基础

变量 数值变量。var a1000;var b3.1415926;字符串变量。 是由单引号或双引号括起来的 Unicode 字符序列。 布尔值。 只有 true 和 false 。用来描述某个事物为真或为假。 数组 是数据的集合&#xff0c;数组中的每一个元素都有自己独有的下标&#xff0c;下标从0开始计数。 表…

《收获,不止Oracle》读书笔记之第三章逻辑体系世界

1.数据库有序的逻辑体系 2.逻辑体系从老余养殖细细说起 推出的逻辑结构是&#xff1a;表空间&#xff08;tablespace&#xff09;、段&#xff08;segment&#xff09;、区&#xff08;extent&#xff09;、块&#xff08;block&#xff09;。Oracle server正是有条理地通过表空…

基础数学(七)——线性方程组的数值解法

文章目录考试要求基础知识一般求解思路消元法高斯消元法小主元导致的计算失误高斯列主元素消去法&#xff08;期末考试要求之一&#xff09;高斯消元法例题&#xff08;期末必考&#xff09;高斯消元法的优缺点减少fill-in现象&#xff08;了解&#xff09;矩阵分解Doolittle分…

【ROS】——常用命令(五)

文章目录前言1. rosnode1.1 rosnode list 列出活动节点1.2 rosnode ping 测试到节点的连接状态1.3 rosnode info 打印节点信息1.4 rosnode machine 列出指定设备上节点1.5 rosnode kill 杀死某个节点1.6 rosnode cleanup 清除不可连接的节点2. rostopic2.1 rostopic list(-v)2.…

【学习】深度强化学习

李宏毅深度学习一、深度强化学习Deep Reinforcement Learning (RL)什么是RL&#xff1f;&#xff08;三步骤&#xff09;计算loss计算A版本0版本1版本2版本32、梯度方法收集训练数据:探索3、actor criticMCTD版本3.5一、深度强化学习Deep Reinforcement Learning (RL) 什么是R…

【回答问题】ChatGPT上线了!给我推荐20个比较流行的图像语义分割模型

给我推荐20个比较流行的图像语义分割模型 在这里列出20个比较流行的图像语义分割模型&#xff1a;U-Net Mask R-CNN Fully Convolutional Network (FCN) SegNet DeepLab v3 PSPNet ENet ICNet RefineNet DenseASPP DenseNet LinkNet FPN BiSeNet ENAS Deeplab v3 GCN CRF-RNN …

Linux环境变量

文章目录什么是环境变量&#xff1f;什么是本地变量&#xff1f;如何获取环境变量&#xff1f;在shell中在程序中如何添加/删除环境变量&&本地变量&#xff1f;在shell中在程序中为什么环境变量能够被子进程继承&#xff1f;什么是环境变量&#xff1f; 环境变量是进程…

Vue初识系列【1】

文章目录一 前端体系1.1 前端三要素1.2 JavaScript 框架1.3 JavaScript 构建工具1.4 前端所需后端技术1.5 UI框架二 前后端分离史2.1 后端为主的 MVC 时代&#xff08;web1.0&#xff09;2.2 基于 AJAX 带来的 SPA 时代(web2.0)2.3 前端为主的 MV* 时代2.4 NodeJS 带来的全栈时…

OBS 进阶 音频面板优化

因为,面板高度就那么大,如果声音源很多的话,就有点乱。 优化目的:静音的,自动放在底部,这样,音频面板上面的都是没有静音的,也是我们最关注的部分。 目录 一、音频面板优化 1、不想要音频面板的title,将其去掉

【LeetCode每日一题】【2023/1/3】2042. 检查句子中的数字是否递增

文章目录2042. 检查句子中的数字是否递增方法1&#xff1a;直接遍历写法2&#xff1a;按本题特有条件方法2&#xff1a;栈方法3&#xff1a;std::stringstream写法22042. 检查句子中的数字是否递增 LeetCode: 2042. 检查句子中的数字是否递增 简单\color{#00AF9B}{简单}简单 句…

202301-第一周资讯

大家好&#xff0c;欢迎来到本周资讯&#xff0c;在过去的一周内呢&#xff0c;我们在示例DEMO、文档、教学视频上都有了较多的产出并且帮助大家解决了不少问题&#xff0c;赶紧看看上周成果吧&#xff01; 目录 DEMO 动态TopN报表 导出图文报告的脚本示例 Superpage pc端…

【FPGA】基本实验步骤演示 | Verilog编码 | 运行合成 | 设备/引脚分配 | 综合/实施 | 设备配置

写在前面&#xff1a;本章的目的是让你理解与门、或门和非门的行为&#xff0c;并使用 Verilog 语言实现多输入与门、或门和非门。在生成输入信号之后&#xff0c;你需要通过模拟来验证这些门的操作&#xff0c;并使用 FPGA 来验证 Verilog 实现的电路的行为。 0x00 引入&#…

Vue+Echarts监控大屏实例十六:Echarts对接天地图插件开发

一、实例概述 本实例实现echarts使用天地图组件的开发,通过修改echarts中百度地图及高德地图插件实现echarts中使用天地图的使用。本实例实现对于监控界面的相关开发资料,提供实例源码、开发过程视频及实现过程。   1. Echarts参考文档: echarts   2. 高德地图插件:ech…

P1455 搭配购买

题目描述 明天就是母亲节了&#xff0c;电脑组的小朋友们在忙碌的课业之余挖空心思想着该送什么礼物来表达自己的心意呢&#xff1f;听说在某个网站上有卖云朵的&#xff0c;小朋友们决定一同前往去看看这种神奇的商品&#xff0c;这个店里有 nn 朵云&#xff0c;云朵已经被老板…

非Web服务弱口令检查工具下载与使用

今天继续给大家介绍渗透测试相关知识&#xff0c;本文主要内容是非Web服务弱口令检查工具下载与使用。 免责声明&#xff1a; 本文所介绍的内容仅做学习交流使用&#xff0c;严禁利用文中技术进行非法行为&#xff0c;否则造成一切严重后果自负&#xff01; 再次强调&#xff1…

操作系统的运行机制和体系结构

文章目录&#x1f380;前言&#xff1a;本篇博客知识总览&#x1f3c5;运行机制&#x1f387;两种指令&#x1f387;两种状态&#x1f387;两种程序&#x1f3c5;操作系统的内核&#x1f387;内核概念&#xff1a;&#x1f3c5;操作系统的体系结构&#x1f3af;最常考点&#x…

DevOps - Jenkins可视化流水线(后端部分)

目录 &#x1f9e1;创建DevOps工程 &#x1f9e1;拉取代码 &#x1f9e1;项目编译 &#x1f9e1;构建镜像 &#x1f9e1;推送镜像 &#x1f9e1;部署到Dev环境 &#x1f9e1;邮件功能 &#x1f9e1;激活微服务 &#x1f49f;这里是CS大白话专场&#xff0c;让枯燥的学习…

zookeeper下载安装

1、环境准备 ZooKeeper服务器是用Java创建的&#xff0c;它运行在JVM之上。需要安装JDK 7或更高版本。 2、上传 将下载的ZooKeeper放到/opt/ZooKeeper目录下 #上传zookeeper altp put f:/setup/apache-zookeeper-3.5.6-bin.tar.gz #打开 opt目录 cd /opt #创建zooKeeper目录…

电子招标采购系统源码之什么是电子招投标系统?

随着互联网时代的到来&#xff0c;各行业都受到不同的影响&#xff0c;其中招投标行业也不例外。为了顺应互联网潮流的发展&#xff0c;电子招投标逐渐取代传统的纸质的招投标方式&#xff0c;给招标方、投标方、招标代理等各方也带来了前所未有的机遇与挑战。那么什么是电子招…

日志采集 - Filebeat

Filebeat 是什么&#xff1f; Filebeat是本地文件的日志数据采集器&#xff0c;可监控日志目录或特定日志文件&#xff08;tail file&#xff09;&#xff0c;并将它们转发给Elasticsearch或Logstatsh进行索引、kafka等。 是使用 Golang 实现的轻量型日志采集器&#xff0c;也…