Huawei OD コンピュータベースのテストアルゴリズムの質問: カバーする線分の最小数

目次

質問部分

解釈と分析

コード


質問部分

トピック カバーする線分の最小数
困難 災害
質問の説明 座標軸(1次元の座標軸)上に線分の集合が与えられたとき、その線分の始点と終点はいずれも整数で、長さは1以上になります。取り得る線分の最小数を求めてください。すべての線分をカバーします。
説明を入力してください 入力の最初の行は、10,000 を超えないすべての線分の数です。後続の各行は、「x,y」の形式で線分を表し、x と y はそれぞれ開始点と終了点、および値の範囲を表します。は [-, 10^{5}]です10^{5}
出力の説明 線分の最小数。正の整数です。
追加情報 なし
-------------------------------------------------- ----
例1
入力 3
1,4
2,5

3,6
出力 2
説明する [2,5] をカバーできる 2 つの線分 [1,4] と [3,6] を選択します。


解釈と分析

質問の解釈:

1 次元の座標系では、多くの線分 (始点と終点で識別される) が存在します。これらの線分がカバーする範囲を見つけることに加えて、これらの線分のうちカバーできる範囲を少なくともいくつか見つける必要があります。これらの範囲。

分析とアイデア:

この質問に答える前に、これらの線分によってカバーされる範囲が不連続な間隔である可能性があることを明確にしましょう。たとえば、3 つの線分 [1,4]、[2,5]、[6,7] によってカバーされる間隔は、[1,5]、[6,7] になります。

この質問に答えるには、並べ替え、セグメント化、バックトラッキングという 3 つのステップがあります。
1. 並べ替えます。すべての線分を並べ替えます。ソート ルールは、最初に線分の開始点を小さいものから大きいものにソートし、開始点が等しい場合は、終点も小さいものから大きいものにソートします。
2. セグメンテーション。前述したように、これらの線分でカバーされる範囲は不連続な区間であってもよい。いわゆるセグメンテーションは、これらの不連続な間隔を見つけることです。
線分を 1 つずつたどります。次の線分の始点が前の線分の終点より大きい場合、次の線分は前の線分と異なる間隔にあり、それ以外の場合は次の線分になります。セグメントは移行線セグメントと同じ間隔にある必要があります。
異なる間隔にあるすべての線分が交差することはないため、異なる分割間隔の線分を個別に数え、最終的にすべての間隔の線分の合計を加算できます。
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;
}

この質問は少し難しく、アルゴリズムを考えてからコードを実装してテストに合格するまで、多くの作業が必要です。Huawei OD試験問題の中でも、難しい問題です。

(以上)

おすすめ

転載: blog.csdn.net/ZiJinShi/article/details/133134874