Skip to content

Commit 00a42ed

Browse files
committed
Add: Add 2025/10/24
1 parent aec421f commit 00a42ed

File tree

3 files changed

+343
-0
lines changed

3 files changed

+343
-0
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# 2048. Next Greater Numerically Balanced Number
2+
3+
An integer `x` is numerically balanced if for every digit `d` in the number `x`, there are exactly `d` occurrences of that digit in `x`.
4+
5+
Given an integer `n`, return the smallest numerically balanced number strictly greater than `n`.
6+
7+
**Constraints:**
8+
9+
- `0 <= n <= 10^6`
10+
11+
## 基礎思路
12+
13+
本題要求找出**嚴格大於給定整數 `n` 的最小數值平衡數(numerically balanced number)**
14+
15+
一個整數被稱為數值平衡數,是指其每個數位 `d` 在整個數中**恰好出現 `d`**
16+
例如:
17+
18+
- `22` 是平衡的(數位 2 出現兩次)。
19+
- `1333` 是平衡的(1 出現一次,3 出現三次)。
20+
- `122` 則不是(2 出現兩次沒錯,但 1 只出現一次,不平衡於 2)。
21+
22+
題目限制 `0 ≤ n ≤ 10^6`,而符合條件的數字不可能含有超過 7 的數位,
23+
因為若要出現 8 次數位 `8`,至少需 8 位數以上。因此所有合法的平衡數都在 7 位以內。
24+
25+
基於此觀察,可採用 **預先生成 + 二分搜尋** 的策略:
26+
27+
1. **預生成所有平衡數**
28+
列舉所有由數位 {1,…,7} 所構成的子集合,
29+
並對每個子集產生「每個數位 d 出現 d 次」的所有排列組合。
30+
由於總位數上限為 7,生成總量有限。
31+
32+
2. **排序與壓縮儲存**
33+
將所有結果排序後以 `Uint32Array` 儲存,利於高效查詢。
34+
35+
3. **查詢階段使用二分搜尋**
36+
對給定 `n`,使用 `upperBound` 尋找第一個嚴格大於 `n` 的元素。
37+
38+
## 解題步驟
39+
40+
### Step 1:`upperBound` — 已排序陣列中找第一個嚴格大於目標的元素
41+
42+
說明:在升冪排序的數列中,使用二分搜尋回傳第一個 `> target` 的索引;若不存在則回傳長度。
43+
44+
```typescript
45+
/**
46+
* Perform a binary search to find the first element strictly greater than the given target.
47+
* @param sortedArray - A sorted Uint32Array of ascending numbers.
48+
* @param targetValue - The number to compare against.
49+
* @returns The index of the first element > targetValue, or sortedArray.length if not found.
50+
*/
51+
function upperBound(sortedArray: Uint32Array, targetValue: number): number {
52+
let leftIndex = 0
53+
let rightIndex = sortedArray.length
54+
55+
while (leftIndex < rightIndex) {
56+
const middleIndex = (leftIndex + rightIndex) >>> 1
57+
if (sortedArray[middleIndex] > targetValue) {
58+
rightIndex = middleIndex
59+
} else {
60+
leftIndex = middleIndex + 1
61+
}
62+
}
63+
64+
return leftIndex
65+
}
66+
```
67+
68+
### Step 2:`PRECOMPUTED_NUMERICALLY_BALANCED_NUMBERS` — 預先生成所有位數 ≤ 7 的平衡數
69+
70+
說明:以位元子集枚舉 `{1..7}`,計算該子集的總位數(各數位 `d` 需出現 `d` 次),若總位數 ≤ 7,則以遞迴回溯產生所有排列;最後排序並打包成 `Uint32Array`
71+
72+
```typescript
73+
/**
74+
* Precompute all numerically balanced numbers with up to 7 digits (using digits 1 through 7).
75+
*
76+
* A number is numerically balanced if, for every digit d in the number,
77+
* the count of digit d is exactly equal to d.
78+
*/
79+
const PRECOMPUTED_NUMERICALLY_BALANCED_NUMBERS: Uint32Array = (() => {
80+
const allBalancedNumbers: number[] = []
81+
const remainingDigitCounts = new Int8Array(8) // index 1..7 represents the remaining count for each digit
82+
83+
/**
84+
* Recursive helper to generate all possible numeric combinations that satisfy the balanced condition.
85+
* @param digitsRemaining - Number of digits left to place.
86+
* @param currentValue - Current partial integer being formed.
87+
*/
88+
function generateNumbersRecursively(digitsRemaining: number, currentValue: number) {
89+
if (digitsRemaining === 0) {
90+
allBalancedNumbers.push(currentValue)
91+
return
92+
}
93+
94+
// Try placing each digit that still has remaining occurrences
95+
for (let digit = 1; digit <= 7; digit++) {
96+
if (remainingDigitCounts[digit] > 0) {
97+
remainingDigitCounts[digit]--
98+
99+
// Build next integer by appending the digit
100+
const nextValue = currentValue * 10 + digit
101+
generateNumbersRecursively(digitsRemaining - 1, nextValue)
102+
103+
// Backtrack after exploring this branch
104+
remainingDigitCounts[digit]++
105+
}
106+
}
107+
}
108+
109+
/**
110+
* Generate all possible digit subsets (from 1 to 7) where each digit d appears exactly d times.
111+
* Skip subsets whose total digit count exceeds 7.
112+
*/
113+
function generateAllSubsets() {
114+
const totalSubsets = 1 << 7 // 2^7 possible subsets of digits {1..7}
115+
116+
for (let subsetMask = 1; subsetMask < totalSubsets; subsetMask++) {
117+
let totalDigitCount = 0
118+
119+
// Compute total digit occurrences for this subset
120+
for (let bitIndex = 0; bitIndex < 7; bitIndex++) {
121+
if ((subsetMask & (1 << bitIndex)) !== 0) {
122+
totalDigitCount += bitIndex + 1
123+
}
124+
}
125+
126+
// Skip invalid subsets that exceed 7 total digits
127+
if (totalDigitCount === 0 || totalDigitCount > 7) {
128+
continue
129+
}
130+
131+
// Initialize remaining counts for this subset
132+
for (let digit = 1; digit <= 7; digit++) {
133+
remainingDigitCounts[digit] = (subsetMask & (1 << (digit - 1))) !== 0 ? digit : 0
134+
}
135+
136+
// Begin recursive generation for this subset
137+
generateNumbersRecursively(totalDigitCount, 0)
138+
}
139+
}
140+
141+
// Generate all balanced numbers once
142+
generateAllSubsets()
143+
144+
// Sort results for binary search compatibility
145+
allBalancedNumbers.sort((a, b) => a - b)
146+
147+
// Pack into a Uint32Array for cache efficiency
148+
const packedArray = new Uint32Array(allBalancedNumbers.length)
149+
for (let index = 0; index < allBalancedNumbers.length; index++) {
150+
packedArray[index] = allBalancedNumbers[index]
151+
}
152+
153+
return packedArray
154+
})()
155+
```
156+
157+
### Step 3:`nextBeautifulNumber` — 查找最小且嚴格大於 `n` 的平衡數
158+
159+
說明:利用 Step 1 的二分搜尋在 Step 2 的預生成表中查找第一個嚴格大於 `n` 的元素;若超出範圍則拋出錯誤。
160+
161+
```typescript
162+
/**
163+
* Find the smallest numerically balanced number strictly greater than n.
164+
*
165+
* A number is numerically balanced if, for every digit d in it,
166+
* the count of digit d is exactly d.
167+
*
168+
* Constraints: 0 <= n <= 10^6
169+
*
170+
* @param inputNumber - The given integer.
171+
* @returns The smallest numerically balanced number strictly greater than inputNumber.
172+
* @throws RangeError - If no numerically balanced number exists that is strictly greater than inputNumber.
173+
*/
174+
function nextBeautifulNumber(inputNumber: number): number {
175+
// Binary search to locate the first precomputed number strictly greater than inputNumber
176+
const foundIndex = upperBound(PRECOMPUTED_NUMERICALLY_BALANCED_NUMBERS, inputNumber)
177+
178+
// If not found, signal to the caller that the request is out of the supported range
179+
if (foundIndex >= PRECOMPUTED_NUMERICALLY_BALANCED_NUMBERS.length) {
180+
throw new RangeError(
181+
`No numerically balanced number strictly greater than ${inputNumber} exists within the precomputed range.`
182+
)
183+
}
184+
185+
// Return the next numerically balanced number
186+
return PRECOMPUTED_NUMERICALLY_BALANCED_NUMBERS[foundIndex]
187+
}
188+
```
189+
190+
## 時間複雜度
191+
192+
- **預生成階段**:設 **`n`** 為「所有位數 ≤ 7 的數值平衡數總數」。
193+
- 每個數字需被完整遞迴生成一次,成本為 $O(n)$;生成後需排序以支援二分搜尋,成本為 $O(n \log n)$。
194+
- 因此預生成階段的最壞時間複雜度為 $O(n \log n)$。
195+
- **查詢階段(單次)**
196+
- 預生成完成後,使用二分搜尋尋找第一個嚴格大於輸入值的平衡數,耗時 $O(\log n)$。
197+
- **最壞情況(首次呼叫)**
198+
- 當快取尚未建立時,系統需執行完整的預生成與查詢流程,整體最壞時間複雜度為 $O(n \log n)$。
199+
- 總時間複雜度為 $O(n \log n)$。
200+
201+
> $O(n \log n)$
202+
203+
## 空間複雜度
204+
205+
- 預生成集合需儲存所有平衡數,為 $O(n)$。
206+
- 輔助結構(如遞迴狀態陣列、遮罩變數等)僅需常數額外空間。
207+
- 總空間複雜度為 $O(n)$。
208+
209+
> $O(n)$
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/**
2+
* Perform a binary search to find the first element strictly greater than the given target.
3+
* @param sortedArray - A sorted Uint32Array of ascending numbers.
4+
* @param targetValue - The number to compare against.
5+
* @returns The index of the first element > targetValue, or sortedArray.length if not found.
6+
*/
7+
function upperBound(sortedArray: Uint32Array, targetValue: number): number {
8+
let leftIndex = 0
9+
let rightIndex = sortedArray.length
10+
11+
while (leftIndex < rightIndex) {
12+
const middleIndex = (leftIndex + rightIndex) >>> 1
13+
if (sortedArray[middleIndex] > targetValue) {
14+
rightIndex = middleIndex
15+
} else {
16+
leftIndex = middleIndex + 1
17+
}
18+
}
19+
20+
return leftIndex
21+
}
22+
23+
/**
24+
* Precompute all numerically balanced numbers with up to 7 digits (using digits 1 through 7).
25+
*
26+
* A number is numerically balanced if, for every digit d in the number,
27+
* the count of digit d is exactly equal to d.
28+
*/
29+
const PRECOMPUTED_NUMERICALLY_BALANCED_NUMBERS: Uint32Array = (() => {
30+
const allBalancedNumbers: number[] = []
31+
const remainingDigitCounts = new Int8Array(8) // index 1..7 represents the remaining count for each digit
32+
33+
/**
34+
* Recursive helper to generate all possible numeric combinations that satisfy the balanced condition.
35+
* @param digitsRemaining - Number of digits left to place.
36+
* @param currentValue - Current partial integer being formed.
37+
*/
38+
function generateNumbersRecursively(digitsRemaining: number, currentValue: number) {
39+
if (digitsRemaining === 0) {
40+
allBalancedNumbers.push(currentValue)
41+
return
42+
}
43+
44+
// Try placing each digit that still has remaining occurrences
45+
for (let digit = 1; digit <= 7; digit++) {
46+
if (remainingDigitCounts[digit] > 0) {
47+
remainingDigitCounts[digit]--
48+
49+
// Build next integer by appending the digit
50+
const nextValue = currentValue * 10 + digit
51+
generateNumbersRecursively(digitsRemaining - 1, nextValue)
52+
53+
// Backtrack after exploring this branch
54+
remainingDigitCounts[digit]++
55+
}
56+
}
57+
}
58+
59+
/**
60+
* Generate all possible digit subsets (from 1 to 7) where each digit d appears exactly d times.
61+
* Skip subsets whose total digit count exceeds 7.
62+
*/
63+
function generateAllSubsets() {
64+
const totalSubsets = 1 << 7 // 2^7 possible subsets of digits {1..7}
65+
66+
for (let subsetMask = 1; subsetMask < totalSubsets; subsetMask++) {
67+
let totalDigitCount = 0
68+
69+
// Compute total digit occurrences for this subset
70+
for (let bitIndex = 0; bitIndex < 7; bitIndex++) {
71+
if ((subsetMask & (1 << bitIndex)) !== 0) {
72+
totalDigitCount += bitIndex + 1
73+
}
74+
}
75+
76+
// Skip invalid subsets that exceed 7 total digits
77+
if (totalDigitCount === 0 || totalDigitCount > 7) {
78+
continue
79+
}
80+
81+
// Initialize remaining counts for this subset
82+
for (let digit = 1; digit <= 7; digit++) {
83+
remainingDigitCounts[digit] = (subsetMask & (1 << (digit - 1))) !== 0 ? digit : 0
84+
}
85+
86+
// Begin recursive generation for this subset
87+
generateNumbersRecursively(totalDigitCount, 0)
88+
}
89+
}
90+
91+
// Generate all balanced numbers once
92+
generateAllSubsets()
93+
94+
// Sort results for binary search compatibility
95+
allBalancedNumbers.sort((a, b) => a - b)
96+
97+
// Pack into a Uint32Array for cache efficiency
98+
const packedArray = new Uint32Array(allBalancedNumbers.length)
99+
for (let index = 0; index < allBalancedNumbers.length; index++) {
100+
packedArray[index] = allBalancedNumbers[index]
101+
}
102+
103+
return packedArray
104+
})()
105+
106+
/**
107+
* Find the smallest numerically balanced number strictly greater than n.
108+
*
109+
* A number is numerically balanced if, for every digit d in it,
110+
* the count of digit d is exactly d.
111+
*
112+
* Constraints: 0 <= n <= 10^6
113+
*
114+
* @param inputNumber - The given integer.
115+
* @returns The smallest numerically balanced number strictly greater than inputNumber.
116+
* @throws RangeError - If no numerically balanced number exists that is strictly greater than inputNumber.
117+
*/
118+
function nextBeautifulNumber(inputNumber: number): number {
119+
// Binary search to locate the first precomputed number strictly greater than inputNumber
120+
const foundIndex = upperBound(PRECOMPUTED_NUMERICALLY_BALANCED_NUMBERS, inputNumber)
121+
122+
// If not found, signal to the caller that the request is out of the supported range
123+
if (foundIndex >= PRECOMPUTED_NUMERICALLY_BALANCED_NUMBERS.length) {
124+
throw new RangeError(
125+
`No numerically balanced number strictly greater than ${inputNumber} exists within the precomputed range.`
126+
)
127+
}
128+
129+
// Return the next numerically balanced number
130+
return PRECOMPUTED_NUMERICALLY_BALANCED_NUMBERS[foundIndex]
131+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function nextBeautifulNumber(n: number): number {
2+
3+
}

0 commit comments

Comments
 (0)