この記事にはAndroidFamily、技術的および職場の問題が含まれています。質問するには公開アカウント [Peng Xurui] および [BaguTree Pro] Knowledge Planet にご注意ください。
T1. 総走行距離(Easy)
- タグ: シミュレーション
T2. パーティション値を見つける (中)
- タグ: 並べ替え
T3.特別アレンジ(中)
- タグ: グラフ、状態圧縮、バックトラッキング
T4. 壁をペイントする (ハード)
- タグ: 動的プログラミング、01 バックパック
T1. 総走行距離(Easy)
https://leetcode.cn/problems/total-distance-traveled/
解決策(シミュレーション)
WA は手を挙げてください:
class Solution {
fun distanceTraveled(mainTank: Int, additionalTank: Int): Int {
return mainTank * 10 + Math.min(additionalTank, mainTank / 5) * 10
}
}
この質問では、給油後に 5 リットルのオイルを補充する状況を考慮する必要があります。
class Solution {
fun distanceTraveled(mainTank: Int, additionalTank: Int): Int {
var ret = 0
var x = mainTank
var y = additionalTank
while (x >= 5) {
val time = x / 5
ret += time * 50
x %= 5
val diff = Math.min(time, y)
y -= diff
x += diff
}
return ret + x * 10
}
}
複雑さの分析:
- 時間計算量: O(log_5{n})
- 空間の複雑さ: O(1)
T2. パーティション値を見つける (中)
https://leetcode.cn/problems/find-the-value-of-the-partition/
解決策(仕分け)
並べ替え後の最小差を計算します。
class Solution {
fun findValueOfPartition(nums: IntArray): Int {
nums.sort()
var ret = Integer.MAX_VALUE
for(i in 1 until nums.size) {
ret = Math.min(ret, nums[i] - nums[i - 1])
}
return ret
}
}
複雑さの分析
- 時間計算量: O(nlgn)
- 空間複雑度: O(lgn)
T3.特別アレンジ(中)
https://leetcode.cn/problems/special-permutations/
問題解決 (グラフ + 状態圧縮 + バックトラッキング)
タイトルは隣接する要素間で少なくとも一方向の可算性を必要とするため、データを前処理して、各要素が (x の隣接するペアの x として使用される場合に次の番号 y が選択できる番号を記録する必要がある) と考えるのは簡単です。 , y)、つまり、x から y への一方向のエッジが存在します。
val edge = HashMap<Int, MutableList<Int>>()
for ((i,x) in nums.withIndex()) {
edge[x] = LinkedList<Int>()
for (y in nums) {
if (x == y) continue
if (x % y == 0 || y % x == 0) edge[x]!!.add(y)
}
}
この質問には最大 14 個の数字があるため、完全な順列を使用するには少なくとも 14 個が必要です! さまざまな状況で、暴力的な完全な順列はタイムアウトになりますか? 経験値 10! = 3628800 (ほぼ 3 · 10^6 に等しい) を使用できます。その場合、14! は 3 · 10^6 · 10^4 より大きくなければならず、明らかにタイムアウトになります。
状態圧縮を使用すると、この問題を解決できます。x が最終的に選択され、選択されたリストが s であるときのオプションの数を表す f(x, s) を定義します。s の 2 進ビットは、次の数の選択と非選択を表します。さまざまな添字 State、s によってさまざまな配置スキームを誘導することができ、最後にメモを使用して枝刈りを行います。short 型のビット数で 14 はカバーできるので、初期状態として (1 << 14) - 1、終了条件として 0 を使用します。
class Solution {
private val MOD = 1000000007
fun specialPerm(nums: IntArray): Int {
val n = nums.size
val mask = 1 shl n
// 预处理
val edge = HashMap<Int, MutableList<Int>>()
for (x in nums.indices) {
edge[x] = LinkedList<Int>()
for (y in nums.indices) {
if (nums[x] != nums[y] && nums[x] % nums[y] == 0 || nums[y] % nums[x] == 0) edge[x]!!.add(y)
}
}
// 备忘录
val memo = Array(n) {
IntArray(mask) {
-1} }
fun backTrack(preIndex: Int, unUsed:Int) : Int{
// 终止条件
if (unUsed == 0) return 1
// 读备忘录
if (-1 != memo[preIndex][unUsed]) return memo[preIndex][unUsed]
var ret = 0
for (choice in edge[preIndex]!!) {
if (unUsed and (1 shl choice) == 0) continue
ret = (ret + backTrack(choice, unUsed xor (1 shl choice))) % MOD
}
// 存备忘录
memo[preIndex][unUsed] = ret
return ret
}
// 枚举首个元素的多种情况
var ret = 0
for (i in nums.indices) {
ret = (ret + backTrack(i, (mask - 1) xor (1 shl i))) % MOD
}
return ret
}
}
複雑さの分析:
- 時間計算量: O(n 2 2 n) 合計 n 2^n 個の状態があり、各状態の遷移の数は最大でも O(n) です。
- スペースの複雑さ: O(n 2^n) の思い出のスペース。
T4. 壁をペイントする (ハード)
https://leetcode.cn/problems/painting-the-walls/
ソリューション(01 バックパック)
アイデアについては、Lingshen のソリューションを参照してください。
有給の塗装業者が優先的に最も低コストで壁を塗装できるという事実を考慮した貪欲な計画は間違っています。
i 番目の壁には、有料ペインターを割り当てるか無料ペインターを割り当てるかの 2 つの選択肢しかなく、次のとおりであることが簡単にわかります。
- ペイウォール + フリーウォール = n
- 有料の壁塗装時間の合計 ≥ 無料の壁の数
2 つの公式を組み合わせると、ペイウォールの数 + 有料の壁ブラッシング時間の合計 ≥ n、つまり、(有料の壁ブラッシング時間 + 1) の合計 ≥ n となります。そしてこのとき、n 個の壁から x 個のペイウォールを選択する問題となり、(壁塗装時間 + 1) ≥ n のときの最小コストは 0 1 ナップサックモデルで解けます。
i を考慮し、(壁描画時間 + 1) が j である場合の最小オーバーヘッドを表す dp[i][j] を定義します。その場合、i 番目の壁には 2 つの転送方法があります。
- 有料ペインターに割り当て (オプション): dp[i][j] = dp[i - 1][j - time[i] - 1] +cost[i]
- フリー ペインタ (選択されていない) に割り当てます: dp[i][j] = dp[i - 1][j]
初期条件: dp[0][0] = 0。これは、0 番目の壁が考慮され、(壁ペイント時間 + 1) が 0 である場合の最小コストが 0 であることを意味します。
class Solution {
fun paintWalls(cost: IntArray, time: IntArray): Int {
val INF = 0x3F3F3F3F
val n = cost.size
// 刷墙时间超过 n 没有意义
val dp = Array(n + 1) {
IntArray(n + 1) {
INF } }
// 初始状态(付费刷墙时间为 0,开销为 0)
for (i in 0 .. n) dp[i][0] = 0
// 枚举物品
for (i in 1 .. n) {
// 枚举状态
for (j in 1 .. n) {
val t = time[i - 1] + 1
val c = cost[i - 1]
dp[i][j] = dp[i - 1][j]
dp[i][j] = Math.min(dp[i][j], dp[i - 1][Math.max(j - t, 0)] + c)
}
}
return dp[n][n]
}
}
このうち、j < t の場合、j は支払った壁塗装時間の合計、t は i 番目の壁を塗装する時間を表します。j - t < 0 の場合、壁塗装後に支払った壁塗装時間の一部を破棄することに等しく、このときのコストは初期状態から i 番目の壁を選択するコストよりも悪くはなりませんが、つまり、 dp[i-1][Math .max(jt,0)] + c です。
0 1 ナップザック問題では、通常、ローリング配列を使用して空間を最適化できます。
class Solution {
fun paintWalls(cost: IntArray, time: IntArray): Int {
val INF = 0x3F3F3F3F
val n = cost.size
// 刷墙时间超过 n 没有意义
val dp = IntArray(n + 1) {
INF }
// 初始状态(付费刷墙时间为 0,开销为 0)
dp[0] = 0
// 枚举物品
for (i in 1 .. n) {
// 枚举状态(逆序)
for (j in n downTo 1) {
val t = time[i - 1] + 1
val c = cost[i - 1]
dp[j] = Math.min(dp[j], dp[Math.max(j - t, 0)] + c)
}
}
return dp[n]
}
}
複雑さの分析:
- 時間計算量: O(n^2)
- 空間の複雑さ: (n)