| | |
| | | loadMore: 'Load More Data', |
| | | complete: 'Complete', |
| | | deprecate: 'Deprecate', |
| | | stockError: 'Insuff stock ', |
| | | inputPlaceholder: 'Use commas to separate', |
| | | resend: 'RESEND', |
| | | selected: 'selected', |
| | |
| | | anfme: "anfme", |
| | | workQty: "workQty", |
| | | qty: "Qty", |
| | | stockLocs: "Stock Locs" |
| | | stockLocs: "Stock Locs", |
| | | stockQty: "Stock Qty", |
| | | |
| | | }, |
| | | task: { |
| | | taskCode: "TaskCode", |
| | |
| | | loadMore: '加载更多', |
| | | complete: '完成', |
| | | deprecate: '废弃', |
| | | stockError: '库存不足', |
| | | resend: '重发', |
| | | selected: '项选中', |
| | | batch: '批量编辑' |
| | |
| | | anfme: "数量", |
| | | workQty: "执行数", |
| | | qty: "完成数量", |
| | | stockLocs: "库存位置" |
| | | stockLocs: "库存位置", |
| | | stockQty: "库存数量", |
| | | }, |
| | | task: { |
| | | taskCode: "任务号", |
| | |
| | | ListContextProvider, |
| | | useList, |
| | | Toolbar, |
| | | SingleFieldList, |
| | | } from 'react-admin'; |
| | | |
| | | import { PAGE_DRAWER_WIDTH, OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting'; |
| | | import { Box, Typography, Card, Stack, DialogContent, DialogTitle, DialogActions, Dialog } from '@mui/material'; |
| | | import { Box, Typography, Card, Stack, DialogContent, DialogTitle, DialogActions, Dialog, Chip, ListItem, Paper } from '@mui/material'; |
| | | import { styled } from '@mui/material/styles'; |
| | | import DialogCloseButton from "../../components/DialogCloseButton"; |
| | | import request from '@/utils/request'; |
| | |
| | | <NumberField source="waveId" label="table.field.waveItem.waveId" /> |
| | | <TextField source="waveCode" label="table.field.waveItem.waveCode" /> |
| | | <TextField source="orderCode" label="table.field.waveItem.orderCode" /> |
| | | {/* <TextField source="maktx" label="table.field.waveItem.matnrName" /> */} |
| | | <NumberField source="matnrId" label="table.field.waveItem.matnrId" /> |
| | | <TextField source="matnrCode" label="table.field.waveItem.matnrCode" /> |
| | | <TextField source="batch" label="table.field.waveItem.batch" /> |
| | |
| | | <NumberField source="anfme" label="table.field.waveItem.anfme" /> |
| | | <NumberField source="workQty" label="table.field.waveItem.workQty" /> |
| | | <NumberField source="qty" label="table.field.waveItem.qty" /> |
| | | <NumberField source="stockQty" label="table.field.waveItem.stockQty" /> |
| | | <WrapperField cellClassName="opt" label="table.field.waveItem.stockLocs"> |
| | | <ArrayField source="stockLocs" stockLocs="table.field.waveItem.stockLocs"> |
| | | <SingleFieldList linkType={false}> |
| | | <ChilpField source="" size="small"></ChilpField> |
| | | </SingleFieldList> |
| | | </ArrayField> |
| | | {/* <NumberField source="anfme" label="table.field.waveItem.stockLocs" /> */} |
| | | {/* <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} /> |
| | | <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} /> */} |
| | | <TagsField /> |
| | | </WrapperField> |
| | | </StyledDatagrid> |
| | | </ListContextProvider> |
| | | </DialogContent> |
| | | <DialogActions> |
| | | <Toolbar sx={{ width: '100%', justifyContent: 'end' }} > |
| | | <GenerateTaskButton record={[record?.id]} /> |
| | | <GenerateTaskButton record={[record?.id]} dataSource={data} /> |
| | | </Toolbar> |
| | | </DialogActions> |
| | | </Dialog> |
| | |
| | | |
| | | export default ItemToTaskModal; |
| | | |
| | | const GenerateTaskButton = (record) => { |
| | | const GenerateTaskButton = (record, dataSource) => { |
| | | const refresh = useRefresh(); |
| | | const notify = useNotify(); |
| | | const redirect = useRedirect(); |
| | | const generateTask = async () => { |
| | | const res = await request.post(`/wave/public/task`, { ids: record?.record }); |
| | | const res = await request.post(`/wave/public/task`, { wave: dataSource }); |
| | | if (res?.data?.code === 200) { |
| | | notify(res.data.msg); |
| | | redirect("/task") |
| | |
| | | refresh(); |
| | | } |
| | | return (<Button variant="contained" label={"ra.action.save"} onClick={generateTask}></Button>) |
| | | } |
| | | |
| | | const TagsField = () => { |
| | | const record = useRecordContext(); |
| | | const translate = useTranslate(); |
| | | const locs = JSON.parse(record.stockLocs); |
| | | if (locs == undefined || locs.length < 1) { |
| | | return ( |
| | | <> |
| | | <ListItem> |
| | | <Chip color="error" label={translate("common.action.stockError")} variant="outlined" /> |
| | | </ListItem> |
| | | </> |
| | | ) |
| | | } else { |
| | | return ( |
| | | <> |
| | | {locs.map((data) => { |
| | | return ( |
| | | <ListItem key={data?.id}> |
| | | <Chip label={data?.locCode} /> |
| | | </ListItem> |
| | | ) |
| | | })} |
| | | </> |
| | | ) |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | |
| | | public class CombinationFinder { |
| | | |
| | | public List<Integer> findCombination(double[] nums, double target) { |
| | | // 处理等值情况 |
| | | List<Integer> equal = findEqual(nums, target); |
| | | if (equal != null && !equal.isEmpty()) { |
| | | return equal; |
| | | } |
| | | |
| | | // 处理大于的情况 |
| | | List<Integer> greater = findGreater(nums, target); |
| | | if (greater != null && !greater.isEmpty()) { |
| | | return greater; |
| | | } |
| | | |
| | | // 处理小于的情况 |
| | | List<Integer> less = findLess(nums, target); |
| | | return less != null ? less : new ArrayList<>(); // 确保不返回null |
| | | } |
| | | |
| | | private List<Integer> findEqual(double[] nums, double target) { |
| | | int n = nums.length; |
| | | double[] dp = new double[(int) (target * 100 + 1)]; // 放大100倍处理精度问题 |
| | | Arrays.fill(dp, Double.MAX_VALUE); |
| | | dp[0] = 0; |
| | | int[] prev = new int[(int) (target * 100 + 1)]; |
| | | Arrays.fill(prev, -1); |
| | | |
| | | for (int j = 0; j < n; j++) { |
| | | int num = (int) (nums[j] * 100); // 放大100倍处理精度问题 |
| | | for (int i = (int) (target * 100); i >= num; i--) { |
| | | if (dp[i - num] != Double.MAX_VALUE && dp[i - num] + 1 < dp[i]) { |
| | | dp[i] = dp[i - num] + 1; |
| | | prev[i] = j; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (dp[(int) (target * 100)] == Double.MAX_VALUE) { |
| | | return null; |
| | | } |
| | | |
| | | List<Integer> indices = new ArrayList<>(); |
| | | int current = (int) (target * 100); |
| | | while (current > 0) { |
| | | int j = prev[current]; |
| | | if (j == -1) return null; |
| | | indices.add(j); |
| | | current -= (int) (nums[j] * 100); |
| | | } |
| | | |
| | | return indices; |
| | | } |
| | | |
| | | private List<Integer> findGreater(double[] nums, double target) { |
| | | List<double[]> sorted = new ArrayList<>(); |
| | | for (int i = 0; i < nums.length; i++) { |
| | | sorted.add(new double[]{nums[i], i}); |
| | | } |
| | | sorted.sort((a, b) -> Double.compare(b[0], a[0])); |
| | | |
| | | double sum = 0; |
| | | List<Integer> indices = new ArrayList<>(); |
| | | for (double[] pair : sorted) { |
| | | sum += pair[0]; |
| | | indices.add((int) pair[1]); |
| | | if (sum > target) { |
| | | return indices; |
| | | } |
| | | } |
| | | |
| | | return null; |
| | | } |
| | | |
| | | private List<Integer> findLess(double[] nums, double target) { |
| | | int n = nums.length; |
| | | List<Integer> bestIndices = new ArrayList<>(); |
| | | double[] maxSum = {-1.0}; |
| | | for (int k = 1; k <= n; k++) { |
| | | List<Integer> currentIndices = new ArrayList<>(); |
| | | double[] currentMax = {-1.0}; |
| | | backtrack(nums, target, 0, k, 0.0, 0, currentIndices, currentMax, new ArrayList<>()); |
| | | if (currentMax[0] != -1.0) { |
| | | if (currentMax[0] > maxSum[0] || (currentMax[0] == maxSum[0] && currentIndices.size() < bestIndices.size())) { |
| | | maxSum[0] = currentMax[0]; |
| | | bestIndices = new ArrayList<>(currentIndices); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return bestIndices.isEmpty() ? null : bestIndices; |
| | | } |
| | | |
| | | private void backtrack(double[] nums, double target, int start, int k, double currentSum, int count, List<Integer> currentIndices, double[] currentMax, List<Integer> path) { |
| | | if (count == k) { |
| | | if (currentSum <= target && currentSum > currentMax[0]) { |
| | | currentMax[0] = currentSum; |
| | | currentIndices.clear(); |
| | | currentIndices.addAll(path); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | for (int i = start; i < nums.length; i++) { |
| | | if (currentSum + nums[i] > target) continue; |
| | | path.add(i); |
| | | backtrack(nums, target, i + 1, k, currentSum + nums[i], count + 1, currentIndices, currentMax, path); |
| | | path.remove(path.size() - 1); |
| | | } |
| | | } |
| | | |
| | | public static void main(String[] args) { |
| | | CombinationFinder finder = new CombinationFinder(); |
| | | double[] nums = {3.0, 1.0, 4.0, 2.0}; |
| | | double target = 0.1; |
| | | List<Integer> result = finder.findCombination(nums, target); |
| | | System.out.println("最优组合的索引: " + result); // 例如,等值组合可能为索引2(4.0)和3(1.0) |
| | | } |
| | | } |
| | |
| | | ExcelUtil.build(ExcelUtil.create(waveService.list(), Wave.class), response); |
| | | } |
| | | |
| | | |
| | | |
| | | @PreAuthorize("hasAuthority('manager:wave:update')") |
| | | @ApiOperation("波次下发任务") |
| | | @PostMapping("/wave/public/task") |
| | | public R publicTask(@RequestBody Map<String, Object> map) { |
| | | if (Cools.isEmpty(map)) { |
| | | if (Cools.isEmpty(map) || Cools.isEmpty(map.get("wave"))) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | return waveService.publicTask(map); |
| | |
| | | @ApiOperation("波次出库任务预览") |
| | | @PostMapping("/wave/locs/preview/page") |
| | | public R mergeWavePreview(@RequestBody Map<String, Object> map) { |
| | | if (Cools.isEmpty(map.get("waveId")) || StringUtils.isBlank(map.get("waveId").toString())) { |
| | | if (Cools.isEmpty(map.get("wave")) || StringUtils.isBlank(map.get("wave").toString())) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | Long waveId = Long.parseLong(map.get("waveId").toString()); |
| | |
| | | @ApiModelProperty(value= "修改人员") |
| | | private Long updateBy; |
| | | |
| | | /** |
| | | * 目标库位 |
| | | */ |
| | | @ApiModelProperty("目标库位") |
| | | @TableField(exist = false) |
| | | private String stockLocs; |
| | | |
| | | /** |
| | | * 库存数量 |
| | | */ |
| | | @ApiModelProperty("库存数量") |
| | | @TableField(exist = false) |
| | | private Double stockQty; |
| | | |
| | | /** |
| | | * 备注 |
| | | */ |
| | | @ApiModelProperty(value= "备注") |
| | |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.vincent.rsf.server.manager.utils.OptimalAlgorithmUtil; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.BeanUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | import java.util.stream.IntStream; |
| | | |
| | | @Service("waveService") |
| | | public class WaveServiceImpl extends ServiceImpl<WaveMapper, Wave> implements WaveService { |
| | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public R publicTask(Map<String, Object> map) { |
| | | List<Long> ids = (List<Long>) map.get("ids"); |
| | | if (Objects.isNull(ids) || ids.isEmpty()) { |
| | | List<WaveItem> itemParams = (List<WaveItem>) map.get("wave"); |
| | | if (Objects.isNull(itemParams) || itemParams.isEmpty()) { |
| | | throw new CoolException("参数不能为空!!"); |
| | | } |
| | | List<Long> ids = itemParams.stream().map(WaveItem::getWaveId).collect(Collectors.toList()); |
| | | List<Wave> waves = this.list(new LambdaQueryWrapper<Wave>().in(Wave::getId, ids)); |
| | | if (Objects.isNull(waves) || waves.isEmpty()) { |
| | | throw new CoolException("波次数据不存在!!"); |
| | |
| | | throw new CoolException("波次明细不存在!!"); |
| | | } |
| | | List<Long> orderIds = waveItems.stream().map(WaveItem::getOrderId).collect(Collectors.toList()); |
| | | |
| | | /**查询每条明细匹配的库位*/ |
| | | try { |
| | | List<WaveItem> items = getLocs(waveItems); |
| | | // List<WaveItem> items = getLocs(waveItems); |
| | | } catch (Exception e) { |
| | | throw new CoolException("库位获取失败!!!"); |
| | | } |
| | |
| | | // 2. 根据物料SKU寻找符合物料库位 {1. 根据物料编码,批次,动态字段 查询符合的库位,再根据库位中物料的数量选择最适合的库位 2. 判断当前订单是全拖出库还是拣料入库} |
| | | // 3. 修改主单、波次执行数量 |
| | | // 4. 判断全仓出库或拣料出库 |
| | | List<AsnOrder> orders = asnOrderService.list(new LambdaQueryWrapper<AsnOrder>().in(AsnOrder::getId, orderIds)); |
| | | /**修改原出库单状态*/ |
| | | if (!asnOrderService.update(new LambdaQueryWrapper<AsnOrder>() |
| | | .eq(AsnOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_WORKING.val) |
| | | .in(AsnOrder::getId, orders))) { |
| | | throw new CoolException("出库单据状态修改失败!!"); |
| | | } |
| | | if (!this.update(new LambdaUpdateWrapper<Wave>().set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_TASK).in(Wave::getId, ids))) { |
| | | throw new CoolException("波次状态修改失败!!"); |
| | | } |
| | | // List<AsnOrder> orders = asnOrderService.list(new LambdaQueryWrapper<AsnOrder>().in(AsnOrder::getId, orderIds)); |
| | | // /**修改原出库单状态*/ |
| | | // if (!asnOrderService.update(new LambdaQueryWrapper<AsnOrder>() |
| | | // .eq(AsnOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_WORKING.val) |
| | | // .in(AsnOrder::getId, orders))) { |
| | | // throw new CoolException("出库单据状态修改失败!!"); |
| | | // } |
| | | // if (!this.update(new LambdaUpdateWrapper<Wave>().set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_TASK).in(Wave::getId, ids))) { |
| | | // throw new CoolException("波次状态修改失败!!"); |
| | | // } |
| | | return R.ok(); |
| | | } |
| | | |
| | |
| | | .eq(LocItem::getSplrBatch, waveItem.getSplrBatch()) |
| | | .eq(StringUtils.isNotBlank(waveItem.getFieldsIndex()), LocItem::getFieldsIndex, waveItem.getFieldsIndex()) |
| | | .groupBy(LocItem::getMatnrCode, LocItem::getSplrBatch, LocItem::getFieldsIndex, LocItem::getId)); |
| | | List<Double> doubles1 = locItems.stream().map(LocItem::getAnfme).collect(Collectors.toList()); |
| | | double[] doubles = doubles1.stream().mapToDouble(Double::doubleValue).toArray(); |
| | | |
| | | Double[] doubles = locItems.stream().map(LocItem::getAnfme).toArray(Double[]::new); |
| | | List<Double> result = OptimalAlgorithmUtil.findBestCombination(doubles, waveItem.getAnfme()); |
| | | String locs = JSONArray.toJSONString(new ArrayList<>()); |
| | | if (!locItems.isEmpty()) { |
| | | List<String> codes = locItems.stream().map(LocItem::getLocCode).collect(Collectors.toList()); |
| | | locs = JSONArray.toJSONString(codes); |
| | | /**使用回溯算法计算,获取符合出库量的最简组合*/ |
| | | List<Integer> result = OptimalAlgorithmUtil.findCombination(doubles, waveItem.getAnfme()); |
| | | String locs = "[]"; |
| | | if (Objects.isNull(result)) { |
| | | waveItem.setStockLocs(locs).setStockQty(0.0); |
| | | } else { |
| | | /**过滤集合中最简短的组合*/ |
| | | List<LocItem> locsInfo = result.stream() |
| | | .filter(i -> i >= 0 && i < locItems.size()) |
| | | .map(locItems::get).collect(Collectors.toList()); |
| | | locs = JSONArray.toJSONString(locsInfo); |
| | | double sumQty = locItems.stream().mapToDouble(LocItem::getAnfme).sum(); |
| | | double surQty = locItems.stream().mapToDouble(LocItem::getWorkQty).sum(); |
| | | double qty = locItems.stream().mapToDouble(LocItem::getQty).sum(); |
| | | double v = sumQty - surQty - qty; |
| | | waveItem.setStockLocs(locs).setStockQty(v); |
| | | } |
| | | waveItem.setStockLocs(locs); |
| | | }); |
| | | return waveItems; |
| | | } |
| | |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * @param |
| | | * @author Ryan |
| | | * @description 根据对象多个属性合并处理 |
| | | * @param |
| | | * @return |
| | | * @time 2025/4/25 10:55 |
| | | */ |
| | |
| | | } |
| | | |
| | | /** |
| | | * @param |
| | | * @author Ryan |
| | | * @description 用于存储多个分组键的内部类 |
| | | * @param |
| | | * @return |
| | | * @time 2025/4/25 10:56 |
| | | */ |
| | |
| | | } |
| | | |
| | | |
| | | public static List<Double> findBestCombination(Double[] candidates, double targetSum) { |
| | | bestSolution = null; |
| | | target = targetSum; |
| | | minDifference = Double.MAX_VALUE; |
| | | Arrays.sort(candidates); |
| | | backtrack(candidates, 0, new ArrayList<>(), 0.0); |
| | | |
| | | // 如果没有精确解,返回最接近的近似解 |
| | | return bestSolution != null ? bestSolution : new ArrayList<>(); |
| | | } |
| | | |
| | | private static void backtrack(Double[] candidates, int start, |
| | | List<Double> current, double currentSum) { |
| | | // 计算当前和与目标的差值 |
| | | double difference = Math.abs(currentSum - target); |
| | | |
| | | // 如果找到更优解(差值更小或差值相同但组合更短) |
| | | if (difference < minDifference - EPSILON || |
| | | (Math.abs(difference - minDifference) < EPSILON && |
| | | (bestSolution == null || current.size() < bestSolution.size()))) { |
| | | minDifference = difference; |
| | | bestSolution = new ArrayList<>(current); |
| | | /** |
| | | * @param |
| | | * @return |
| | | * @author Ryan |
| | | * @description 获取全拖或非全拖库位 |
| | | * @time 2025/4/28 09:41 |
| | | */ |
| | | public static List<Integer> findCombination(double[] nums, Double target) { |
| | | // 处理等值情况 |
| | | List<Integer> equal = findEqual(nums, target); |
| | | if (equal != null && !equal.isEmpty()) { |
| | | return equal; |
| | | } |
| | | |
| | | // 如果已经找到精确解,不需要继续搜索更长的组合 |
| | | if (minDifference < EPSILON) { |
| | | // 处理大于的情况 |
| | | List<Integer> greater = findGreater(nums, target); |
| | | if (greater != null && !greater.isEmpty()) { |
| | | return greater; |
| | | } |
| | | |
| | | // 处理小于的情况 |
| | | List<Integer> less = findLess(nums, target); |
| | | return less != null ? less : new ArrayList<>(); // 确保不返回null |
| | | } |
| | | |
| | | /** |
| | | * @author Ryan |
| | | * @description 使用动态规划寻找和等于目标值的最少元素组合。动态规划数组dp记录达到每个和所需的最少元素数目,prev数组记录路径以便回溯找到具体索引。 |
| | | * @param |
| | | * @return |
| | | * @time 2025/4/28 12:33 |
| | | */ |
| | | private static List<Integer> findEqual(double[] nums, double target) { |
| | | int n = nums.length; |
| | | double[] dp = new double[(int) (target * 100 + 1)]; // 放大100倍处理精度问题 |
| | | Arrays.fill(dp, Double.MAX_VALUE); |
| | | dp[0] = 0; |
| | | int[] prev = new int[(int) (target * 100 + 1)]; |
| | | Arrays.fill(prev, -1); |
| | | |
| | | for (int j = 0; j < n; j++) { |
| | | int num = (int) (nums[j] * 100); // 放大100倍处理精度问题 |
| | | for (int i = (int) (target * 100); i >= num; i--) { |
| | | if (dp[i - num] != Double.MAX_VALUE && dp[i - num] + 1 < dp[i]) { |
| | | dp[i] = dp[i - num] + 1; |
| | | prev[i] = j; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (dp[(int) (target * 100)] == Double.MAX_VALUE) { |
| | | return null; |
| | | } |
| | | |
| | | List<Integer> indices = new ArrayList<>(); |
| | | int current = (int) (target * 100); |
| | | while (current > 0) { |
| | | int j = prev[current]; |
| | | if (j == -1) return null; |
| | | indices.add(j); |
| | | current -= (int) (nums[j] * 100); |
| | | } |
| | | |
| | | return indices; |
| | | } |
| | | |
| | | /** |
| | | * @author Ryan |
| | | * @description 将数组降序排序后,贪心地累加元素直到总和超过目标值,返回这些元素的索引 |
| | | * @param |
| | | * @return |
| | | * @time 2025/4/28 12:34 |
| | | */ |
| | | private static List<Integer> findGreater(double[] nums, double target) { |
| | | List<double[]> sorted = new ArrayList<>(); |
| | | for (int i = 0; i < nums.length; i++) { |
| | | sorted.add(new double[]{nums[i], i}); |
| | | } |
| | | sorted.sort((a, b) -> Double.compare(b[0], a[0])); |
| | | |
| | | double sum = 0; |
| | | List<Integer> indices = new ArrayList<>(); |
| | | for (double[] pair : sorted) { |
| | | sum += pair[0]; |
| | | indices.add((int) pair[1]); |
| | | if (sum > target) { |
| | | return indices; |
| | | } |
| | | } |
| | | |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * @author Ryan |
| | | * @description 使用回溯法生成所有可能的组合,寻找总和最大但不超过目标值的组合,并记录元素数目最少的组合。 |
| | | * @param |
| | | * @return |
| | | * @time 2025/4/28 12:34 |
| | | */ |
| | | private static List<Integer> findLess(double[] nums, double target) { |
| | | int n = nums.length; |
| | | List<Integer> bestIndices = new ArrayList<>(); |
| | | double[] maxSum = {-1.0}; |
| | | for (int k = 1; k <= n; k++) { |
| | | List<Integer> currentIndices = new ArrayList<>(); |
| | | double[] currentMax = {-1.0}; |
| | | backtrack(nums, target, 0, k, 0.0, 0, currentIndices, currentMax, new ArrayList<>()); |
| | | if (currentMax[0] != -1.0) { |
| | | if (currentMax[0] > maxSum[0] || (currentMax[0] == maxSum[0] && currentIndices.size() < bestIndices.size())) { |
| | | maxSum[0] = currentMax[0]; |
| | | bestIndices = new ArrayList<>(currentIndices); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return bestIndices.isEmpty() ? null : bestIndices; |
| | | } |
| | | |
| | | private static void backtrack(double[] nums, double target, int start, int k, double currentSum, int count, List<Integer> currentIndices, double[] currentMax, List<Integer> path) { |
| | | if (count == k) { |
| | | if (currentSum <= target && currentSum > currentMax[0]) { |
| | | currentMax[0] = currentSum; |
| | | currentIndices.clear(); |
| | | currentIndices.addAll(path); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | // 遍历候选数字 |
| | | for (int i = start; i < candidates.length; i++) { |
| | | double num = candidates[i]; |
| | | |
| | | // 剪枝:如果当前和已经远大于目标值,跳过 |
| | | if (currentSum + num > target + minDifference + EPSILON) { |
| | | continue; |
| | | } |
| | | |
| | | // 跳过重复数字 |
| | | if (i > start && Math.abs(candidates[i] - candidates[i - 1]) < EPSILON) { |
| | | continue; |
| | | } |
| | | |
| | | current.add(num); |
| | | backtrack(candidates, i + 1, current, currentSum + num); |
| | | current.remove(current.size() - 1); |
| | | for (int i = start; i < nums.length; i++) { |
| | | if (currentSum + nums[i] > target) continue; |
| | | path.add(i); |
| | | backtrack(nums, target, i + 1, k, currentSum + nums[i], count + 1, currentIndices, currentMax, path); |
| | | path.remove(path.size() - 1); |
| | | } |
| | | } |
| | | |