华为OD机考算法题:最小数量线段覆盖

博客围绕华为OD一道难度较大的考题展开,题目是在一维坐标系中,求线段覆盖范围外至少取几个线段能覆盖这些范围。解题分排序、分段、回溯三步,排序按起点和终点大小,分段找出间断区间,回溯用递归穷举找最小线段数,还给出了Java和JavaScript代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

题目部分

解读与分析

代码实现


题目部分

题目最小数量线段覆盖
难度
题目说明给定坐标轴(一维坐标轴)上的一组线段,线段的起点和终点均为整数并且长度不小于1,请你从中找到最少数量的线段,这些线段可以覆盖住所有线段。
输入描述第一行输入为所有线段的数量,不超过 10000,后面每行表示一条线段,格式为”x,y",x 和 y 分别表示起点和终点,取值范围是 [-10^{5},10^{5}]。
输出描述最少线段数量,为正整数。
补充说明
------------------------------------------------------
示例
示例1
输入3
1,4
2,5

3,6
输出2
说明选取 2 条线段 [ 1,4 ] 和 [ 3,6 ],这两条线段可以覆盖 [ 2,5 ]。


解读与分析

题目解读

在一个一维坐标系中,有很多线段(通过起止点标识),在求出这些线段覆盖的范围之外,求这些线段中至少可以取几个就能覆盖这些范围。

分析与思路

在解答此题之前,先澄清一下,这些线段覆盖的范围可能是间断的区间。如 3 条线段 [1,4]、[2,5]、[6,7] 覆盖的区间是 [1,5]、[6,7]。

解答此题分三步:排序、分段、回溯。
1. 排序。对所有的线段排序。排序规则为,先对线段的起点进行排序,从小到大;如果起点相等,则对终点排序,也是从小到大。
2. 分段。在前文提到,这些线段覆盖的范围可能是间断的区间。所谓分段,就是找出这些间断的区间。
逐一遍历线段,如果后一个线段的起点大于前一个线段的终点,那么后一个线段就与前一个线段在不同的区间;否则,后一个线段一定与迁移线段处于同一个区间中。
所有位于不同区间的线段绝不会相交,因此,不同分段区间的线段可以单独统计,最后把所有区间的线段之和相加即可。
3. 回溯。对每个单独的区间,使用回溯算法,通过穷举得出所有可能覆盖的情况,从所有的情况中找出所需线段最小的情况。回溯可以使用递归实现,容易理解。

此题时间复杂度为 O(n^{2}),空间复杂度为 O(n)。


代码实现

Java代码

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;

/**
 * 最少线段覆盖
 * 
 * @since 2023.09.23
 * @version 0.1
 * @author Frank
 *
 */
public class MinLineCount {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		while (sc.hasNext()) {
			String input = sc.nextLine();
			int count = Integer.parseInt(input);

			List<int[]> lines = new ArrayList<>();
			for (int i = 0; i < count; i++) {
				input = sc.nextLine();
				String[] strStartEnd = input.split(",");
				int[] startEnd = new int[2];
				startEnd[0] = Integer.parseInt(strStartEnd[0]);
				startEnd[1] = Integer.parseInt(strStartEnd[1]);
				lines.add(startEnd);
			}
			processMinLineCount( lines);
		}
	}

	private static void processMinLineCount( List<int[]> lines) {
		// 1. 排序
		Comparator<int[]> cmp = new Comparator<int[]>() {
			@Override
			public int compare(int[] o1, int[] o2) {
				for (int i = 0; i < o1.length; i++) {
					if (o1[i] != o2[i]) {
						return o1[i] - o2[i];
					}
				}
				return 0;
			}
		};

		lines.sort(cmp);

		// 2. 分段
		class LinePart {
			int start; // 起始点,包含在内
			int end; // 终止点,包含在内
			int startIdx; // 对应lines中的起始下标,包含在内
			int endIdx; // 对应lines中的终止下标,包含在内
		}

		List<LinePart> lpList = new ArrayList<LinePart>();
		LinePart tmpLP = new LinePart();
		for (int i = 0; i < lines.size(); i++) {
			int[] tmpLine = lines.get(i);
			if (i == 0) {
				tmpLP.start = tmpLine[0];
				tmpLP.end = tmpLine[1];
				tmpLP.startIdx = 0;
				tmpLP.endIdx = 0;
				lpList.add( tmpLP );
				continue;
			}
			
			// 不是同一个区间
			if( tmpLine[0] > tmpLP.end )
			{
				tmpLP = new LinePart();
				tmpLP.start = tmpLine[0];
				tmpLP.end = tmpLine[1];
				tmpLP.startIdx = i;
				tmpLP.endIdx = i;
				lpList.add( tmpLP );
				continue;
			}else  // 同一个区间
			{
				tmpLP.end = tmpLine[1];
				tmpLP.endIdx = i;
			}
			
		}
		
		//3.逐段求和
		int count = 0;
		for( int i = 0; i < lpList.size(); i ++ )
		{
			LinePart lpPart = lpList.get( i );
			List<int[]> tmpList = new ArrayList<int[]>();
			for( int j = lpPart.startIdx; j <= lpPart.endIdx; j ++ )	//  j <= lpPart.endIdx,包含在内
			{
				int[] tmpLine = lines.get( j );
				int[] copy = new int[2];
				copy[0] = tmpLine[0];
				copy[1] = tmpLine[1];
				tmpList.add( copy );
			}
			int partCount = getPartMinCount( tmpLP.start, tmpLP.end, tmpList );
			count += partCount;
		}
		System.out.println( count );

	}
	
	private static int getPartMinCount( int start, int end, List<int[]> list)
	{
		if( start >= end )
		{
			return 0;
		}
		int minCount = list.size();
		for( int i = 0; i < list.size(); i ++ )
		{
			int tmpCount = 0;
			int[] cur = list.get( i );
			
			// 当它已经无法覆盖
			if( cur[0] > start )
			{
				break;
			}
			
			// 如果end小于start,那这样的线段根本不需要
			if( cur[1] < start  )
			{
				continue;
			}
			
			list.remove( i );
			
			tmpCount ++;
			tmpCount += getPartMinCount( cur[1], end, list);
			
			list.add( i, cur );
			if( tmpCount < minCount )
			{
				minCount = tmpCount;
			}
		}
		
		return minCount;
	}
}

JavaScript代码

const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function() {
    while (line = await readline()) {
        var count = parseInt(line);

        var lines = new Array();
        for (var i = 0; i < count; i++) {
            line = await readline();
            var strStartEnd = line.split(",");
            var startEnd = [];
            startEnd[0] = parseInt(strStartEnd[0]);
            startEnd[1] = parseInt(strStartEnd[1]);
            lines[i] = startEnd;
        }

        processMinLineCount(lines);
    }
}();

function compareLineFun(a, b) {
    for (var i = 0; i < a.length; i++) {
        if (a[i] != b[i]) {
            return a[i] - b[i];
        }
    }
    return 0;
}

function processMinLineCount(lines) {
    // 1. 排序
    lines.sort(compareLineFun);

    // 2. 分段

    // LinePart 的数据结构
    // LinePart  {
    //      start; // 起始点,包含在内
    //      end; // 终止点,包含在内
    //      startIdx; // 对应lines中的起始下标,包含在内
    //      endIdx; // 对应lines中的终止下标,包含在内
    //  }

    var lpList = new Array();
    var tmpLP = {};
    for (var i = 0; i < lines.length; i++) {
        var tmpLine = lines[i];
        if (i == 0) {
            tmpLP.start = tmpLine[0];
            tmpLP.end = tmpLine[1];
            tmpLP.startIdx = 0;
            tmpLP.endIdx = 0;
            lpList.push(tmpLP);
            continue;
        }

        // 不是同一个区间
        if (tmpLine[0] > tmpLP.end) {
            tmpLP = new LinePart();
            tmpLP.start = tmpLine[0];
            tmpLP.end = tmpLine[1];
            tmpLP.startIdx = i;
            tmpLP.endIdx = i;
            lpList.push(tmpLP);
            continue;
        } else // 同一个区间
        {
            tmpLP.end = tmpLine[1];
            tmpLP.endIdx = i;
        }

    }

    //3.逐段求和
    var count = 0;
    for (var i = 0; i < lpList.length; i++) {
        var lpPart = lpList[i];
        var tmpList = new Array();
        for (var j = lpPart.startIdx; j <= lpPart.endIdx; j++) //  j <= lpPart.endIdx,包含在内
        {
            var tmpLine = lines[j];
            var copy = [];
            copy[0] = tmpLine[0];
            copy[1] = tmpLine[1];
            tmpList.push(copy);
        }
        var partCount = getPartMinCount(tmpLP.start, tmpLP.end, tmpList);
        count += partCount;
    }
    console.log(count);
}

function getPartMinCount(start, end, list) {
    if (start >= end) {
        return 0;
    }
    var minCount = list.length;
    for (var i = 0; i < list.length; i++) {
        var tmpCount = 0;
        var cur = list[i];

        // 当它已经无法覆盖
        if (cur[0] > start) {
            break;
        }

        // 如果end小于start,那这样的线段根本不需要
        if (cur[1] < start) {
            continue;
        }

        list.splice(i, 1);

        tmpCount++;
        tmpCount += getPartMinCount(cur[1], end, list);

        list.splice(i, 0, cur);
        if (tmpCount < minCount) {
            minCount = tmpCount;
        }
    }

    return minCount;
}

此题难度有些大,从思考算法,到实现代码并通过测试,都有不少工作量。在华为 OD 考题中,属于难度较大的题。

(完)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值