演算法題系列4

MingYu15發表於2024-08-11

請設計一個檔案快取系統,該檔案快取系統可以指定快取的最大值(單位為位元組)。檔案快取系統有兩種操作:儲存檔案(put)和讀取檔案(get),操作命令為put fileName fileSize或者get fileName,儲存檔案是把檔案放入檔案快取系統中;讀取檔案是從檔案快取系統中訪問已存在的檔案,如果檔案不存在,則不作任何操作。當快取空間不足以存放新的檔案時,根據規則刪除檔案,直到剩餘空間滿足新的檔案大小為止,再存放新檔案。
具體的刪除規則為:檔案訪問過後,會更新檔案的最近訪問時間和總的訪問次數,當快取不夠時,按照第一優先順序為訪問次數從少到多,第二順序為時間從老到新的方式來刪除檔案。
輸入描述:
第一行為快取最大值m(整數,取值範圍為0<m<=52428800);第二行為 檔案操作序列個數n(0 <=n<= 300000)從第三行起為檔案操作序列,每個序列單獨一行檔案操作定義為"op file name file size"
file name是 檔名,file size是檔案大小
輸出描述
輸出當前檔案快取中的檔名列表,檔名用英文逗號分隔,按字典順序排序如:a.c
如果檔案快取中沒有檔案,則輸出NONE
補充說明:
1.如果新檔案的檔名和檔案快取中已有的檔名相同,則不會放在快取中
2.新的檔案第一次存入到檔案快取中時,檔案的總訪問次數不會變化

為了解決這個檔案快取系統的問題,我們需要設計一個高效的檔案快取系統,支援儲存和讀取操作,並在快取空間不足時,按照特定的刪除規則來清理舊檔案。下面是一個Java實現的詳細設計方案:

系統設計

  1. 資料結構

    • 使用 HashMap 儲存檔案資訊,其中鍵為檔名,值為 FileEntry 物件,FileEntry 記錄檔案的大小、訪問次數和最後訪問時間。
    • 使用一個 PriorityQueue 來根據檔案的訪問次數和最後訪問時間來優先刪除檔案。PriorityQueue 會根據檔案的訪問次數(從小到大)和最後訪問時間(從早到晚)排序。
  2. 操作處理

    • 儲存檔案 (put)

      • 如果檔案已經存在,忽略儲存操作。
      • 如果檔案不存在且快取空間足夠,直接儲存檔案。
      • 如果快取空間不足,根據刪除規則刪除舊檔案,直到有足夠的空間儲存新檔案。
    • 讀取檔案 (get)

      • 更新檔案的最後訪問時間和訪問次數。如果檔案不存在,忽略操作。
  3. 刪除檔案的規則

    • 優先刪除訪問次數少的檔案。如果多個檔案訪問次數相同,刪除最早被訪問的檔案。

Java 程式碼實現

import java.util.*;

public class FileCacheSystem {
    static class FileEntry {
        int size;
        int accessCount;
        long lastAccessTime;

        FileEntry(int size) {
            this.size = size;
            this.accessCount = 0;
            this.lastAccessTime = System.nanoTime();
        }
    }

    private final int maxSize;
    private int currentSize;
    private final Map<String, FileEntry> cache;
    private final PriorityQueue<Map.Entry<String, FileEntry>> evictionQueue;

    public FileCacheSystem(int maxSize) {
        this.maxSize = maxSize;
        this.currentSize = 0;
        this.cache = new HashMap<>();
        this.evictionQueue = new PriorityQueue<>(
                Comparator.comparingInt((Map.Entry<String, FileEntry> e) -> e.getValue().accessCount)
                          .thenComparingLong(e -> e.getValue().lastAccessTime)
        );
    }

    public void put(String fileName, int fileSize) {
        if (fileSize > maxSize) return; // Ignore files larger than cache size
        
        if (cache.containsKey(fileName)) return; // File already in cache

        while (currentSize + fileSize > maxSize) {
            if (evictionQueue.isEmpty()) break;

            Map.Entry<String, FileEntry> entry = evictionQueue.poll();
            FileEntry fileEntry = entry.getValue();
            cache.remove(entry.getKey());
            currentSize -= fileEntry.size;
        }

        if (currentSize + fileSize <= maxSize) {
            FileEntry newEntry = new FileEntry(fileSize);
            cache.put(fileName, newEntry);
            evictionQueue.offer(new AbstractMap.SimpleEntry<>(fileName, newEntry));
            currentSize += fileSize;
        }
    }

    public void get(String fileName) {
        if (!cache.containsKey(fileName)) return; // File not in cache

        FileEntry fileEntry = cache.get(fileName);
        fileEntry.accessCount++;
        fileEntry.lastAccessTime = System.nanoTime();

        evictionQueue.removeIf(entry -> entry.getKey().equals(fileName));
        evictionQueue.offer(new AbstractMap.SimpleEntry<>(fileName, fileEntry));
    }

    public void printCache() {
        if (cache.isEmpty()) {
            System.out.println("NONE");
            return;
        }

        List<String> sortedFileNames = new ArrayList<>(cache.keySet());
        Collections.sort(sortedFileNames);

        System.out.println(String.join(",", sortedFileNames));
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int maxSize = scanner.nextInt();
        int n = scanner.nextInt();
        scanner.nextLine(); // Consume newline

        FileCacheSystem fileCacheSystem = new FileCacheSystem(maxSize);

        for (int i = 0; i < n; i++) {
            String line = scanner.nextLine();
            String[] parts = line.split(" ");
            String operation = parts[0];
            String fileName = parts[1];
            int fileSize = Integer.parseInt(parts[2]);

            if ("put".equals(operation)) {
                fileCacheSystem.put(fileName, fileSize);
            } else if ("get".equals(operation)) {
                fileCacheSystem.get(fileName);
            }
        }

        fileCacheSystem.printCache();
        scanner.close();
    }
}

說明

  1. 檔案快取系統類

    • FileEntry 記錄檔案的大小、訪問次數和最後訪問時間。
    • FileCacheSystem 管理快取的最大容量、當前容量、快取資料及其優先佇列。
  2. 操作處理

    • put 操作會新增檔案到快取中,並處理快取空間不足的情況。
    • get 操作會更新檔案的訪問次數和最後訪問時間。
    • printCache 方法列印快取中的檔名,按字典順序排序。
  3. 效能考慮

    • PriorityQueue 的使用確保了能夠在 O(log N) 時間複雜度內進行優先順序排序和刪除操作。
    • HashMap 用於快速訪問檔案資訊和更新快取。
  4. 輸入和輸出

    • 輸入透過 Scanner 讀取,輸出透過 System.out.println 輸出快取中檔案的排序結果。

此實現有效地處理了檔案快取系統中的儲存、讀取和快取清理操作,能夠處理大規模的操作序列,並滿足題目要求。



題目描述
評估一個網路的訊號質量,其中一個做法是將網路劃分為柵格,然後對每個柵格的訊號質量計算,路測的時候,希望選擇一條訊號最好的路線(彼此相連的柵格集合)進行演示,現給出R行C列的整數陣列COV,每個單元格的數值S即為該柵格的訊號質量(已歸一化,無單位,值越大訊號越好)

要求從[0,0]到[R-1,C-1]設計一條最優路測路線。返回該路線得分。

規則:

  1. 路測路線可以上下左右四個方向,不能對角

  2. 路線的評分是以路線上訊號最差的柵格為準的,例如路徑8->4->5->9的值為 4,該線路評分為4。線路最優表示該條線路的評分最高。

輸入
第1行表示柵格的行數R

第2行表示柵格的列數C

第3行開始,每一行表示柵格地圖一行的訊號值,如5 4 5

輸出
最優路線的得分
樣例輸入
3
3
5 4 5
1 2 6
7 4 6
樣例輸出
4
提示
1 <= R,C <= 20

0 <= S <= 65535

要解決這個問題,我們需要找到一條從柵格左上角 (0,0) 到右下角 (R-1,C-1) 的路徑,使得路徑上的最小訊號質量最大。這是一個典型的最優路徑問題,其中路徑的評分是路徑上最小訊號質量的最大值。

思路

我們可以使用 最大最小路徑(Maximum Minimum Path) 的策略來解決這個問題。這種策略的核心思想是將所有路徑分數轉化為一個最大最小路徑問題。具體步驟如下:

  1. 定義目標

    • 我們希望找到一條路徑,使得路徑上所有柵格的最小訊號質量儘可能大。換句話說,找到路徑中最小訊號質量的最大值。
  2. 二分搜尋結合 BFS

    • 二分搜尋:在可能的訊號質量值範圍內進行二分搜尋,以確定路徑中最小訊號質量的最大可能值。
    • BFS(廣度優先搜尋):對於每個候選訊號質量值,使用 BFS 來檢查從 (0,0) 到 (R-1,C-1) 是否存在一條路徑,其中所有柵格的訊號質量都大於或等於該候選值。

具體實現

  1. 輸入處理

    • 讀取柵格的行數和列數。
    • 讀取每個柵格的訊號質量,並儲存在二維陣列中。
  2. 二分搜尋

    • 定義訊號質量的範圍,即從最小值到最大值。
    • 對於每個候選值,使用 BFS 檢查是否存在一條符合條件的路徑。
  3. BFS 實現

    • 從起始柵格 (0,0) 開始,檢查是否能到達終點 (R-1,C-1)。
    • 在 BFS 過程中,確保路徑上所有柵格的訊號質量都大於或等於當前候選值。

Java 程式碼實現

import java.util.*;

public class NetworkSignal {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int R = scanner.nextInt();
        int C = scanner.nextInt();
        int[][] grid = new int[R][C];
        
        for (int i = 0; i < R; i++) {
            for (int j = 0; j < C; j++) {
                grid[i][j] = scanner.nextInt();
            }
        }
        
        System.out.println(findMaxMinPath(grid, R, C));
    }

    private static int findMaxMinPath(int[][] grid, int R, int C) {
        int left = 0, right = 65535; // Possible signal quality range
        
        // Binary search to find the maximum minimum value of the path
        while (left < right) {
            int mid = left + (right - left + 1) / 2;
            if (canReach(grid, R, C, mid)) {
                left = mid;
            } else {
                right = mid - 1;
            }
        }
        
        return left;
    }

    private static boolean canReach(int[][] grid, int R, int C, int minSignal) {
        boolean[][] visited = new boolean[R][C];
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{0, 0});
        visited[0][0] = true;
        
        int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // Right, Down, Left, Up
        
        while (!queue.isEmpty()) {
            int[] curr = queue.poll();
            int x = curr[0];
            int y = curr[1];
            
            if (x == R - 1 && y == C - 1) {
                return true; // Reached the end
            }
            
            for (int[] dir : directions) {
                int newX = x + dir[0];
                int newY = y + dir[1];
                
                if (newX >= 0 && newX < R && newY >= 0 && newY < C && !visited[newX][newY] && grid[newX][newY] >= minSignal) {
                    visited[newX][newY] = true;
                    queue.offer(new int[]{newX, newY});
                }
            }
        }
        
        return false; // No path found
    }
}

說明

  1. 二分搜尋:在 [0, 65535] 的範圍內查詢,使得訊號質量值最大化。
  2. BFS:用於檢查從 (0,0)(R-1,C-1) 是否存在符合當前訊號質量的路徑。
  3. 複雜度
    • 時間複雜度:主要是二分搜尋和 BFS 的複雜度,總體上是 ( O(\log M \cdot (R \cdot C)) ),其中 ( M ) 是訊號質量的範圍。
    • 空間複雜度:主要是 BFS 使用的佇列和訪問標記,空間複雜度是 ( O(R \cdot C) )。

此程式碼實現了題目要求,能夠有效地處理給定大小的訊號柵格,並找到最優路徑的評分。



題目

警察在偵破一個案件時,得到了線人給出的可能犯罪時間,形如 “HH:MM” 表示的時刻。根據警察和線人的約定,為了隱蔽,該時間是修改過的,解密規則為:利用當前出現過的數字,構造下一個距離當前時間最近的時刻,則該時間為可能的犯罪時間。每個出現數字都可以被無限次使用。

輸入描述:形如 HH:MM 的字串,表示原始輸入

輸出描述:形如 HH:MM 的字串,表示推理出來的犯罪時間

示例 1
輸入
18:52
輸出
18:55
說明
利用數字1, 8, 5, 2構造出來的最近時刻是18:55,是3分鐘之後。結果不是18:51因為這個時刻是18小時52分鐘之後。

示例 2
輸入
23:59
輸出
22:22
說明
利用數字2, 3, 5, 9構造出來的最近時刻是22:22。 答案一定是第二天的某一時刻,所以選擇可構造的最小時刻為犯罪時間。

答案

import java.util.HashSet;
import java.util.Set;

public class NextClosestTime {
    public static void main(String[] args) {
        String input = "18:52"; // Example input; replace with actual input
        System.out.println(findNextClosestTime(input));
    }

    public static String findNextClosestTime(String time) {
        Set<Character> digits = new HashSet<>();
        for (char c : time.toCharArray()) {
            if (c != ':') {
                digits.add(c);
            }
        }
        
        String currentTime = time;
        while (true) {
            currentTime = getNextTime(currentTime);
            if (isValidTime(currentTime, digits)) {
                return currentTime;
            }
        }
    }

    private static String getNextTime(String time) {
        String[] parts = time.split(":");
        int hours = Integer.parseInt(parts[0]);
        int minutes = Integer.parseInt(parts[1]);

        minutes++;
        if (minutes == 60) {
            minutes = 0;
            hours++;
            if (hours == 24) {
                hours = 0;
            }
        }

        return String.format("%02d:%02d", hours, minutes);
    }

    private static boolean isValidTime(String time, Set<Character> digits) {
        String[] parts = time.split(":");
        String hourPart = parts[0];
        String minutePart = parts[1];

        for (char c : hourPart.toCharArray()) {
            if (!digits.contains(c)) {
                return false;
            }
        }

        for (char c : minutePart.toCharArray()) {
            if (!digits.contains(c)) {
                return false;
            }
        }

        return true;
    }
}

解釋

  1. 提取數字:從給定的時間中提取出所有出現的數字,並存入一個集合 digits
  2. 生成下一個時間:逐步生成下一個時間,直到找到一個滿足條件的時間。時間遞增的邏輯在 getNextTime 方法中實現。
  3. 驗證時間有效性:檢查生成的時間是否僅使用了之前出現的數字。透過 isValidTime 方法進行驗證。
  4. 處理時間:如果找到了一個符合條件的時間,則返回;否則繼續查詢。


題目

給定兩個字串 AB,將其對映到一個二維陣列中,每個字元為一個座標軸上的點。定義原點為 (0, 0),終點為 (m, n),其中 mn 分別為字串 AB 的長度。每兩個字元相同的座標點之間可以作一個斜邊,斜邊的距離為 1。我們需要計算從 (0, 0)(m, n) 的最短路徑距離。路徑可以是水平、垂直或斜邊的組合。

輸入描述

  • 兩個字串 AB,長度不超過 1000。

輸出描述

  • 輸出從 (0, 0)(m, n) 的最短距離。

示例

輸入

ABCABBA
CBABAC

輸出

9

解題思路

  1. 定義問題: 我們需要在一個二維網格中尋找最短路徑,允許水平、垂直和斜對角移動。網格的每一個點對應於字串 AB 的一個字元。如果兩個字元相同,可以沿對角線移動。

  2. 使用動態規劃: 我們可以使用動態規劃 (DP) 來解決這個問題。定義一個 DP 表格 dp[i][j] 表示從 (0, 0)(i, j) 的最短距離。初始狀態為 dp[0][0] = 0,即起點的距離為 0。

  3. 狀態轉移:

    • (i-1, j)(i, j) 需要加上水平邊的距離。
    • (i, j-1)(i, j) 需要加上垂直邊的距離。
    • (i-1, j-1)(i, j) 需要加上斜邊的距離,前提是 A[i-1]B[j-1] 相同。
  4. 邊界條件: 需要處理邊界條件,如初始化第一行和第一列。

  5. 最終結果: dp[m][n] 即為從 (0, 0)(m, n) 的最短距離。

Java 程式碼實現

public class ShortestPath {
    public static void main(String[] args) {
        String A = "ABCABBA";
        String B = "CBABAC";
        System.out.println(findShortestDistance(A, B));
    }

    public static int findShortestDistance(String A, String B) {
        int m = A.length();
        int n = B.length();
        
        // Create a 2D array to store the minimum distance
        int[][] dp = new int[m + 1][n + 1];
        
        // Initialize the DP table
        for (int i = 0; i <= m; i++) {
            for (int j = 0; j <= n; j++) {
                dp[i][j] = Integer.MAX_VALUE;
            }
        }
        
        // Starting point
        dp[0][0] = 0;
        
        // Fill the DP table
        for (int i = 0; i <= m; i++) {
            for (int j = 0; j <= n; j++) {
                if (i > 0) {
                    dp[i][j] = Math.min(dp[i][j], dp[i - 1][j] + 1);
                }
                if (j > 0) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][j - 1] + 1);
                }
                if (i > 0 && j > 0 && A.charAt(i - 1) == B.charAt(j - 1)) {
                    dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - 1] + 1);
                }
            }
        }
        
        // The answer is the shortest distance to (m, n)
        return dp[m][n];
    }
}

解釋

  • 初始化: dp 表格初始化為 Integer.MAX_VALUE,表示初始狀態下距離為無窮大。
  • 動態規劃填表:
    • 對於每個點 (i, j),檢查從 (i-1, j)(i, j-1) 的水平和垂直邊的距離。
    • 如果 A[i-1]B[j-1] 相同,還需檢查斜對角的距離。
  • 輸出: dp[m][n] 是最終從 (0, 0)(m, n) 的最短距離。


題目

給定一個一維整型陣列,計算陣列中的眾數,並組成一個新的陣列,然後求出這個新陣列的中位數。具體步驟如下:

  1. 眾數是指一組資料中出現次數最多的數。如果有多個數出現次數相同且最多,它們都算作眾數。
  2. 中位數是指將陣列從小到大排列後,取最中間的數。如果陣列元素個數為偶數,則取中間兩個數之和除以 2 的結果。

輸入描述

  • 輸入一個一維整型陣列,陣列大小取值範圍 0 < N < 1000,陣列中每個元素取值範圍 0 < E < 1000

輸出描述

  • 輸出眾陣列成的新陣列的中位數。

示例

示例 1

輸入

10 11 21 19 21 17 21 16 21 18 15

輸出

21

示例 2

輸入

2 1 5 4 3 3 9 2 7 4 6 2 15 4 2 4

輸出

3

解題思路

  1. 計算眾數:

    • 使用雜湊表統計每個數字出現的次數。
    • 找到出現次數最多的數字,即為眾數。如果有多個出現次數最多的數字,所有這些數字都應被記錄下來。
  2. 生成新陣列:

    • 根據眾數生成一個新陣列,其中包含所有眾數的值。
  3. 計算中位數:

    • 將新陣列進行排序。
    • 根據新陣列的長度,計算中位數。如果長度為奇數,取中間元素;如果長度為偶數,取中間兩個元素的平均值。

Java 程式碼實現

import java.util.*;

public class ModeMedian {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String[] input = scanner.nextLine().split(" ");
        
        // Convert input to integer array
        int[] arr = Arrays.stream(input).mapToInt(Integer::parseInt).toArray();
        
        // Find the mode(s)
        List<Integer> modes = findModes(arr);
        
        // Calculate median of the mode array
        double median = findMedian(modes);
        
        // Print the median
        System.out.println((int) median);
    }
    
    // Function to find the mode(s) of the array
    public static List<Integer> findModes(int[] arr) {
        Map<Integer, Integer> frequencyMap = new HashMap<>();
        int maxFrequency = 0;
        
        // Build frequency map and find max frequency
        for (int num : arr) {
            int frequency = frequencyMap.getOrDefault(num, 0) + 1;
            frequencyMap.put(num, frequency);
            maxFrequency = Math.max(maxFrequency, frequency);
        }
        
        // Collect all modes
        List<Integer> modes = new ArrayList<>();
        for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {
            if (entry.getValue() == maxFrequency) {
                modes.add(entry.getKey());
            }
        }
        
        return modes;
    }
    
    // Function to calculate median of a list of integers
    public static double findMedian(List<Integer> list) {
        Collections.sort(list);
        int size = list.size();
        if (size % 2 == 1) {
            return list.get(size / 2);
        } else {
            return (list.get(size / 2 - 1) + list.get(size / 2)) / 2.0;
        }
    }
}

解釋

  1. 輸入處理:

    • 讀取並解析輸入,將字串轉換為整數陣列。
  2. 計算眾數:

    • 使用雜湊表統計每個數的出現次數。
    • 找到最大出現次數的所有數,並記錄下來。
  3. 計算中位數:

    • 將眾數列表進行排序,並根據其長度計算中位數。
  4. 輸出結果:

    • 列印中位數。如果輸入的陣列為空或不存在有效眾數,則處理後的結果根據實際情況處理。

這個解決方案高效地處理了陣列眾數和中位數的計算,適用於給定的輸入範圍。


題目

設計一個簡易的重複內容識別系統。系統需要處理兩個字串,並透過給定的相似字元對來判斷這兩個字串是否相似。如果相似,則返回 True 和相似的字元對;如果不相似,則返回第一個內容的“不相似”資訊。

輸入描述

  1. 兩個字串 str1str2 需要比較相似性。
  2. 一些相似字元對,如 (頓號, 逗號) 表示頓號和逗號相似。
  3. 匹配相似字元對時,字元對可以有任意長度的內容匹配。

輸出描述

  • 如果兩個字串相似,輸出 True 和相似的字元對。
  • 如果不相似,返回第一個內容的不相似資訊。多處不相似的字串用空格分隔。

示例

示例 1

輸入

異世邪君(人氣玄幻作家)
異世邪君
(異世邪君, 異世邪君)

輸出

True (異世邪君, 異世邪君)

示例 2

輸入

hello world
hello, world
(hello, hello) (world, world)

輸出

hello world

解題思路

  1. 建立相似字元對的對映關係:

    • 使用一個字典來儲存所有的相似字元對。
    • 利用傳遞性關係將所有相關字元對連線在一起。
  2. 替換字串中的字元:

    • 使用上述對映關係將字串中的字元替換為它們的相似字元。
    • 例如,如果 頓號逗號 是相似的,那麼所有的 頓號 可以替換為 逗號,進行比較時將其替換為相似字符集合的標準字元。
  3. 比較字串:

    • 將兩個字串轉換為標準形式後進行比較。
    • 如果兩者相等,則返回相似結果和相似字元對。
    • 如果不相等,返回不相似的內容。

Java 程式碼實現

import java.util.*;

public class ContentSimilarity {

    private static Map<String, Set<String>> similarityMap = new HashMap<>();

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Read input
        String str1 = scanner.nextLine().trim();
        String str2 = scanner.nextLine().trim();
        String[] pairs = scanner.nextLine().trim().split(" ");
        
        // Initialize similarity map
        initializeSimilarityMap(pairs);
        
        // Check similarity
        if (areSimilar(str1, str2)) {
            System.out.println("True " + getSimilarPairs());
        } else {
            System.out.println(getDissimilarParts(str1));
        }
        
        scanner.close();
    }
    
    // Initialize similarity map
    private static void initializeSimilarityMap(String[] pairs) {
        for (String pair : pairs) {
            String[] parts = pair.replace("(", "").replace(")", "").split(",");
            if (parts.length == 2) {
                String a = parts[0].trim();
                String b = parts[1].trim();
                
                similarityMap.computeIfAbsent(a, k -> new HashSet<>()).add(b);
                similarityMap.computeIfAbsent(b, k -> new HashSet<>()).add(a);
                
                // Adding transitive relations
                addTransitiveRelations(a, b);
            }
        }
    }
    
    // Add transitive relations to the map
    private static void addTransitiveRelations(String a, String b) {
        Set<String> aSet = similarityMap.get(a);
        Set<String> bSet = similarityMap.get(b);
        
        if (aSet != null && bSet != null) {
            for (String item : bSet) {
                aSet.add(item);
                similarityMap.get(item).add(a);
            }
            for (String item : aSet) {
                bSet.add(item);
                similarityMap.get(item).add(b);
            }
        }
    }
    
    // Check if two strings are similar
    private static boolean areSimilar(String str1, String str2) {
        return transform(str1).equals(transform(str2));
    }
    
    // Transform string based on similarity map
    private static String transform(String str) {
        StringBuilder transformed = new StringBuilder();
        for (char ch : str.toCharArray()) {
            String chStr = String.valueOf(ch);
            if (similarityMap.containsKey(chStr)) {
                transformed.append(getRepresentative(chStr));
            } else {
                transformed.append(ch);
            }
        }
        return transformed.toString();
    }
    
    // Get representative for a character or string
    private static String getRepresentative(String str) {
        Set<String> set = similarityMap.get(str);
        return set != null ? set.iterator().next() : str;
    }
    
    // Get similar pairs
    private static String getSimilarPairs() {
        StringBuilder result = new StringBuilder();
        for (Map.Entry<String, Set<String>> entry : similarityMap.entrySet()) {
            for (String similar : entry.getValue()) {
                result.append("(").append(entry.getKey()).append(", ").append(similar).append(") ");
            }
        }
        return result.toString().trim();
    }
    
    // Get dissimilar parts
    private static String getDissimilarParts(String str) {
        return str;
    }
}

解釋

  1. 建立相似字元對的對映關係:

    • 利用雜湊表儲存每個字元的相似字元對,並建立傳遞關係。
  2. 字串轉換:

    • 將字串中所有字元根據對映關係轉換為標準字元。
  3. 比較和輸出:

    • 比較轉換後的兩個字串是否相同,如果相同則輸出相似的字元對;否則輸出第一個內容的不相似資訊。


題目

給定一張地圖上有 n 個城市和道路,城市間的道路構成了一棵樹。要求找到一個城市,使得切斷通往該城市的所有道路後,地圖上最大的城市群(連通分量)的城市數目最小。具體來說,求解每個城市的聚集度(Degree of Polymerization,DP),DP 定義為:切斷該城市後,形成的多個連通子圖中最大的城市數目。我們需要找出所有使得 DP 最小的城市,並按升序輸出。

輸入描述

  • 第一行是一個整數 N,表示城市的數量,1 <= N <= 1000。
  • 接下來的 N-1 行,每行包含兩個整數 xy,表示城市 x 和城市 y 之間有一條道路。

輸出描述

  • 輸出所有使得 DP 最小的城市編號。如果有多個,按編號升序輸出。

示例

示例 1

輸入

5
1 2
2 3
3 4
4 5

輸出

3

示例 2

輸入

6
1 2
2 3
2 4
3 5
3 6

輸出

2 3

解題思路

  1. 建圖:

    • 使用鄰接表來儲存城市之間的連線關係。由於城市構成樹狀結構,所以每個節點的連線關係只有一個。
  2. DFS 計運算元樹大小:

    • 使用深度優先搜尋 (DFS) 計算每個節點的子樹大小。
  3. 計算 DP 值:

    • 對於每個城市 i,假設移除 i,計算所有生成的子圖的大小,並得到 DP 值。
    • 計算 DP 值時,對於每個節點,移除該節點後,可以得到多個子圖。DP 值為這些子圖中最大的子圖的大小。
  4. 尋找最小 DP 值的城市:

    • 遍歷所有城市,找到 DP 值最小的城市,並輸出這些城市編號。

Java 實現

import java.util.*;

public class MinDegreeOfPolymerization {

    private static List<List<Integer>> graph;
    private static int[] subtreeSize;
    private static int n;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        graph = new ArrayList<>(n + 1);
        for (int i = 0; i <= n; i++) {
            graph.add(new ArrayList<>());
        }

        for (int i = 0; i < n - 1; i++) {
            int x = scanner.nextInt();
            int y = scanner.nextInt();
            graph.get(x).add(y);
            graph.get(y).add(x);
        }

        subtreeSize = new int[n + 1];
        boolean[] visited = new boolean[n + 1];
        calculateSubtreeSizes(1, visited);

        int minDp = Integer.MAX_VALUE;
        List<Integer> result = new ArrayList<>();
        
        for (int i = 1; i <= n; i++) {
            int dp = calculateDP(i);
            if (dp < minDp) {
                minDp = dp;
                result.clear();
                result.add(i);
            } else if (dp == minDp) {
                result.add(i);
            }
        }

        Collections.sort(result);
        System.out.println(result.stream().map(String::valueOf).collect(Collectors.joining(" ")));
    }

    private static void calculateSubtreeSizes(int node, boolean[] visited) {
        visited[node] = true;
        subtreeSize[node] = 1;
        for (int neighbor : graph.get(node)) {
            if (!visited[neighbor]) {
                calculateSubtreeSizes(neighbor, visited);
                subtreeSize[node] += subtreeSize[neighbor];
            }
        }
    }

    private static int calculateDP(int root) {
        boolean[] visited = new boolean[n + 1];
        visited[root] = true;
        int maxSize = 0;

        for (int neighbor : graph.get(root)) {
            if (!visited[neighbor]) {
                maxSize = Math.max(maxSize, dfsSize(neighbor, visited));
            }
        }

        return maxSize;
    }

    private static int dfsSize(int node, boolean[] visited) {
        visited[node] = true;
        int size = 1;
        for (int neighbor : graph.get(node)) {
            if (!visited[neighbor]) {
                size += dfsSize(neighbor, visited);
            }
        }
        return size;
    }
}

解釋

  1. 圖的構建:

    • 用鄰接表表示城市之間的連線關係。
  2. DFS 計運算元樹大小:

    • 從一個節點開始,使用 DFS 計算每個節點的子樹大小。
  3. 計算 DP 值:

    • 對於每個城市,計算切斷它後生成的各個子圖的大小,確定最大值作為 DP 值。
  4. 找出最小 DP 值的城市:

    • 遍歷所有城市,找到 DP 值最小的城市,並輸出這些城市編號。


題目

某公司部門需要派遣員工去國外做專案。代號為 x 的國家和代號為 y 的國家分別需要 cntx 名和 cnty 名員工。每個員工有一個員工號(1, 2, 3, ...),工號連續,從1開始。

部長派遣員工的規則

  1. [1, k] 中選擇員工派遣出去。
  2. 編號為 x 的倍數的員工不能去 x 國,編號為 y 的倍數的員工不能去 y 國。

問題

找到最小的 k,使得可以將編號在 [1, k] 中的員工分配給 x 國和 y 國,且滿足 x 國和 y 國的需求。

輸入描述

  • 四個整數 x, y, cntx, cnty

條件

  • 2 ≤ x < y ≤ 30000
  • xy 一定是質數
  • 1 ≤ cntx, cnty < 10^9
  • cntx + cnty ≤ 10^9

輸出描述

  • 輸出滿足條件的最小的 k

解題思路

  1. 確定有效員工總數:

    • 需要排除 xy 的倍數。對於每個 k,總員工數是 k,但我們需要排除掉不能派遣的員工。即不能派遣的員工數包括 k / xk / y,但需要加上 k / lcm(x, y),因為那些是 xy 的倍數的員工被計算了兩次。
  2. 計算公式:

    • 使用以下公式來確定有效員工總數:
      [
      \text{有效員工總數} = k - \left(\frac{k}{x} + \frac{k}{y} - \frac{k}{\text{lcm}(x, y)}\right)
      ]
    • 這裡,lcm(x, y)xy 的最小公倍數,可以透過公式計算:
      [
      \text{lcm}(x, y) = \frac{x \times y}{\text{gcd}(x, y)}
      ]
    • 用二分查詢來找到最小的 k,使得有效員工總數至少滿足 cntx + cnty
  3. 二分查詢:

    • 二分查詢從 1 到一個合理的上界(例如 cntx + cnty + max(x, y)),在每一步中計算有效員工數,並判斷是否能滿足需求。

Java 實現

import java.util.Scanner;

public class EmployeeDispatch {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        int x = scanner.nextInt();
        int y = scanner.nextInt();
        long cntx = scanner.nextLong();
        long cnty = scanner.nextLong();

        long left = 1;
        long right = cntx + cnty;
        long result = right;

        while (left <= right) {
            long mid = left + (right - left) / 2;
            long validEmployees = mid - (mid / x + mid / y - mid / lcm(x, y));

            if (validEmployees >= cntx + cnty) {
                result = mid;
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }

        System.out.println(result);
    }

    private static long gcd(long a, long b) {
        while (b != 0) {
            long temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }

    private static long lcm(long a, long b) {
        return a * b / gcd(a, b);
    }
}

解釋

  1. 計算有效員工總數:

    • 使用 k - (k / x + k / y - k / lcm(x, y)) 公式來計算有效員工數量。
  2. 二分查詢:

    • [1, cntx + cnty] 範圍內進行二分查詢來找到滿足條件的最小 k
  3. 輔助函式:

    • gcdlcm 用於計算最小公倍數。

結果

透過上述實現,可以找到滿足條件的最小 k。使用二分查詢的方法保證了效率,即使 cntxcnty 很大,程式碼仍然可以在合理的時間內完成計算。


題目描述

專案組有 N 名開發人員,專案經理接到了 M 個獨立需求,每個需求的工作量不同,且每個需求只能由一個開發人員獨立完成,不能多人合作。任務是幫助專案經理安排工作,使得整個專案用最少的時間交付。

輸入描述

  1. 第一行輸入為 M 個需求的工作量,單位為天,用逗號隔開。例如:6 2 7 7 9 3 2 1 3 11 4
  2. 第二行輸入為專案組人員數量 N。例如:2

輸出描述

輸出最快完成所有工作的天數。

示例

輸入

6 2 7 7 9 3 2 1 3 11 4
2

輸出

28

說明

共有兩位員工,分配需求 6 2 7 7 3 2 1 共需要 28 天完成,另一位分配需求 9 3 11 4 共需要 27 天完成,因此整個專案的最短完成時間是 28 天。

解題思路

這個問題可以轉化為一個 “工作分配問題”,它類似於一個 “最小最大負載” 問題。目標是將任務分配給 N 個開發人員,使得所有開發人員的工作量的最大值儘可能地小。

具體步驟

  1. 定義問題:

    • 將任務分配給 N 個開發人員,最小化完成所有工作的最大天數。
    • 這是一個典型的分配問題,可以用二分查詢配合貪心演算法解決。
  2. 二分查詢:

    • 設定搜尋範圍:
      • 最小值是最大工作量(因為至少一個開發人員要處理最大任務)。
      • 最大值是所有工作量之和(即所有工作都由一個人完成的情況)。
  3. 檢查函式:

    • 設計一個函式來判斷某個時間 T 是否能夠將所有任務分配給 N 個開發人員,使得每個開發人員的工作時間不超過 T
  4. 二分查詢演算法:

    • 在上述範圍內進行二分查詢,根據檢查函式的結果來調整搜尋範圍,最終確定最小的 T

Java 實現

import java.util.Scanner;

public class ProjectManager {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 讀取需求的工作量
        String[] input = scanner.nextLine().split(" ");
        int[] workloads = new int[input.length];
        for (int i = 0; i < input.length; i++) {
            workloads[i] = Integer.parseInt(input[i]);
        }
        
        // 讀取專案組人員數量
        int N = scanner.nextInt();
        
        // 二分查詢範圍的設定
        int low = 0;
        int high = 0;
        for (int workload : workloads) {
            low = Math.max(low, workload);
            high += workload;
        }
        
        // 二分查詢最小的最大工作時間
        while (low < high) {
            int mid = low + (high - low) / 2;
            if (canDistributeWork(workloads, N, mid)) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }
        
        // 輸出結果
        System.out.println(low);
    }

    // 檢查是否可以將工作分配給 N 個開發人員,且最大工作時間不超過 maxTime
    private static boolean canDistributeWork(int[] workloads, int N, int maxTime) {
        int count = 1;
        int currentTime = 0;
        
        for (int workload : workloads) {
            if (currentTime + workload > maxTime) {
                count++;
                currentTime = workload;
                if (count > N) {
                    return false;
                }
            } else {
                currentTime += workload;
            }
        }
        return true;
    }
}

解釋

  1. 輸入解析:

    • 讀取並解析工作量陣列以及員工數量。
  2. 二分查詢:

    • 使用二分查詢確定最小的最大工作時間。
  3. 檢查函式:

    • canDistributeWork 函式判斷給定最大工作時間下,是否可以在 N 名開發人員之間合理分配工作。
  4. 輸出結果:

    • 輸出滿足條件的最小最大工作時間。


題目描述

給定一個包含 01 的二維矩陣,物體從給定的初始位置開始移動,以給定的速度進行運動。在碰到矩陣邊緣時,物體會發生鏡面反射。你需要計算在經過 t 時間單位後,物體經過的 1 的點的次數。

輸入描述

  1. 矩陣:一個二維矩陣,只包含 01
  2. 初始位置:物體的初始位置 (x, y)。
  3. 速度:物體的速度 (vx, vy),表示物體每個時間單位在 x 和 y 方向上的移動距離。
  4. 時間 t:模擬的時間單位。

輸出描述

輸出在經過 t 時間單位後,物體經過的 1 的點的次數。

示例

輸入

5 5
1 1 1 1 1
1 0 0 0 1
1 0 0 0 1
1 0 0 0 1
1 1 1 1 1
2 2
1 1
3

輸出

7

解釋

在給定的初始位置 (2, 2) 和速度 (1, 1) 下,物體在 3 個時間單位後經過的點如下:

  • 時間 0: (2, 2),值為 1
  • 時間 1: (3, 3),值為 0
  • 時間 2: (4, 4),值為 1
  • 時間 3: (0, 0),值為 1(經過邊界反射)。

在這些時間點中,物體總共經過了 7 個 1

解題思路

  1. 矩陣反射處理:

    • 對於物體的每次移動,檢查是否超出了矩陣的邊界。若超出,則調整方向進行反射。
  2. 位置計算:

    • 使用模運算和反射邏輯來計算物體的位置。例如,x 軸的反射可以透過 (x + t * vx) % (2 * (cols - 1)) 計算,然後調整方向。
  3. 遍歷每個時間單位:

    • 從時間 0 到時間 t,計算物體的位置並檢查其值是否為 1,並統計經過 1 的次數。

Java 實現

import java.util.Scanner;

public class MatrixReflection {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 讀取矩陣的行和列
        int rows = scanner.nextInt();
        int cols = scanner.nextInt();
        
        // 讀取矩陣
        int[][] matrix = new int[rows][cols];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                matrix[i][j] = scanner.nextInt();
            }
        }
        
        // 讀取初始位置和速度
        int startX = scanner.nextInt();
        int startY = scanner.nextInt();
        int vx = scanner.nextInt();
        int vy = scanner.nextInt();
        
        // 讀取時間 t
        int t = scanner.nextInt();
        
        // 計算經過的1的點的次數
        int count = 0;
        
        for (int time = 0; time <= t; time++) {
            int x = startX + time * vx;
            int y = startY + time * vy;
            
            // 處理 x 方向反射
            int horizontalPeriod = 2 * (cols - 1);
            if (x < 0) {
                x = -x;
                x = (x / horizontalPeriod) % 2 == 0 ? x % horizontalPeriod : horizontalPeriod - x % horizontalPeriod;
            } else {
                x = x % horizontalPeriod;
                if (x >= cols) {
                    x = horizontalPeriod - x;
                }
            }
            
            // 處理 y 方向反射
            int verticalPeriod = 2 * (rows - 1);
            if (y < 0) {
                y = -y;
                y = (y / verticalPeriod) % 2 == 0 ? y % verticalPeriod : verticalPeriod - y % verticalPeriod;
            } else {
                y = y % verticalPeriod;
                if (y >= rows) {
                    y = verticalPeriod - y;
                }
            }
            
            // 檢查當前位置是否是1
            if (matrix[y][x] == 1) {
                count++;
            }
        }
        
        // 輸出結果
        System.out.println(count);
    }
}

說明

  1. 矩陣處理:

    • 讀取矩陣並初始化。
  2. 位置計算:

    • 使用反射邏輯計算當前位置,並處理邊界情況。
  3. 統計:

    • 遍歷每個時間單位,檢查當前位置並統計經過 1 的次數。

相關文章