Compare commits

...

5 Commits

7 changed files with 542 additions and 3 deletions

View File

@@ -337,9 +337,9 @@ EOF
# 建立 C# 專案
mkdir -p "$problem_dir/csharp"
cd "$problem_dir/csharp"
dotnet new console -n "Problem$number" --force > /dev/null 2>&1
dotnet new console --force > /dev/null 2>&1
# 建立 C# 解法檔案
# 覆寫自動生成的 Program.cs
cat > "Program.cs" << EOF
// LeetCode $number: $(echo "$name" | tr '-' ' ' | sed 's/\b\w/\u&/g')
// 難度: $difficulty
@@ -430,7 +430,7 @@ EOF
echo "├── README.md"
echo "├── csharp/"
echo "│ ├── Program.cs"
echo "│ └── Problem$number.csproj"
echo "│ └── *.csproj"
echo "├── go/"
echo "│ ├── main.go"
echo "│ └── go.mod"

View File

@@ -0,0 +1,69 @@
# 2025-09 學習記錄
## 📅 每日練習記錄
### Week 1
| 日期 | 題目 | 難度 | 語言 | 耗時 | 狀態 | 心得 |
|------|------|------|------|------|------|------|
| 09-01 | Maximum Average Pass Ratio | Medium | C# | 1hr | Done | 第一次用到Priority Queue |
### Week 2
| 日期 | 題目 | 難度 | 語言 | 耗時 | 狀態 | 心得 |
|------|------|------|------|------|------|------|
| | | | | | | |
### Week 3
| 日期 | 題目 | 難度 | 語言 | 耗時 | 狀態 | 心得 |
|------|------|------|------|------|------|------|
| | | | | | | |
### Week 4
| 日期 | 題目 | 難度 | 語言 | 耗時 | 狀態 | 心得 |
|------|------|------|------|------|------|------|
| | | | | | | |
## 📈 本月統計
### 完成情況
- **總練習天數**: 1 天
- **完成題數**: 1題
- **語言分布**: C# 1(題), Go 0(題)
- **難度分布**: Easy 0(題), Medium 1(題), Hard 0(題)
### 時間投入
- **總時間**: 1小時
- **平均每題**: 分鐘
- **每日平均**: 分鐘
## 🎯 本月重點學習
### 新掌握的技巧
1. 貪心策略
### 常犯錯誤分析
1.
### 語言學習心得
- **C#**:
- **Go**:
## 🔄 遇到的困難與解決
### 困難1:
- **問題**:
- **解決**:
- **學習**:
## 📝 改進計畫
### 下月目標
1.
### 學習方法調整
1.
## 💡 本月金句
>
---
**總結**:

View File

@@ -0,0 +1,75 @@
# [1792] Maximum Average Pass Ratio
## 題目資訊
- **難度**: Medium
- **標籤**: Array, Greedy, Heap(Priority Queue)
- **題目連結**: [LeetCode](https://leetcode.com/problems/maximum-average-pass-ratio/)
- **練習日期**: 2025-09-01
## 題目描述
There is a school that has classes of students and each class will be having a final exam. You are given a 2D integer array `classes`, where `classes[i] = [passi, totali]`. You know beforehand that in the `ith` class, there are `totali` total students, but only `passi` number of students will pass the exam.
You are also given an integer `extraStudents`. There are another `extraStudents` brilliant students that are **guaranteed** to pass the exam of any class they are assigned to. You want to assign each of the `extraStudents` students to a class in a way that **maximizes** the **average** pass ratio across **all** the classes.
The **pass ratio** of a class is equal to the number of students of the class that will pass the exam divided by the total number of students of the class. The **average pass ratio** is the sum of pass ratios of all the classes divided by the number of the classes.
Return the *maximum* possible average pass ratio after assigning the `extraStudents` students. Answers within `10-5` of the actual answer will be accepted.
## 解題思路
### 初步分析
- 這題主要考察什麼概念?
- 貪心算法,找出每次分配學生,通過率可以提升至最高
- Pass Ratio = pass / total
- Average Pass Ratio = sum of pass ration / number of class
### 解法概述
1. **暴力解法**:
- 思路:
- 每次分配一個學生看哪個班級提升率會最高
- leetcode 這邊會 time limit exceeded
- 時間複雜度O(N)
- 空間複雜度O(N)
2. **最佳解法**:
- 思路:
- Priority Queue(Max Heap) 存每個班級潛在
- 時間複雜度O(?)
- 空間複雜度O(?)
## 測試案例
### 範例輸入輸出
```
Input: classes = [[1,2],[3,5],[2,2]], extraStudents = 2
Output: 0.78333
Explanation: You can assign the two extra students to the first class. The average pass ratio will be equal to (3/4 + 3/5 + 2/2) / 3 = 0.78333.
```
### 邊界情況
- 1 <= classes.length <= 10^5
- classes[i].length == 2
- 1 <= passi <= totali <= 10^5
- 1 <= extraStudents <= 10^5
## 學習筆記
### 今天學到什麼?
- 第一次使用 Priority Queue
- 第一次使用 Unit Test
### 遇到的困難
- 可能要再了解 Priority Queue 更多實際用途
### 改善方向
-
### 相關題目
- [#1627](https://leetcode.com/problems/graph-connectivity-with-threshold/description/) Graph Connectivity With Threshold
- [#1728](https://leetcode.com/problems/cat-and-mouse-ii/description/) Cat and Mouse II
- [#3440](https://leetcode.com/problems/reschedule-meetings-for-maximum-free-time-ii/description/) Reschedule Meetings for Maximum Free Time II
---
**總結**:
1. 貪心策略,每次都選擇當下收益最大解
2. 練習使用Priority Queue 快速找到最大收益

View File

@@ -0,0 +1,187 @@
// LeetCode 1792: Maximum Average Pass Ratio
// 難度: Medium
// 日期: 2025-09-01
using System;
using System.Collections.Generic;
using System.Linq;
public class Solution
{
public double MaxAverageRatio(int[][] classes, int extraStudents)
{
// use priority queue
var pq = new PriorityQueue<ClassInfo, double>();
// 初始化:將所有班級加入優先佇列
for (int i = 0; i < classes.Length; i++)
{
int pass = classes[i][0];
int total = classes[i][1];
double gain = CalculateGain(pass, total);
pq.Enqueue(new ClassInfo(pass, total), -gain);
}
for (int i = 0; i < extraStudents; i++)
{
var classInfo = pq.Dequeue();
classInfo.Pass++;
classInfo.Total++;
double newGain = CalculateGain(classInfo.Pass, classInfo.Total);
pq.Enqueue(classInfo, -newGain);
}
double totalRatio = 0;
while (pq.Count > 0)
{
var classInfo = pq.Dequeue();
totalRatio += (double)classInfo.Pass / classInfo.Total;
}
return totalRatio / classes.Length;
}
public double MaxAverageRatioByBruteForce(int[][] classes, int extraStudents)
{
var n = classes.Length;
var pass = new int[n];
var total = new int[n];
for (var i = 0; i < n; i++)
{
pass[i] = classes[i][0];
total[i] = classes[i][1];
}
for (int student = 0; student < extraStudents; student++)
{
int bestClass = -1;
double maxInprovement = 0;
for (int i = 0; i < n; i++)
{
double currentAvg = CalculateAvergaeRatio(pass, total);
pass[i]++;
total[i]++;
double newAvg = CalculateAvergaeRatio(pass, total);
double improvement = newAvg - currentAvg;
pass[i]--;
total[i]--;
if (improvement > maxInprovement)
{
maxInprovement = improvement;
bestClass = i;
}
}
if (bestClass != -1)
{
pass[bestClass]++;
total[bestClass]++;
}
}
return CalculateAvergaeRatio(pass, total);
}
private double CalculateGain(int pass, int total)
{
// 計算提升率
return (double)(pass + 1) / (total + 1) - (double)pass / total;
}
private double CalculateAvergaeRatio(int[] pass, int[] total)
{
double sum = 0;
for (var i = 0; i < pass.Length; i++)
{
sum += (double)pass[i] / total[i];
}
return sum / pass.Length;
}
}
public class ClassInfo
{
public int Pass { get; set; }
public int Total { get; set; }
public ClassInfo(int pass, int total)
{
Pass = pass;
Total = total;
}
}
// 測試程式
public class Program
{
public static void Main()
{
Solution solution = new Solution();
// TestCase
TestCase1(solution);
TestCase2(solution);
}
static void TestCase1(Solution solution)
{
// Input:
int[][] classes = new int[][]
{
new int[] {1, 2},
new int[] {3, 5},
new int[] {2, 2}
};
int extraStudents = 2;
Console.WriteLine("Test 1: ");
Console.WriteLine($"classes: [{string.Join("], [", classes.Select(c => string.Join(", ", c)))}]");
Console.WriteLine($"extraStudents: {extraStudents}");
// Expected:
Console.WriteLine("Expect: ");
Console.WriteLine($"output: 0.78333");
// Actual:
Console.WriteLine("Actual: ");
Console.WriteLine($"Brute Force: {solution.MaxAverageRatioByBruteForce(classes, extraStudents):F6}");
Console.WriteLine($"Priority Queue: {solution.MaxAverageRatio(classes, extraStudents):F6}");
}
static void TestCase2(Solution solution)
{
// Input:
int[][] classes = new int[][]
{
new int[] {2, 4},
new int[] {3, 9},
new int[] {4, 5},
new int[] {2, 10}
};
int extraStudents = 4;
Console.WriteLine("Test 2: ");
Console.WriteLine($"classes: [{string.Join("], [", classes.Select(c => string.Join(", ", c)))}]");
Console.WriteLine($"extraStudents: {extraStudents}");
// Expected:
Console.WriteLine("Expect: ");
Console.WriteLine($"output: 0.53485");
// Actual:
Console.WriteLine("Actual: ");
Console.WriteLine($"Brute Force: {solution.MaxAverageRatioByBruteForce(classes, extraStudents):F6}");
Console.WriteLine($"Priority Queue: {solution.MaxAverageRatio(classes, extraStudents):F6}");
}
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,176 @@
// LeetCode 1792 單元測試
// 使用 xUnit 框架
using Xunit;
using System;
using System.Collections.Generic;
public class SolutionTests
{
private readonly Solution _solution;
private const double TOLERANCE = 0.00001;
public SolutionTests()
{
_solution = new Solution();
}
#region Basic Test
[Fact]
public void TestCase1()
{
// Arrange
int[][] classes = new int[][]
{
new int[] {1, 2},
new int[] {3, 5},
new int[] {2, 2}
};
int extraStudents = 2;
double expected = 0.78333;
// Act
var result = _solution.MaxAverageRatio(classes, extraStudents);
// Assert
Assert.True(Math.Abs(result - expected) < TOLERANCE,
$"Expected: {expected}, Actual: {result}");
}
[Fact]
public void TestCase2()
{
// Arrange
int[][] classes = new int[][]
{
new int[] {2, 4}, // 通過率: 2/4 = 0.5
new int[] {3, 9}, // 通過率: 3/9 = 0.333...
new int[] {4, 5}, // 通過率: 4/5 = 0.8
new int[] {2, 10} // 通過率: 2/10 = 0.2
};
int extraStudents = 4;
double expected = 0.53485;
// Act
var result = _solution.MaxAverageRatio(classes, extraStudents);
// Assert
Assert.True(Math.Abs(result - expected) < TOLERANCE,
$"Expected: {expected}, Actual: {result}");
}
[Fact]
public void TestBruteForceVsPriorityQueue()
{
// Arrange
int[][] classes = new int[][]
{
new int[] {1, 3},
new int[] {2, 4},
new int[] {1, 5}
};
int extraStudents = 3;
// Act
var resultBruteForce = _solution.MaxAverageRatioByBruteForce(classes, extraStudents);
var resultPriorityQueue = _solution.MaxAverageRatio(classes, extraStudents);
// Assert
Assert.True(Math.Abs(resultBruteForce - resultPriorityQueue) < TOLERANCE,
$"Brute Force: {resultBruteForce}, Priority Queue: {resultPriorityQueue}");
}
#endregion
#region Edge Test
[Fact]
public void TestEdgeCases_MinClassesLength_OneClass()
{
// Arrange
int[][] classes = new int[][]
{
new int[] {50, 100}
};
int extraStudents = 10;
double expected = 60.0 / 110.0;
// Act
var result = _solution.MaxAverageRatio(classes, extraStudents);
// Assert
Assert.True(result >= 0 && result <= 1,
$"Result should be between 0 and 1, got: {result}");
Assert.True(Math.Abs(result - expected) < TOLERANCE,
$"Expected: {expected}, Actual: {result}");
}
[Fact]
public void TestEdgeCases_LargeClassesLength()
{
// Arrange
const int numClasses = 1000;
var classes = new int[numClasses][];
var random = new Random(42);
for (int i = 0; i < numClasses; i++)
{
int total = random.Next(10, 100); // total 在 10-100 之間
int pass = random.Next(1, total + 1); // pass 在 1-total 之間
classes[i] = new int[] { pass, total };
}
int extraStudents = 100;
// Act
var result = _solution.MaxAverageRatio(classes, extraStudents);
// Assert
Assert.True(result >= 0 && result <= 1,
$"Result should be between 0 and 1, got: {result}");
}
[Fact]
public void TestEdgeCases_MinExtraStudents_One()
{
// Arrange
int[][] classes = new int[][]
{
new int[] {1, 3},
new int[] {2, 4}
};
int extraStudents = 1;
// Act
var result = _solution.MaxAverageRatio(classes, extraStudents);
// Assert
double originalAvg = (1.0 / 3 + 2.0 / 4) / 2;
Assert.True(result > originalAvg,
$"Result {result} should be greater than original average {originalAvg}");
}
[Fact]
public void TestEdgeCases_LargeExtraStudents()
{
// Arrange - 大量額外學生
int[][] classes = new int[][]
{
new int[] {10, 100}, // 10% 通過率
new int[] {20, 100}, // 20% 通過率
new int[] {30, 100} // 30% 通過率
};
int extraStudents = 10000;
// Act
var result = _solution.MaxAverageRatio(classes, extraStudents);
// Assert
Assert.True(result >= 0 && result <= 1,
$"Result should be between 0 and 1, got: {result}");
// 大量學生加入後,通過率應該顯著提升
double originalAvg = (0.1 + 0.2 + 0.3) / 3; // = 0.2
Assert.True(result > originalAvg + 0.1,
$"Result {result} should be significantly higher than original average {originalAvg}");
}
#endregion
}

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../csharp/csharp.csproj" />
</ItemGroup>
</Project>